简体   繁体   中英

Proper Core 3.0 API middleware to connect Azure AD B2C user to my database account?

I have a.Net Core 3 API that properly authorizes and authenticates the tokens passed to it via Azure AD B2C. It also has a little bit of custom code built to check the scopes passed in the JWT token to confirm access to endpoints. (I'm using the Microsoft.AspNetCore.Authentication.AzureADB2C.UI package) Here's the block of middleware that makes this possible:

services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
                .AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));

var issuer = $"https://{Configuration["AzureAdB2C_Custom:TenantB2CHost"]}/{Configuration["AzureAdB2C:Domain"]}/v2.0/";
services.AddAuthorization(options =>
     {
          options.AddPolicy("api_read", policy => policy.Requirements.Add(new HasScopeRequirement("api_read", issuer)));
          options.AddPolicy("api_write", policy => policy.Requirements.Add(new HasScopeRequirement("api_write", issuer)));
     });

This all seems to be working to gaining access to endpoints properly. These endpoints then expose various information from my SQL database in a model.

However, now I'm trying to bring home the other aspect of my security, by controlling access to particular parts of the data retrieved in these endpoints, based on who the JWT token is actually tied to in my database.

In my particular case, I have an Accounts table, where I can store the ObjectID from Azure AD B2C that's found in the JWT token and then obtain the Account using that ObjectID at the time of calling. I could just do this at the top of an endpoint, but I suspect that this is not the best way to do it. It seems like I should be building this at the middleware layer in Startup as some kind of handler.

Can someone confirm for me that this would be the right approach and possibly give me an example of this or at least point me in the right direction?

Thanks guys for your responses. Here's what I landed on for anyone else dealing with this. Shout out to @juunas for his link he sent, which is what started me on the right path. That particular solution was not enough because I am using the Azure AD B2C libraries for the authorization/authentication.

Here is the link I found, once I tried implementing the solution from @juunas: https://blog.denious.net/azure-b2c-role-based-authorization-part-1/

Based on what I discovered in that link, here is what my solution ended up looking like:

//Azure ADB2C is injected for authentication
        services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
            .AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));

        services.PostConfigure<JwtBearerOptions>(
            AzureADB2CDefaults.JwtBearerAuthenticationScheme,
            o =>
            {
                o.Events = new JwtBearerEvents
                {
                    OnTokenValidated = ctx =>
                    {
                        //Get user's immutable object id from claims that came from Azure AD
                        string oid = ctx.Principal
                            .FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");

                        //Get My Data EF context
                        var db = ctx.HttpContext.RequestServices.GetRequiredService<MyDataContext>();

                        //Grab the user - found in My Accounts table, tied to OID
                        var user = db.Accounts.Include("AccountType")
                            .Where(a => a.AuthorityId == oid)
                            .FirstOrDefault();

                        if (user != null)
                        {
                            // since we're using AADB2C only, the first identity is the only identity
                            var identity = ctx.Principal.Identities.First();

                            //Place My user type into the claims as a role claim type
                            var extraRoleClaim = new Claim(identity.RoleClaimType, user.AccountType);
                            identity.AddClaim(extraRoleClaim);
                        }

                        return Task.CompletedTask;
                    }
                };
            });

Once that was setup, I was able to do this inside of my endpoint:

if (User.IsInRole("Some Role"))
            {
                return Ok();
            }
            else
            {
                return StatusCode(403, "Forbidden - Unacceptable Role");
            }

Note from the link, that I also would be able to do the following within my Authorize attribule on a controller or action: [Authorize(Roles = "Some Role")]

Basically you will need to add a custom attribute and include it in the JWT.

as described in here also you might need to keep the attributes in sync using graph API by using this Sample

I'm struggling a bit with the example as the config in application.json is missing, and google search is a bit confusing due to changes between the .net core versions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM