1
votes

Currently I'm using standard .NET authentication with JWT middleware to authenticate users. Azure AD is the token issuer. So my Startup.cs looks something like this:

            var authentication = new AuthenticationOptions();
            configuration.Bind("AzureAd", authentication);

            services
                .AddAuthentication(options =>
                {
                    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(options =>
                {
                    options.Authority = $"{authentication.Instance}{authentication.TenantId}";
                    options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                    {
                        ValidAudience = $"{authentication.ClientId}",
                        ValidIssuer = $"{authentication.Instance}{authentication.TenantId}/v2.0"
                    };
                });

And I use the [Authorize] tag to protect endpoints. What I now want to do is create a custom JWT for a user that's already logged in. To do this I currently have the following code:

            var username = User.Claims.FirstOrDefault(x => x.Type == "name").Value;
            var email = User.Claims.FirstOrDefault(x => x.Type == "preferred_username").Value;

            var authentication = new AuthenticationOptions
            {
                My client and tenant credentials
            };

            var mySecret = "someSecret";
            var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(mySecret));

            var tokenHandler = new JwtSecurityTokenHandler();
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim("name", username),
                    new Claim("preferred_username", email)
                }),
                Issuer = $"{authentication.Instance}{authentication.TenantId}/v2.0",
                Audience = $"{authentication.ClientId}",
                SigningCredentials = new SigningCredentials(mySecurityKey, SecurityAlgorithms.HmacSha256Signature),
            };

            var tokenPlainText = tokenHandler.CreateToken(tokenDescriptor);
            var token = tokenHandler.WriteToken(tokenPlainText);

            return token;

What I would like to do is use the [Authorize] tag to validate both the tokens issued by Azure AD when a user logs in, but also the custom JWT created by my code. The problem is (I assume) that Azure signs the tokens with a private key that I don't have access to.

Is it possible to include several validation options in .AddJwtBearer for instance, so that it checks both possible ways a user can be valid and gives access if either one of them passes? Or is there a way to create a custom token that will pass the check already in place? Or maybe a way to add another tag to an endpoint so it becomes something like [Authorize] || [CustomAuthorize] and if one of them passes the user gains access?

1

1 Answers

1
votes

Not using a custom JWT token

I don't really see the nead for you needing to create your own tokens. AAD does a pretty good job of managing users and their login status. As a possible alternative, you could also evaluate cookie authentication for these signed in users. Creating your own JWT token sounds overengineered and possibly less secure.

Validating tokens from different sources

You can chain .AddJwtBearer calls to set up multiple token sources. You need to give the second one a different scheme name. When doing this, you need to do some special handling after UseAuthentication to check that scheme as well. This is probably the cleanest solution which is also closest to what you describe.

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
    //...
}).AddJwtBearer(_FallbackScheme, jwtOptions =>
{
    //...
});


// ...

app.UseAuthentication();
// https://github.com/aspnet/Security/issues/1847
app.Use(async (context, next) => {
    if (!context.User.Identity.IsAuthenticated)
    {
        var result = await context.AuthenticateAsync(_FallbackScheme);
        if (result.Succeeded)
        {
            context.User = result.Principal;
        }
    }
    await next();
});

Extending the claims

If you just want to add a couple of extra claims at runtime, it might also be ok to do this at runtime. However, this will happen on every API call, so you need to keep this in mind.

https://joonasw.net/view/adding-custom-claims-aspnet-core-2