9

Working on a project that uses Firebase for some data storage, and our client requests that the server be implemented with C#.NET. We're setting up REST endpoints on the server so that the client is able to communicate with it for a few purposes (for example, triggering an algorithm to run which can only occur on the server).

Firebase recommends we identify users via an ID token, as noted here: https://firebase.google.com/docs/auth/server/verify-id-tokens#verify_id_tokens_using_a_third-party_jwt_library

Since there is no official .NET Firebase server SDK that supports token authentication, we've resorted to using a 3rd-party JWT library to do this: https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet

As specified in the Firebase documentation, we're first generating a sending a token to the server. After checking a few different fields in the token, we're using the kid field to grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com

We've been digging around through documentation and StackOverflow for a long time, but we can't find a way to use this public key to do this, as specified by the Firebase documentation:

Finally, ensure that the ID token was signed by the private key corresponding to the token's kid claim. Grab the public key from https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com and use a JWT library to verify the signature.

The Firebase documentation doesn't really offer any explanation for this, and neither does the documentation for the library we are using. So we've not been able to even get a basic idea as to how we could possibly verify the token was signed by a private key when all we are given is the public key.

What would be the best way to verify that the token was actually signed by the correct private key?

AL.
  • 36,815
  • 10
  • 142
  • 281
nolasaint
  • 145
  • 1
  • 8

5 Answers5

8

You should be able to accomplish the token validation by doing something like the following, which leverages the System.IdentityModel.Tokens.Jwt Nuget package to perform most the validations:

class Program {
  static HttpClient client = new HttpClient();
  static void Main() { RunAsync().Wait(); }

  static async Task RunAsync() {
    string encodedJwt = "[TOKEN_TO_BE_VALIDATED]";
    // 1. Get Google signing keys
    client.BaseAddress = new Uri("https://www.googleapis.com/robot/v1/metadata/");
    HttpResponseMessage response = await client.GetAsync(
      "x509/securetoken@system.gserviceaccount.com");
    if (!response.IsSuccessStatusCode) { return; }
    var x509Data = await response.Content.ReadAsAsync<Dictionary<string, string>>();
    SecurityKey[] keys = x509Data.Values.Select(CreateSecurityKeyFromPublicKey).ToArray();
    // 2. Configure validation parameters
    const string FirebaseProjectId = "[FIREBASE_PROJECT_ID]";
    var parameters = new TokenValidationParameters {
      ValidIssuer = "https://securetoken.google.com/" + FirebaseProjectId,
      ValidAudience = FirebaseProjectId,
      IssuerSigningKeys = keys,
    };
    // 3. Use JwtSecurityTokenHandler to validate signature, issuer, audience and lifetime
    var handler = new JwtSecurityTokenHandler();
    SecurityToken token;
    ClaimsPrincipal principal = handler.ValidateToken(encodedJwt, parameters, out token);
    var jwt = (JwtSecurityToken)token;
    // 4.Validate signature algorithm and other applicable valdiations
    if (jwt.Header.Alg != SecurityAlgorithms.RsaSha256) {
      throw new SecurityTokenInvalidSignatureException(
        "The token is not signed with the expected algorithm.");
    }
  }
  static SecurityKey CreateSecurityKeyFromPublicKey(string data) {
    return new X509SecurityKey(new X509Certificate2(Encoding.UTF8.GetBytes(data)));
  }
}

List of using statements for sample program:

using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Net.Http;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Tokens;
João Angelo
  • 56,552
  • 12
  • 145
  • 147
  • Getting a SecurityTokenSIgnatureKeyNotFoundException. Gonna dig into this deeper, thanks for the starting point! If I get it working with this solution, I'll go ahead and accept the answer. – nolasaint Oct 12 '16 at 19:40
  • That will happen if none of the security keys that you provided match the one the JWT declared to be using. If you decode the token in [jwt.io](https://jwt.io/) you'll likely find a `kid` property in the header. The value should match one of the certificates returned in the list obtained from Google. If it doesn't, that explains the exception. – João Angelo Oct 13 '16 at 07:53
  • I have implemented your solution and hit the same exception. I have checked the kid of my token against the certs at https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com and it matches one of the 4. The four certs are loaded into the keys array but SecurityTokenSIgnatureKeyNotFoundException is thrown on validation. Any ideas? – Keith Coughtrey Nov 01 '16 at 04:01
  • Note that for this to work I had to install an additional Nuget package, otherwise `ReadAsAsync<>` was not recognised as a valid method. `Install-package Microsoft.AspNet.WebApi.Client` https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Client/ – Rory McCrossan Nov 23 '17 at 14:20
  • I am looking for a similar way to validate firebase tokens in Azure APIM using validate-jwt policy with JWKS and issuer key? – vishnu Oct 01 '19 at 21:57
6

Now we can use the Firebase Admin SDK for .NET.

https://github.com/Firebase/firebase-admin-dotnet

var decoded = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(idToken);
var uid = decoded.Uid;
Suyon Won
  • 346
  • 2
  • 8
4

Use the following code snippet in Startup.cs to create a service that automatically validates the JWT token when a request received to the server. After using this code snippet, you have to use [Authorize] attribute above [ApiController] in controller class file(s) to force program to authenticate before giving access to the action methods in that particular controller class.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options => {
        options.Authority = "https://securetoken.google.com/<PROJECT ID>";
        options.TokenValidationParameters = new TokenValidationParameters {
            ValidIssuer = "https://securetoken.google.com/<PROJECT ID>",
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidAudience = "<PROJECT ID>",
            ValidateLifetime = true
        };
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{

    app.UseAuthentication();

}

Edit> How you should send the Token to the server.

As you see, you have to send a Post/Get request with Authorization header. The value should be in Bearer format. Please check firebase documentation to find out how to extract the token id from an authenticated user.

https://firebase.google.com/docs/reference/node/firebase.auth.Auth#signinwithemailandpassword https://firebase.google.com/docs/reference/node/firebase.auth#usercredential https://firebase.google.com/docs/reference/node/firebase.User#getidtoken

enter image description here

Don Dilanga
  • 2,722
  • 1
  • 18
  • 20
  • Is this working for you? I keep getting 401. Is there any additional firebase setup needed? – Marko Prcać Feb 20 '21 at 11:29
  • yeah it works for me :) 401 is unauthorized error, it means the token is not validated. You have to send the token as a bearer token to the server, it means header should have Authorization tag, and the value should be Bearer . – Don Dilanga Feb 21 '21 at 08:50
  • You don't have to configure firebase anymore further as long as you have the token. If you want to test whether it works or not. Print the firebase token after you logged in in console, and then use Postman to send a request to your server as a bearer token in Authorization tab. Make sure you add [Authorize] attribute above any action method as well. then you can execute whatever the action method to display your result. If it displays, it means you are success. – Don Dilanga Feb 21 '21 at 08:55
  • 1
    Hi. Yes your code is correct, nothing else has to be done on Firebase. Issue was that I am using several JWT providers in the same solution (identityServer4 and Firebase). I had to redefine what authorized "means" in order to get every token validated by both Identity providers.This answer helped me a lot: https://stackoverflow.com/a/49706390/6357154 to get bot providers to work together. Thank you – Marko Prcać Feb 21 '21 at 10:31
  • This solution is incomplete as it does not validate the signature of the issuer. You need to also implement the `IssuerSigningKeyResolver` or otherwise supply the signing keys to verify the signature of the JWT. This should be combined with the accepted answer. – Charles Chen Sep 24 '22 at 18:25
1

If you're using another Newtonsoft.JSON library like me and don't want to import the Microsoft one, try this one: https://gist.github.com/saltyJeff/41029c9facf3ba6159ac019c1a85711a

Use Verify(string token) to asynchronously verify that a token is valid: returns the unique identifier of the user if valid, and null if invalid.

saltyJeff
  • 67
  • 1
  • 4
0

Firebase has a real lack of support for c sharpers. I've created a 3rd party token verification library for the C# community. https://github.com/gtaylor44/FirebaseAuth

Most of the code is based on João Angelo's answer. I've added caching of the web request using the Cache-Control["max-age"] property in response header as documented by Firebase for better performance.

Greg R Taylor
  • 3,470
  • 1
  • 25
  • 19