Skip to content

AdditionalScopesToConsent seemingly being ignored #61610

@JSparshottO2

Description

@JSparshottO2

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I believe this is actually a bug within the MSAL library. I have raised an issue on that related repo but it's pretty silent there. After picking this issue back up today I've been looking through the codebase and AddMsalAuthentication, as a call, actually lives in this repo so On the off-chance the issue is here (maybe it's not passing through the config to the MSAL library), I'm going to raise it here as well. At the very least I hope to drum up some attention on the MSAL issue.

The issue on the MSAL board accurately reproduces it, copied from that issue for ease:

I am in a Blazor Wasm application. The End-State that I'm trying to get to is on application login, scopes are provided such that 1st and 3rd party resources are accessible using the single login without any future redirecting. In this example I am trying to access a 1st party API and a 3rd party Azure resource (a storage account).
I am using a custom TokenCredential implementation that uses the IAccessTokenProvider to request a token with the Azure service scope, and creates an Azure.Core.AccessToken that can be used with the Azure.Storage.Blobs library.

I've tried various combinations of DefaultAccessTokenScopes and AdditionalScopesToConsent. AdditionalScopesToConsent doesn't seem to do anything in terms of "prepping" the scopes for future use. I'm not even sure it's purpose really, it seems to do nothing.

Depending on which scope is set as the DefaultAccessTokenScopes will dictate which service works.

I'm happy to provide any additional context as needed. I've spent the last 2 days both trying to work out the combinations based upon my knowledge, and using the internet to try and work out how I can use the SSO into the blazor app to work with multiple external resources.

N.B. All external resources use the same Microsoft Entra source. Users are configured in the IAM of the related resources.

Relevant code snippets

Program.cs

//unrelated code...
builder.Services.AddScoped<TokenCredential, WasmTokenCredential>();
builder.Services.AddMsalAuthentication(options =>
{
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("api://<API_resource_id>/access_as_user");
    options.ProviderOptions.AdditionalScopesToConsent.Add("https://storage.azure.com/.default");
    options.UserOptions.RoleClaim = "roles";
});
//more unrelated code...

WasmTokenCredential.cs

using Azure.Core;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

using AzureAccessToken = Azure.Core.AccessToken;
using WasmAccessToken = Microsoft.AspNetCore.Components.WebAssembly.Authentication.AccessToken;

namespace TokenExample;

public class WasmTokenCredential : TokenCredential
{
    private readonly IAccessTokenProvider _accessTokenProvider;

    public WasmTokenCredential(IAccessTokenProvider accessTokenProvider)
    {
        _accessTokenProvider = accessTokenProvider;
    }

    public override AzureAccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
        => throw new NotSupportedException("Cannot use synchronous token acquisition in a WebAssembly environment.");

    public override async ValueTask<AzureAccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
        => FromWasmResult(await _accessTokenProvider.RequestAccessToken(new()
        {
            Scopes = [.. requestContext.Scopes]
        }));

    private AzureAccessToken FromWasmResult(AccessTokenResult tokenResult)
    {
        if (tokenResult.TryGetToken(out WasmAccessToken? accessToken) == false)
        {
            throw new InvalidOperationException("Failed to obtain an access token.");
        }
        return new AzureAccessToken(accessToken.Value, accessToken.Expires);
    }
}

BlobService.cs

using Azure.Core;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Options;

namespace TokenExample;

public class BlobService
{
    private readonly BlobServiceClient _blobServiceClient;

    public BlobService(IOptions<BlobOptions> configOption, TokenCredential credentials)
    {
        _blobServiceClient = new BlobServiceClient(new Uri(configOption.Value.Location), credentials);
    }

    public async Task<bool> HasContainer(string container)
        => (await _blobServiceClient.GetBlobContainerClient(container).ExistsAsync()).Value;
}

appsettings.json

{
  "AzureAd": {
    "ClientId": "<CLIENT_ID>",
    "Authority": "https://login.microsoftonline.com/<TENANT_ID>",
    "ValidateAuthority": true
  },
  "Blob": {
    "Location": "https://<BLOB_NAME>.blob.core.windows.net"
  }
}

Expected Behavior

On initial login in blazor WebAssembly, no further login prompts are required for accessing other authorized Azure Resources. (in this example, A blob storage)

.NET Version

9.0.203

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions