4

Given an asp.net core api and working firebase auth using only the [Authorize] attribute, how can I add custom claims to the token to use [Authorize(Policy = "admin")]? The admin SDK is only available for node.js, Java, python or go and I can't find any docs on how to use the Firebase Admin API directly. From my understanding the user claims have to be stored in the Firebase Auth backend.

ReyAnthonyRenacia
  • 17,219
  • 5
  • 37
  • 56
user3838018
  • 305
  • 3
  • 14

2 Answers2

4

Since the Firebase Admin .NET SDK 1.1 is released the Control Access With Custom Claims feature is supported.

You can find the repository here: https://github.com/firebase/firebase-admin-dotnet

Setup guide and documentation: https://firebase.google.com/docs/admin/setup

Solution:

// Initialize the admin SDK with your service account keys
FirebaseApp.Create(new AppOptions()
{
    Credential = GoogleCredential.FromFile("path/to/serviceAccountKey.json"),
});

// Create the custom user claim that has the role key
var claims = new Dictionary<string, object>
{
    { ClaimTypes.Role, "Administrator" }
};

// This will call the Firebase Auth server and set the custom claim for the user
await FirebaseAuth.DefaultInstance.SetCustomUserClaimsAsync(uid, claims);
Dominik
  • 649
  • 7
  • 16
1

To the best of my knowledge, with the C# Firebase Admin SDK you can't. I had the same issue and, at first, I used the token to get the user id, retrieved the roles for the user id from the database, set up a Claims array, and created a custom token with the added claims with the . Then I sent it to Firebase via their REST API to get a regular Firebase ID and Refresh token as explained in the reference guide. Finally i gave the token to the client. However, I had issues (mostly 404 errors) ad it is not well documented what changes you need to make to the verification middleware to accept the new reissued tokens, and I was never able to get the refresh Token as it was never in the REST API response. I even followed the steps in here and here and minted my own custom token according to Firebase instructions, but was never able to make the darn thing work.

So I ended up doing something a little different. I retrieved the user claims from the firebase IdToken and created my own token using using System.IdentityModel.Tokens.Jwt. There I added my own claims and roles and sent that to the client. Now the client logs in using FireBase but i have full control of the tokens to use my API

This is how I modified the startup. Keep in mind I haven't played with all options

var key = System.Text.Encoding.UTF8.GetBytes(Configuration["MyToken:Key"]);
    services.AddAuthentication(
        auth => {
        auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;})
    .AddJwtBearer(options => {
        options.RequireHttpsMetadata = false;
        options.IncludeErrorDetails = true;
        options.TokenValidationParameters = new TokenValidationParameters {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(key),
            ValidateIssuer = false,
            ValidateAudience = false,
            ValidateLifetime = true
        };
   });

this is the simplified Encoding function :

public string EncodeTokenMS(string uid, Claim[] claims)
{
    var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(<Secret key here>));
    var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature);
    var now = DateTimeOffset.UtcNow;
    var _claims = new[] {
        new Claim(JwtRegisteredClaimNames.Iss, "https://auth.xxx.com"),
        new Claim(JwtRegisteredClaimNames.Aud, "https://auth.xxx.com"),
        new Claim(JwtRegisteredClaimNames.Sub, uid),
        new Claim(JwtRegisteredClaimNames.AuthTime, now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
        new Claim(JwtRegisteredClaimNames.Iat, now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
        new Claim(JwtRegisteredClaimNames.Exp, now.AddMinutes(60).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
        // TODO Actually write the code to get tenancy, roles, and user info 
        new Claim("tenantId", "PippoBaudo" ),
        new Claim(ClaimTypes.Role, "User")
    };

    //Create and sign the JWT, and write it to a string
    var jwt = new JwtSecurityToken(
        claims: _claims,
        signingCredentials: signingCredentials);

    return new JwtSecurityTokenHandler().WriteToken(jwt);
}

The drawback of this approach is there is an added roundtrip when the user signs up, however it is only when the user needs a token so it shouldn't add a great deal of latency, and I can increase the lifetime of the token to more than 60 minutes.

Suggestions and (constructive) comments on this approach are welcomed!

Hope this helps!

Alberto L. Bonfiglio
  • 1,767
  • 23
  • 38