2
votes

I'm developing a multi-tenant SPA application that calls a back-end .Net Core Web API for data. The front end UI will use MSAL and Microsoft's v2 common endpoint for authenticating the user against AAD and obtaining an id and access token.

In my Web API, I want to validate the issuer, but as noted here, using the common endpoint provides metadata that makes the normal issuer validation un-useable.

I've seen references to a couple places where it may be possible to override or customize token validation, but I'm not sure which is preferred or if either of these methods causes undesired side effects.

One method uses the Events of the JwtBearer options: options.Events.TokenValidated and the other uses the IssuerValidator delegate of the TokenValidationParameters.

I don't want to have to write any token validation logic other than just ensuring the issuer exists in my database of verified issuers. Should that logic go in the IssuerValidator or TokenValidated?

My current code looks like this (currently set up for single tenant)

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
   .AddJwtBearer(options =>
    {
       options.Authority = "https://myauthority.com";
       options.Audience = "https://myaudience.com/api/v1";
       options.TokenValidationParameters = new TokenValidationParameters
       {  
          ValidateIssuer = true,
          ValidIssuer = "myauthority.com",
          ValidateAudience = true,
          ValidAudience = "https://myaudience.com",
          ValidateLifetime = true,
          ValidateIssuerSigningKey = true,
        };
    });

One of the issues I see with using IssuerValidator is that there does not appear to be a way to inject or pass in a reference to a dbContext needed to be able to look up a tenant id in a database.

Has anyone tackled this or done something similar?

2
Just had a read of this dzimchuk.net/… seems helpful and points towards IssuerValidator. I understand the problem about getting a dbcontext injected into there and will be creating an answer when I can get near a computermatt_lethargic
@matt_lethargic you mentioned posting an answer demonstrating how to get the dbcontext in IssuerValidator. Is that still coming? That feels the more natural place to perform my validation.RHarris

2 Answers

2
votes

You can check that in OnTokenValidated event , to access the database context , you can try :

 options.Events.OnTokenValidated = async (context) =>
   {


       var dbContext = context.HttpContext.RequestServices.GetRequiredService<BloggingContext>();
       var blogs = await dbContext.Blogs.ToListAsync();

       if (!true)
       {
           throw new SecurityTokenValidationException($"Tenant xxxxx is not registered");
       }

   };
1
votes

Wow, so this lead me down a long road! As you pointed out most of the documents point towards setting ValidateIssuer = false and leaving at that. I tried the IssuerValidator and I didn't get anywhere. What I did find was IAuthorizationHandler. I've created a PoC Using IMyService in place of DBContext. I left ValidateIssuer = false.

public class IssuerAuthorizationHandler : IAuthorizationHandler
{
    private readonly IMyService _service;

    public IssuerAuthorizationHandler(IMyService service)
    {
        _service = service ?? throw new ArgumentNullException(nameof(service));
    }

    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        if (context.User.FindFirst("iss") != null)
        {
            string issuer = context.User.FindFirst("iss").Issuer;
            // do issuer validation here
        }
        else
        {
            // fail the authentication
            context.Fail();
        }

        return Task.CompletedTask;
    }
}

Add this to DI

services.AddScoped<IAuthorizationHandler, IssuerAuthorizationHandler>();

Hope this help

Update:

Filter pipeline showing when authorisation handler is called

core pipeline