c# – Firebase – Verifying api requests have a valid id token in .net 5 – Code Utility

[

I’m using firebase auth from a flutter app which gets a jwt from the front end like so:

accessToken = await user!.getIdToken(true);

Now I want my .net 5 AspNetCore back end app to verify the id token.

I add the firebase admin SDK to my .net app like so:

In startup.cs:

FirebaseApp.Create();

The Firebase docs show how to verify the id token:

FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
    .VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;

But where does this code go? It’s not something I want to manually do for every controller endpoint in my AspNetCore app. Can this be done as middleware to occur automatically on every api request?

EDIT: Eeek I don’t even think Firebase Admin is what I want. I just want to verify the jwt id token which is created on the client using Firebase Auth for Flutter, in the most firebasey way possible. It is not for admins, it is for normal users. I thought Firebase Admin was that. What direction should I take? I have found all sorts of articles online about different strategies so am confused which is the right one. Is this sufficient? https://dominique-k.medium.com/using-firebase-jwt-tokens-in-asp-net-core-net-5-834c43d4aa00 It doesn’t seem very firebasey and seems quite over simplified? I think Dharmaraj’s answer below is the most firebasey way, but I’m unsure where idToken gets a value from (how to get from the request headers? Otherwise it is just null).

,

I essentially followed this SO answer for the non-firebase stuff and this tutorial for the firebase stuff, and it all works, I have access to the user, the access token, the claims, everything, through the extendedContext.userResolverService, and the id token is verified in all of my web controller endpoints that use [Authorize].

Since I have the id token in all my controllers, I could manually call this from any controller too however it is not necessary.

FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
    .VerifyIdTokenAsync(idToken);
string uid = decodedToken.Uid;

userResolverService.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Http;

public class UserResolverService
{
    public readonly IHttpContextAccessor _context;

    public UserResolverService(IHttpContextAccessor context)
    {
        _context = context;
    }

    public string GetGivenName()
    {
        return _context.HttpContext.User.FindFirst(ClaimTypes.GivenName).Value;
    }

    public string GetSurname()
    {
        return _context.HttpContext.User.FindFirst(ClaimTypes.Surname).Value;
    }

    public string GetNameIdentifier()
    {
        return _context.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
    }

    public string GetEmails()
    {
        return _context.HttpContext.User.FindFirst("emails").Value;
    }
}

Extend the DataContext (via composition):

namespace Vepo.DataContext {
    public class ExtendedVepoContext
    {
        public VepoContext _context;
        public UserResolverService _userResolverService;

        public ExtendedVepoContext(VepoContext context, UserResolverService userService)
        {
            _context = context;
            _userResolverService = userService;
            _context._currentUserExternalId = _userResolverService.GetNameIdentifier();
        }
    }
}

startup.cs:

public void ConfigureServices(IServiceCollection services)
        {
            ....

        services.AddHttpContextAccessor();

        services.AddTransient<UserResolverService>();

        services.AddTransient<ExtendedVepoContext>();

            FirebaseApp.Create(new AppOptions()
            {
                Credential = GoogleCredential.FromFile("firebase_admin_sdk.json"),
            });

            services
                .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.Authority = "https://securetoken.google.com/my-firebase-app-id";
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateIssuer = true,
                        ValidIssuer = "https://securetoken.google.com/my-firebase-app-id",
                        ValidateAudience = true,
                        ValidAudience = "my-firebase-app-id",
                        ValidateLifetime = true
                    };
                });

also startup.cs:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env, VepoContext context, ISearchIndexService searchIndexService)
{ ....

    app.UseAuthentication();
    app.UseAuthorization();

Then add auth into the controller endpoint like so:

[HttpPost]
[Authorize]
public async Task<ActionResult<GroceryItemGroceryStore>> PostGroceryItemGroceryStore(GroceryItemGroceryStore groceryItemGroceryStore)
{...

You could take it a step further, and do things with the user on every save etc like add metadata:

the entity to save with metadata added:

public interface IDomainEntity<TId>
{
    TId Id { get; set; }    
    DateTime SysStartTime { get; set; }
    DateTime SysEndTime { get; set; }
    string CreatedById { get; set; }
    User CreatedBy { get; set; }
    string UpdatedById { get; set; }
    User UpdatedBy { get; set; }
}

my DataContext:

public class VepoContext : DbContext
{
    public VepoContext(DbContextOptions<VepoContext> options)
        : base(options)
    {
    }

        public DbSet<User> User { get; set; }

        public string _currentUserExternalId;
        
        public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            var user = await User.SingleAsync(x => x.Id == _currentUserExternalId);

            AddCreatedByOrUpdatedBy(user);

            return (await base.SaveChangesAsync(true, cancellationToken));
        }

        public override int SaveChanges()
        {
            var user = User.Single(x => x.Id == _currentUserExternalId);

            AddCreatedByOrUpdatedBy(user);

            return base.SaveChanges();
        }

        public void AddCreatedByOrUpdatedBy(User user)
        {
            foreach (var changedEntity in ChangeTracker.Entries())
            {
                if (changedEntity.Entity is IDomainEntity<int> entity)
                {
                    switch (changedEntity.State)
                    {
                        case EntityState.Added:
                            entity.CreatedBy = user;
                            entity.UpdatedBy = user;
                            break;
                        case EntityState.Modified:
                            Entry(entity).Reference(x => x.CreatedBy).IsModified = false;
                            entity.UpdatedBy = user;
                            break;
                    }
                }
            }
        }

,

The “Admin” word in Firebase Admin SDK name implies that you use this SDK in trusted environments, such as you development machine, or a server that you control – such as is the case with your ASP.NET code.

The Admin SDK can verify tokens from any kind of user. In fact, it has no knowledge of types of users, it just verifies whether the token is valid.

,

You add the functions which authenticate requests in a middleware. I’m am not sure about how that goes in .NET but I looked up for some middleware snippet and found this:

public class Startup
{
    public Startup()
    {
    } 

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
         app.Run(MyMiddleware);
    }

    private Task MyMiddleware(HttpContext context) 
    {
        // Add the code here and verify the IDToken
        //FirebaseToken decodedToken = await FirebaseAuth.DefaultInstance
    .VerifyIdTokenAsync(idToken); 
        //string uid = decodedToken.Uid;
        return context.Response.WriteAsync("Hello World! ");
    }
}

Now that middleware will check for the token for all the specified endpoints. You can easily read for the idToken (from headers or body) in there and return an error in case of any issue.

If the token is valid, you can add the user’s UID and any other info that you require to the context object.

]