Search code examples
azureasp.net-coreazure-ad-msalmicrosoft-entra-id

Integrating Microsoft Entra ID into SPA and ASP.NET Core Web API - Understanding Scopes and User-Specific Claims


I'm integrating Microsoft Entra ID into Svelte SPA and ASP.NET Core Web API. The goal is to secure the API and implement user-specific permissions dynamically.

Current configuration:

  1. Authentication setup in Entra ID

    • Added Single-Page Application (SPA) platform with redirect URI: http://localhost:3000/auth-callback.
  2. Expose an API setup in Entra ID:

    • Defined Application ID URI: api://fxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
    • Created a scope AdminTool.Read
    • Added client application under "Authorized client applications" with the same client id since both FE and BE use the same Entra ID application
  3. ASP.NET Core Web API configuration:

    Startup (in Program.cs):

    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
    

    appsettings.json:

    "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "TenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "ClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "ClientCredentials": [
            {
                "SourceType": "ClientSecret",
                "ClientSecret": "xxxxxxxxxxxxxxxxxxx"
            }
        ],
        "Scopes": "api://fxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/AdminTool.Read"
    }
    
  4. Frontend (Svelte):

    When requesting a token, I pass the following scopes:

    const scopes = ['openid', 'profile', 'api://fxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/AdminTool.Read'];
    

    If I don't include the custom scope (AdminTool.Read), the API rejects the request.

Questions

  1. What exactly do these API scopes do?

    • Are they only for controlling access to the API at an application level, or can they be used for per-user permissions?
  2. How can I implement user-specific permissions dynamically?

    • I want to introduce scopes per user, such as Dashboard.Read, which I can add/remove through Graph API per user.

Is there a way to manage these scopes dynamically for individual users rather than defining them statically under "Expose an API"? Best Practices?


Solution

  • Note: API scopes in Entra ID control access at an application level, but user-specific permissions can be managed dynamically using app roles.

    • Scopes grant access to specific resources or operations on your backend API.
    • When your SPA requests a token, it specifies the desired scopes to Azure AD. In response, Azure AD issues a token that contains claims regarding the user's identity and their associated permissions.

    Scopes are primarily used to manage access to the API at the application level, but they can also be applied on a per-user basis by checking the claims in the JWT token that is sent to your backend API.

    • Although scopes like AdminTool.Read are defined at the application level, you can dynamically manage user-specific permissions by including app roles in the user's token.
    • For instance, a user with the Dashboard.Read role will be granted access to the dashboard feature in your app.

    To implement it, check the below:

    Created API app and exposed and API:

    enter image description here

    In SPAapp, granted API permissions like below:

    enter image description here

    As you are making use of two applications (API app and SPA app), you need to create app roles in both the applications and assign the user to it on both of them.

    Create app role Dashboard.Read in both API app and SPA app:

    enter image description here

    enter image description here

    Now assign users to the app role in both applications in Enterprise application under users and groups blade:

    enter image description here

    enter image description here

    For sample, I generated access token by using below parameters:

    https://login.microsoftonline.com/TenantID/oauth2/v2.0/token
    
    client_id: ClientID
    grant_type: authorization_code
    code: Code
    redirect_uri: RedirectURL
    code_verifier: xxx
    scope: api://xxx/AdminTool.Read
    

    enter image description here

    When decoded, the access token will be containing both scp and roles claim:

    enter image description here

    This depicts that the user with scope AdminTool.Read with role Dashboard.Read will be able to read the dashboard.

    • The user has the scope AdminTool.Read, which grants them permission to access the AdminTool in your API.
    • The user also has the role Dashboard.Read, which grants them permission to read the dashboard.

    I tried to generate access token and did not assign the role to the user:

    enter image description here

    • The access token does not contain role claim with value Dashboard.Read, hence the user will not be able to read the dashboard.

    Reference:

    azure - How to add roles claim in access_token , currently it is coming in id_token? - Stack Overflow by juunas