microsoft-identity-web icon indicating copy to clipboard operation
microsoft-identity-web copied to clipboard

[Bug] ValidIssuers is not applied

Open jc4gh opened this issue 3 years ago • 13 comments

Which version of Microsoft Identity Web are you using? Microsoft Identity Web 1.15.1

Where is the issue?

  • Web app
    • [ ] Sign-in users
    • [ ] Sign-in users and call web APIs
  • Web API
    • [x] Protected web APIs (validating tokens)
    • [ ] Protected web APIs (validating scopes)
    • [ ] Protected web APIs call downstream web APIs
  • Token cache serialization
    • [ ] In-memory caches
    • [ ] Session caches
    • [ ] Distributed caches
  • Other (please describe)

Is this a new or an existing app? This is a new app or an experiment.

Repro Get a token from Tenant1 and call API.

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddMicrosoftIdentityWebApi(this.Configuration.GetSection("AzureAd"), subscribeToJwtBearerMiddlewareDiagnosticsEvents: true);

services.Configure(JwtBearerDefaults.AuthenticationScheme, (JwtBearerOptions options) =>
{
    var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;
    options.Events.OnTokenValidated = async context =>
    {
        await existingOnTokenValidatedHandler(context);

        // Your code to add extra configuration that will be executed after the current event implementation.
        options.TokenValidationParameters.ValidIssuers = new[] { "https://sts.windows.net/[Tenant2 GUID]/" };
        options.TokenValidationParameters.ValidAudiences = new[] { "[audience]" };
    };
});

Expected behavior The request gets 401.

Actual behavior "Successfully validated the token." and passes validation.

Additional context / logs / screenshots Above code is from https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration#customizing-token-validation.

jc4gh avatar Jul 29 '21 21:07 jc4gh

@jc-msft try moving the condition for validating the token to before it's validated. that should resolve the issue.

services.Configure(JwtBearerDefaults.AuthenticationScheme, (JwtBearerOptions options) =>
{
     // Your code to add extra configuration that will be executed after the current event implementation.
     options.TokenValidationParameters.ValidIssuers = new[] { "https://sts.windows.net/[Tenant2 GUID]/" };
     options.TokenValidationParameters.ValidAudiences = new[] { "[audience]" };
     
     var existingOnTokenValidatedHandler = options.Events.OnTokenValidated;

     options.Events.OnTokenValidated = async context =>
     {
          await existingOnTokenValidatedHandler(context);
     };
});

jennyf19 avatar Aug 02 '21 15:08 jennyf19

i believe this is resolved. closing, but feel free to reopen

jennyf19 avatar Aug 09 '21 19:08 jennyf19

It still doesn't seem to validate. Token: image

Code: image

image

jc4gh avatar Aug 09 '21 21:08 jc4gh

@jennyf19 I can't seem to re-open.

jc4gh avatar Aug 09 '21 21:08 jc4gh

Hey @jennyf19 I'm having a similar issue - it seems like, even when setting ValidIssuers, the https://login.microsoftonline.com/{tenant}/v2.0 issuer is still being added to the ValidIssuers list after configuration, presumably from the openid connect metadata.

For example - when the merged options are created, and when they are assigned to openid options, only my two issuers (from the ValidIssuers config) are in the list.

image

But at runtime, when the aad issuer validator runs, the list of valid issuers also includes the metadata value:

image

Presumably, if the user provided a list of valid issuers, they would like to ignore the metadata issuer and checking the issuer against the ValidIssuers list only and skipping (or even removing) the metadata-fetched templated issuer would have a desired result, but I'm not entirely certain what side-effects that would have.

jpda avatar Aug 26 '21 00:08 jpda

@jpda can you send me a repro? thx.

jennyf19 avatar Aug 26 '21 01:08 jennyf19

here you go @jennyf19 https://github.com/jpda/restricted-issuer

jpda avatar Aug 26 '21 01:08 jpda

@jpda thanks so much. here you need to configure the OpenIdConnectOptions not the MicrosoftIdentityOptions and just set either ValidIssuer or ValidIssuers but not both.

jennyf19 avatar Aug 26 '21 01:08 jennyf19

Ah ok - I had ValidIssuer in there to check for something else (that is a bad issuer value in the ValidIssuer anyway).

OK - so I have OpenIdConnectOptions as the only bit of configuration here

image

However the valid issuer list seems to still include the one from metadata.

image

Even if the issuer isn't in the valid list, this block would fetch metadata and get that templated issuer value from metadata, which would bypass the ValidIssuers list

I have a rather inelegant experiment here: https://github.com/jpda/microsoft-identity-web/blob/1809d6a3cd1ace35025c7dcde1c73264b7405dc8/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs#L78

and maybe a little better of the same idea here: https://github.com/jpda/microsoft-identity-web/blob/7140e3ce461c31ee5b61d22db9982c574edad6e3/src/Microsoft.Identity.Web/Resource/AadIssuerValidator.cs#L81

jpda avatar Aug 26 '21 03:08 jpda

I was very glad to stumble across this issue, because I've been trying for hours to understand why my code has stopped working after migrating to Microsoft.Identity.Web! Can you please recommend a workaround until this is fixed (hopefully soon - seems like a pretty major security hole)? Something like the following maybe?

services
        .AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApp(options =>
        {
          options.Events = new OpenIdConnectEvents
          {
            OnTokenValidated = async context =>
            {
              await existingOnTokenValidatedHandler(context);

              if (validIssuers.Length > 0 && !validIssuers.Any(iss => iss == context.SecurityToken.Issuer))
              {
                context.HandleResponse();
                context.Response.Redirect($"{baseUrl}/Account/AccessDenied");
              }
            }
          };
        });
`

thecodetinker avatar Nov 23 '21 14:11 thecodetinker

@jpda @thecodetinker I investigated, and it turns out that ASP.NET Core always adds an issuer to the TokenValidationParameters.Issuers. The Issuer which is fetched from the OpenIdConnectConfiguration (From the issuer property of the https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration metadata document)

Then the other ValidIssuers are appended to this one.

This is happening here:

https://github.com/dotnet/aspnetcore/blob/a450cb69b5e4549f5515cdb057a68771f56cefd7/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs#L101

In order to avoid this behavior, I recommend that you also override the IssuerValidator delegate to match the issuer with only your valid issuers.

I'm going to start a conversation with the ASP.NET Core team to understand if there is a better option. cc: @tratcher

jmprieur avatar Dec 01 '21 06:12 jmprieur

Hi again and thanks for the update. I'm a bit unsure about overriding the IssuerValidator delegate - from what I can see the default does a lot of checking that would need replicating. Did you have any luck with the ASP.NET Core team, or the possible solutions proposed by @jpda ?

thecodetinker avatar Dec 08 '21 12:12 thecodetinker

@tratcher do we want to do something about the fact that ASP.NET Core systematically adds the ClientId as a valid issuer? (note that this would be a breaking change)

jmprieur avatar Aug 21 '22 19:08 jmprieur