Azure Functions + Entra tenant for Customer : EasyAuth uses wrong tenant URL to retrieve JWT keys

232 views Asked by At

I have been trying to build a very simple "proof of concept" with Microsoft's Entra ID "tenant for customers" (yes, I know it's fairly new but it's based on B2C). I am following this article. I also have a React SPA, for which I set up Sign up/Sign In following this article. I'm using my own SPA, but the relevant part around auth is essentially the same. Sign up, sign in and log out work, as well as a call to the MS Graph to get the user's profile.

When I try to call my own API (HTTP Trigger Azure Function, that 100% vanilla "Welcome to Azure Functions" edit-in-portal starting function created from the portal), I get a correct response if I use no auth (401 or 302, depending how the Auth response is configured in the portal).

I can also correctly acquire an access token that should work to call my API (using .acquireSilently in the SPA). The token seems fine when viewed with jwt.ms.

The problems start when I try to use auth with bearer tokens. In fact, as soon as I put anything as bearer token, even random strings, I get a 500 error response from the Function. I narrowed it down to the built-in EasyAuth middleware (so, before my function is even hit) from the App Service.

EDIT: The comment from David made me look more carefully at the full flow, and the error was in my face but I still don't know the cause nor the fix: That EasyAuth middleware is using an incorrect URL to get JWT keys, and therefore can't possibly validate the tokens. It should get 'jwks_uri' by accessing ../.well-know/openid-configuration at one of these URLs (with or without /v2.0/):

https://<MYTENANT>.ciamlogin.com/<MYTENANTID>/[v2.0/].well-known/openid-configuration
https://login.microsoftonline.com/<MYTENANTID>[/v2.0/].well-known/openid-configuration

From this metadata, the keys should be at these URLs (equivalent):

    https://login.microsoftonline.com/<MYTENANTID>/discovery/[v2.0/]keys
https://<MYTENANT>.ciamlogin.com/<MYTENANTID>/discovery/[v2.0/]keys

And indeed from all these URLs I get the same (correct) JWT keys. However the error in the logs clearly indicates it's trying to access the /common/ URL to get keys, which returns this error:

Call to HTTP endpoint https://MYTENANT.ciamlogin.com/common/discovery/keys failed: 400 (Bad Request). Partial response: {"error":"server_error","error_description":"AADSTS500209: Unspecific Tenant is not supported in this domain.

I also see these errors in the logs:

App Service Authentication specific error detected (500.79) - The request failed because of an unhandled exception in the Easy Auth module.

JWT validation failed: IDX12741: JWT: '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' must have three segments (JWS) or five segments (JWE)..

I have tried:

  • Deploying the AppService+AzureFunction with various versions of runtime etc. (.Net 6, 7, 8, isolated or not, NodeJs)
  • Tear down everything in Azure and rebuild, probably 3 times now.
  • Different plans for the app service (Consumption or Premium).
  • Calling directly with Postman (using token acquired with the SPA)

What's really pushing me to nervous breakdown is that on two occasions since I've been trying to make it work, for a short time and during low-usage times (around midnight..), the whole thing started to work perfectly. The 2nd time this happened I ensure I changed nothing, went to bed, and of course the next morning it was not working anymore. Possibly related to load or key rotation.. ?

Now I'm hoping Santa is real and someone here will have a new idea for me to try.

1

There are 1 answers

1
James On

I've had this problem trying to protect an API with the new Entra External ID tenant. Same issue as above the JWKS URL was broken in my meta data config url.

Your comment about using generic provider helped. Here's the code that's working for me if anyone interested. I'll likely swap back once resolved by MS:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = $"{azAdConfig.Instance}/{azAdConfig.TenantId}/v2.0";

        options.Audience = $"api://{azAdConfig.ClientId}";

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateLifetime = true,
            ValidateIssuer = true,
            ValidIssuer = $"https://{azAdConfig.TenantId}.ciamlogin.com/{azAdConfig.TenantId}/"
        };
    });

My instance is: subdomainhere.ciamlogin.com