4
votes

I have an ASP.NET MVC Core 2.2 application, that integrates with an Azure AD B2C to authenticate users. I can sign in correctly, and the user is authenticated.

I also have created an ASP.NET Core Web API which is also integrated with the Azure B2C AD, and the goal is to call that web api from an ASP.NET MVC controller action method.

So I added the following test code in the controller of the MVC site:

if (HttpContext.User.Identity.IsAuthenticated)
{
    string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
    TokenCache userTokenCache = new MSALSessionCache(signedInUserID, HttpContext).GetMsalCacheInstance();
    ConfidentialClientApplication cca = new ConfidentialClientApplication(mgpPortalApplicationId, authority, redirectUri, new ClientCredential(mgpPortalSecretKey), userTokenCache, null);                
    IEnumerable<IAccount> accounts = await cca.GetAccountsAsync();
    IAccount firstAccount = accounts.FirstOrDefault();
    AuthenticationResult result = await cca.AcquireTokenSilentAsync(null, firstAccount, authority, false);
    HttpClient client = new HttpClient();
    HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:44307/api/values");
    request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
    HttpResponseMessage response = await client.SendAsync(request);
}

The problem is that accounts.FirstOrDefault() gives back null. Not sure why:

Does anyone have an idea on what I'm doing wrong? Thanks for any hints!

Additional observation: if I run the demo https://github.com/Azure-Samples/active-directory-b2c-dotnetcore-webapp, which uses an older Microsoft.Identity.Client, then the call to cca.Users.FirstOrDefault() gives back a user correctly. However, when I upgrade this demo project to Microsoft.Identity.Client 2.7 (which is needed for .NET Core 2.2), then I have to pass an IAccount and so I need to call GetAccountsAsync(), and this returns no account.

3

3 Answers

1
votes

Firstly , you need to asking for the scopes you need for accessing your api when invoke AcquireTokenSilentAsync

AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, cca.Users.FirstOrDefault(), AzureAdB2COptions.Authority, false);

And you need to implement the initial token acquisition and save tokens in MSAL token cache using AuthorizationCodeReceived notification of the authorization middleware :

public async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
    // Use MSAL to swap the code for an access token
    // Extract the code from the response notification
    var code = context.ProtocolMessage.Code;

    string signedInUserID = context.Principal.FindFirst(ClaimTypes.NameIdentifier).Value;
    TokenCache userTokenCache = new MSALSessionCache(signedInUserID, context.HttpContext).GetMsalCacheInstance();
    ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);
    try
    {
        AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, AzureAdB2COptions.ApiScopes.Split(' '));


        context.HandleCodeRedemption(result.AccessToken, result.IdToken);
    }
    catch (Exception ex)
    {
        //TODO: Handle
        throw;
    }
}

So that when acquiring token in controller , MSAL will look up the cache and return any cached token which match with the requirement. If such access tokens are expired or no suitable access tokens are present, but there is an associated refresh token, MSAL will automatically use that to get a new access token and return it transparently:

// Retrieve the token with the specified scopes
var scope = AzureAdB2COptions.ApiScopes.Split(' ');
string signedInUserID = HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
TokenCache userTokenCache = new MSALSessionCache(signedInUserID, this.HttpContext).GetMsalCacheInstance();
ConfidentialClientApplication cca = new ConfidentialClientApplication(AzureAdB2COptions.ClientId, AzureAdB2COptions.Authority, AzureAdB2COptions.RedirectUri, new ClientCredential(AzureAdB2COptions.ClientSecret), userTokenCache, null);

AuthenticationResult result = await cca.AcquireTokenSilentAsync(scope, cca.Users.FirstOrDefault(), AzureAdB2COptions.Authority, false);

Here is the code sample using ASP.NET Core 2.0 And MSAL 2 , please read the README.md for detailed explanation .

You can click also here for code sample that authenticates user against Azure AD B2C and acquires an access token using MSAL.NET using the ASP.NET Core 2.1 .

1
votes

The .NETCore B2C web app has been updated to use MSAL v2.7. The scope being used was incorrect, so the sample now uses the correct scope and an access token is returned.

"ApiScopes": "https://fabrikamb2c.onmicrosoft.com/helloapi/demo.read"

0
votes

@L-Four , there is actually a branch of the code you mention that is already updated to latest version of MSAL: active-directory-b2c-dotnetcore-webapp

Saying that, when running it I get an unauthorised error. So not really sure what's up with that. But I do get an account back.