0
votes

So I’m using firebase for authentication on my WebAPI app. It gets a JWT token and then authenticates it and then puts the claims in the HttpContext.User.

My only problem is I just want to use Firebase Authentication for authentication, not for authorization. I want to use ASP.NET Identity for this.

so right now, when someone connects to my server, I’ll check if they don’t have an account, if not I will create one for them.

var name = _contextAccessor.HttpContext.User.Claims.First(c => c.Type == "user_id").Value;

ApplicationUser user = await _userManager.FindByNameAsync(name);

if (user == null)
{
    user = new ApplicationUser(name);
    await _userManager.CreateAsync(user);                
}

So this works, and now there is a record in the ASPNetUsers against the user and later on, I can give it Claims and Roles against whatever business rules I’d like.

However, my question is, previously, when I’ve used ASP.NET Identity I’ve been able to leverage all of the built-in features like the Authorize attribute to do my authorization.

So if both authentication and authorization is done using ASP.NET Identity, I can write

[Authorize(Roles = "Administrator")]

Obviously, that won’t work with external authentication because HttpContext.User is the Firebase Authenticated user, not the corresponding ASP.NET Identity user that has the Administrator role.

Is there a way to customize the existing Authorize attribute to configure it to somehow convert my firebase token into an ASP.NET Identity so that it would recognize the roles and claims it has or if I wanted to do all this through middleware, am I going to need to write my own authorize attribute?

3

3 Answers

1
votes

Ok so I've finally figured it out. It seems there's a couple of events you can intercept during the Jwt authentication process. In particular, there is an OnTokenValidated event.

services.AddJwtBearer(options =>
{
    ...

    options.Events = new JwtBearerEvents
    {

        OnTokenValidated = async ctx =>
        {
            // 1. grabs the user id from firebase
            var name = ctx.Principal.Claims.First(c => c.Type == "user_id").Value;

            // Get userManager out of DI
            var _userManager = ctx.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();

            // 2. retrieves the roles that the user has
            ApplicationUser user = await _userManager.FindByNameAsync(name);
            var userRoles = await _userManager.GetRolesAsync(user);

            //3.  adds the role as a new claim 
            ClaimsIdentity identity = ctx.Principal.Identity as ClaimsIdentity;
            if (identity != null)
            {
                foreach (var role in userRoles)
                {
                    identity.AddClaim(new System.Security.Claims.Claim(ClaimTypes.Role, role));
                }
            }

        }

    };
});

So what the code above is saying

  1. once the token is authenticated, take the userId from the external provider
  2. Go into ASP.NET Identity and find the user and the roles for that user [remember in my original question, I insert a user into ASP.NET Identity table when they first log in, I'm just grabbing that user]

  3. Insert the roles of that user back into the ClaimsIdentity

The result is that when the Authorise attribute runs, it'll include the AspNET Identity Roles of the user in the check and I can do something like below and it'll check the role.

[Authorize(Roles = "Administrator")]
0
votes

Yes you can use the authorize attribute with some modification,

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,Roles = "Administrator")]

But if you want to make it a default scheme, then refer to this answer Here is a summary, Add it to he Startup class,

services.AddAuthentication(cfg =>
        {
            cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
            cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
        })
0
votes

I think this should make your Authorization work:

  1. You have to make ClaimsPrincipal the base class of your ApplicationUser class. (ClaimsPrincipal implements the IPrincipal interface)
  2. Then inside your ApplicationUser class implement/override the IsInRole method that comes with the ClaimsPrincipal (base) class. IsInRole official documentation. The role argument of the method will contain the value of the Roles property on the AuthorizeAtribute that you specify.
  3. Set the HttpContext.User using your instance of type ApplicationUser. (explanation: Setting the property User on Httpcontext allows you to specify (=customize) the user of the current request within asp.net request processing)