Kbalz
Kbalz

Reputation: 101

How to obtain the token returned from Azure AD B2C in ASP Core 2.0?

I have used Visual Studio's latest New Project wizard to create a ASP Core 2.0 Web page (Razor Pages) that uses Individual Accounts as my authentication option. I have created an Azure AD B2C tenant and validated that it works properly.

When I run the web application that was created by the wizard and click Log In in the upper right, it redirects to my Azure AD B2C site, and I can properly login.

After login, the callback url goes to the endpoint configured in my user secrets:

 ...
 "CallbackPath": "/signin-oidc",
 ...

That all seems to work properly. I understand that the Azure AD B2C portal sends a token back to the above /signin-oidc callback path and stores it.

How can I retrieve the value of that token?

I've been following all of the Azure AD B2C guides, but not all of them have been updated to ASP Core 2.0, and none of them seem to use the code generated from the 15.4 VS wizard such:

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(sharedOptions =>
    {
        sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddAzureAdB2C(options => Configuration.Bind("AzureAdB2C", options))
    .AddCookie();

    services.AddMvc();
}

Note: the .AddAzureAdB2C(...)

None of the B2C samples are using this so its difficult for me to follow.

My end goal is to get the token and use that in a strongly-typed set of API classes I generated from Swagger using Autorest which require the token.

Upvotes: 0

Views: 2885

Answers (2)

spottedmahn
spottedmahn

Reputation: 15981

The ASP.NET Core team has created 2 excellent documents on this process.

Upvotes: 2

Saca
Saca

Reputation: 10646

The best way to do this is outlined in the Azure AD B2C .Net Core sample, specifically the branch for Core 2.0.

In the normal model/flow, your application will get an id_token and an authorization code but not a token. The authorization code needs to be exchanged for a token by your middle tier. This token is what you'd then be sending over to your web API.

The way to do this involves the following:

  1. Ensure your middle tier is requesting id_token+code for your primary policy (you don't want to do this for your edit profile or password reset policies). From the sample's OpenIdConnectOptionsSetup.cs#L77:
public Task OnRedirectToIdentityProvider(RedirectContext context)
{
    var defaultPolicy = AzureAdB2COptions.DefaultPolicy;
    if (context.Properties.Items.TryGetValue(AzureAdB2COptions.PolicyAuthenticationProperty, out var policy) &&
        !policy.Equals(defaultPolicy))
    {
        context.ProtocolMessage.Scope = OpenIdConnectScope.OpenIdProfile;
        context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.IdToken;
        context.ProtocolMessage.IssuerAddress = context.ProtocolMessage.IssuerAddress.ToLower().Replace(defaultPolicy.ToLower(), policy.ToLower());
        context.Properties.Items.Remove(AzureAdB2COptions.PolicyAuthenticationProperty);
    }
    else if (!string.IsNullOrEmpty(AzureAdB2COptions.ApiUrl))
    {
        context.ProtocolMessage.Scope += $" offline_access {AzureAdB2COptions.ApiScopes}";
        // -----------------------------
        // THIS IS THE IMPORTANT PART:
        context.ProtocolMessage.ResponseType = OpenIdConnectResponseType.CodeIdToken;
        // -----------------------------
    }
    return Task.FromResult(0);
}
  1. Exchange the code for a token. From the sample's OpenIdConnectOptionsSetup.cs#L103-L124:
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;
    }
}
  1. You can then use this token elsewhere in your code to call an API. From the sample's HomeController.cs#L45-L57:
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);


HttpClient client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, AzureAdB2COptions.ApiUrl);


// Add token to the Authorization header and make the request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
HttpResponseMessage response = await client.SendAsync(request);

Upvotes: 2

Related Questions