Firebase Authentication (JWT) with .NET Core

23,509

Solution 1

Firebase uses the RSA256 asymmetric key cryptosystem, which means it has a public and a private key. Signing a token happens with the private key, while verifying a token happens with the public key.

The following diagram illustrates how the token signing happens.

enter image description here

During signing in and accessing a secure endpoint, the following steps are involved.

  1. When our application starts up (and then later also periodically) the JwtBearerMiddleware calls https://securetoken.google.com/my-firebase-project/.well-known/openid-configuration, where it can access the current public keys of Google. It's important that when we're using a public key asymmetric cryptosystem, the public key is not kept as a secret, but it is published in plain sight.
  2. A client signs in using their credential through Firebase (these are the client's own credentials, they have nothing to do with the key used to sign the token).
  3. If the signin was successful, then Firebase constructs a JWT token for the client. A crucial part of this token is that it's signed using the private key of the key pair. In contrast to the public key, the private key is never exposed publicly, it is kept as a secret inside Google's infrastructure.
  4. The client receives the JWT token.
  5. The client calls a secure endpoint on our Api, and puts the token in the Authorization header. At this point the JwtBearerMiddleware in the pipeline checks this token, and verifies if it's valid (if it was signed with Google's private key). The important thing to realize here is that in order to do the verification, our Api does not need to have access to the private key. Only the public key is necessary to do that. After verification, the middleware populates HttpContext.User, and HttpContext.User.Identity.IsAuthenticated accordingly.

You can find an even simpler description of this concept on the RSA Wikipedia page, and some more information about Firebase + ASP.NET in my blog post.

Solution 2

For the backend side, there is a Nuget package that does the trick for you:

Install-Package AspNetCore.Firebase.Authentication

public void Configure(IApplicationBuilder app, IHostingEnvironment env,  ILoggerFactory loggerFactory)
{      
app.UseFirebaseAuthentication("https://securetoken.google.com/MYPROJECTNAME", "MYPROJECTNAME");
}

It does the audience, issuer, signature and validity validation

Solution 3

The NuGet packet is using the old ASP NET Core 1.x Authentication mechanism

The authentication has been changed: https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/identity-2x

Share:
23,509

Related videos on Youtube

Izzy Rodriguez
Author by

Izzy Rodriguez

I'm a Software Developer with 15+ years of experience. Absolutely passionate about technology in general, from the simplest transistor, all the way to Artificial Intelligence. Always updating myself to stay in touch with the current trends in software development. I'm very easy to deal with, really enjoy work in groups and always in a good mood.

Updated on July 05, 2022

Comments

  • Izzy Rodriguez
    Izzy Rodriguez almost 2 years

    I'm developing a simple API that handles Authentication made by Firebase - to be used later with Android clients.

    So in Firebase console I enabled Facebook and Google sign-in methods and created a sample html page that I can use it to test the login method - this next function is called by a button:

    function loginFacebook() {
            var provider = new firebase.auth.FacebookAuthProvider();
            var token = "";
            firebase.auth().signInWithPopup(provider).then(function (result) {
                var token = result.credential.accessToken;
                var user = result.user;
                alert("login OK");
                user.getToken().then(function (t) {
                    token = t;
                    loginAPI();
                });
            }).catch(function (error) {
                var errorCode = error.code;
                var errorMessage = error.message;
                alert(errorCode + " - " + errorMessage);
            });
        }
    

    next I use the token and send it to my API with a simple ajax call from jQuery here:

    function loginAPI()
    {
        $.ajax({
            url: "http://localhost:58041/v1/Users/",
            dataType: 'json',
            type: 'GET',
            beforeSend: function (xhr) {
                xhr.setRequestHeader("Accept", "application/json");
                xhr.setRequestHeader("Content-Type", "application/json");
                xhr.setRequestHeader("Authorization", "Bearer " + token);
            },
            error: function (ex) {
                console.log(ex.status + " - " + ex.statusText);
            },
            success: function (data) {
                console.log(data);
                return data;
            }
        });
    }
    

    Next stop: the API backend - written with .NET Core.

    Under the Startup I've configured the JwtBearer Auth (package Microsoft.AspNetCore.Authentication.JwtBearer):

    app.UseJwtBearerAuthentication(new JwtBearerOptions
    {
        AutomaticAuthenticate = true,
        IncludeErrorDetails = true,
        Authority = "https://securetoken.google.com/PROJECT-ID",
        TokenValidationParameters = new TokenValidationParameters
        {  
            ValidateIssuer = true,
            ValidIssuer = "https://securetoken.google.com/PROJECT-ID",
            ValidateAudience = true,
            ValidAudience = "PROJECT-ID",
            ValidateLifetime = true,
        },
    });
    

    And here is the Controller code - with the [Authorize] attribute:

    [Authorize]
    [Route("v1/[controller]")]
    public class UsersController : Controller
    {
        private readonly ILogger _logger;
        private readonly UserService _userService;
    
        public UsersController(ILogger<UsersController> logger, UserService userService)
        {
            _logger = logger;
            _userService = userService;
        }
    
        [HttpGet]
        public async Task<IList<User>> Get()
        {
            return await _userService.GetAll();
        }
    }
    

    The API response is 200 OK (HttpContext.User.Identity.IsAuthenticated is true inside the Controller), but I think it shouldn't. My problem is that I'm not entirely sure that this is secure.

    How this is checking the signature part of the JWT token? I saw a lot of code samples mentioning x509 or RS256 algorithm, where do they fit with this? Shouldn't be checking against some kind of certificate or private key with the IssuerSigningKey or TokenDecryptionKey from the TokenValidationParameters class? What I'm missing?

    Relevant sources of knowledge about the issue:

    • Mark Vincze
      Mark Vincze about 7 years
      The response is 200 OK even if you don't pass the Authorization header? That's definitely wrong. Do you have the app.UseMvc() call before or after app.UseJwtBearerAuthentication()?
    • Izzy Rodriguez
      Izzy Rodriguez about 7 years
      If I don't pass the Authorization header, the the response is 401. And the app.UseMvc() is after the app.UseJwtBearerAuthentication(). I'm just not sure about the security. I tried to hack my app and was not able to. Maybe I'm just paranoid.
    • Mark Vincze
      Mark Vincze about 7 years
      Hmm, but you say the response "shouldn't be 200". Why? Are you intentionally passing an incorrect token in the header? Or why do you expect the response not to be 200?
    • Izzy Rodriguez
      Izzy Rodriguez about 7 years
      Since I'm not providing a private key anywhere in my code so I think that shouldn't work, but after being unable to hack it, I think I'm just paranoid.
    • Mark Vincze
      Mark Vincze about 7 years
      I'm afraid you misunderstand how the token signing works in this flow. Your application is not supposed to provide (or have) a private key (or any kind of key). I added a description of the flow to my blog post: blog.markvincze.com/secure-an-asp-net-core-api-with-firebase‌​/…
  • Primico
    Primico over 6 years
    Not sure this works with ASP .NET Core 2.0. I get the following error: Could not load type 'Microsoft.AspNetCore.Builder.JwtBearerOptions' from assembly 'Microsoft.AspNetCore.Authentication.JwtBearer, Version=2.0.0.0
  • Tzvi Gregory Kaidanov
    Tzvi Gregory Kaidanov almost 6 years
    @Raphaël What is ApiKey for?
  • Raphaël
    Raphaël almost 6 years
    @TzviGregoryKaidanov actually it's in the appsettings, but unused, as the middleware only validate token & signature, which doesn't require ApiKey
  • Raphaël
    Raphaël almost 6 years
    @Primico I published a .netcore2.0 compatible package
  • Raphaël
    Raphaël almost 6 years
    Published the package AspNetCore.Firebase.Authentication 2.0 which support .aspnetcore2.0
  • Tang Thanh Tam
    Tang Thanh Tam almost 4 years
    Hi @Raphaël, I'm using your nuget package very good on .Net Core 2.1. But it's not working on .Net Core 3.1. Could you please help me to check this issue ?
  • Dan
    Dan over 3 years
    I've created a gist that covers this gist.github.com/deeja/c67e6027ca37a8d6a367b8b8bf86d5c6