Search code examples
.netasp.net-coreblazor-webassemblymicrosoft-entra-id

Is passing an id_token to my own API ok in this case?


I am following this guidance on how to enable Entra Id auth for my Blazor WASM SPA. This should securely log the users in - using Entra External ID - collect some custom attributes on sign up and call my own downstream Web API (NET) project.

I have configured the two app registrations and can successfully login using a Google account. So the auth works fine.

There are app_roles assigned to the user, and these show correctly in the id_token. I have also configured the id_token to include custom properties I have collected during sign-up.

Problems start when I want to call my API. Looking at the access_token:

  • Whatever I do, its aud claim value is always 00000003-0000-0000-c000-000000000000 (MS Graph API and not the scope I have requested, which is api://<CLIENT_ID_WEB_API>/access_as_user).

  • The app_roles shown in the id_token do not carry over to the access_token. I want to use this for AuthZ decisions.

  • Custom attributes also don't carry over, there is one attribute (a guid, which is assigned by me), I need to use in the backend to use with filtering the data that is returned to the user.

General guidance is to for an app to not even look at the access token and just pass it along to the downstream API. In my case, this token does not contain al lhe required information I need.

Therefore I'd started to wonder:

  • The id_token is signed
  • It contains a unique identifier
  • It contains the app_roles
  • It contains the custom attributes
  • It contains the Blazor SPA's client_id in the aud claim, but I could just employ a policy on the backend to enforce exactly this value

Both apps (SPA + API), I control. When there is no other information I need why do I not just use the id_token instead?

Do you find this acceptable?


Solution

  • Let me start with your question: Can I use ID Token to call API?

    The short answer is: No, you should never use ID Tokens to call APIs. Access Tokens should be used for that instead. Why?

    1. ID Token is to be only used by the application where user authenticated to confirm user identity.
    2. APIs never use ID Token for authorization operations. Access Tokens are used for that instead due to OIDC and OAuth specifications.
    3. ID Token aud (audience) claim matches your client's application ID in the Microsoft Entra External ID tenant. During the token validation, application is looking into the audience claim to check whether the token was issued for the right application itself. Id audience claim does not match, application should not accept the ID Token. Now if you send this ID Token to your API, accordingly to best practices, your API should validate audience claim too. However, it will match your client application ID, not the client ID of the API application registered in the Microsoft Entra External ID.
    4. When accessing API, token should contain scp (scope) claim so the API can check the scope value and decide whether to accept the request or return unauthorized response. The scp claim is presented only in the Access Token. ID Token does not contain it.

    Now moving forward, let's fix your problem. I assume that you have two app registrations in your Microsoft Entra External ID tenant:

    • One for the client application - Blazor
    • One for the Web API (ASP .NET Core API)

    If you're implementing app role business logic in an app-calling-API scenario, you have two app registrations. One app registration is for the app, and a second app registration is for the API. In this case, define the app roles and assign them to the user or group in the app registration of the API. When the user authenticates with the app and requests an access token to call the API, a roles claim is included in the token. Your next step is to add code to your web API to check for those roles when the API is called. Here is example of two roles I defined for tests:

    enter image description here

    I also assume that you granted permissions for your Blazor app to call API like this: enter image description here

    Next, you have to assign App Roles to users. To do it, you have to open Enterprise Applications section and from there select your API app:

    enter image description here

    Next, select Users and groups tab, then click + button to add assign user to the application and assign specific role:

    enter image description here

    enter image description here

    Once you assign role or roles, then you have to update your Blazor application's code. I assume you use MSAL and default configuration there. To get Access Token to call your API, you have to define the right scope (using DefaultAccessTokenScopes):

    builder.Services.AddMsalAuthentication(options =>
    {
        builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
        options.ProviderOptions.LoginMode = "Redirect";
        options.ProviderOptions.Cache.CacheLocation = "localStorage";
        options.ProviderOptions.DefaultAccessTokenScopes.Add("api://8e8b8422-86e8-4b1f-b0fd-bbc1c8606998/access_as_user");
    });
    

    Once user is successfully authenticated, you will also get Access Token with the roles assigned to your user:

    enter image description here

    Now you can add this Access Token to the authorization header of the HTTP request and call your API securely following the best practices. I hope this helped.