Azure B2C uses OATH 2 / OpenID Connect as the main way to secure single page apps and API's. OATH 2 uses Json Web Tokens (stateless cryptographically signed tokens) to provide authentication between multiple services (OpenID Connect is an extension of OATH 2).
The Client application (the SPA in this case) would ask Azure AD B2C for JWT tokens. If the user is logged in to B2C the security token service would then issue tokens for the SPA to use.
When the SPA calls the API, the ID token from the security token service (in the OpenID Connect flow) would be sent to the API in the Authorization
header (Authorization: Bearer $token$
). The API could then validate the token based on the signature block of the JWT to validate that the token was issued by the security token service and has not been modified. Because of the trust relationship between the STS and the API (the API trusts the STS), the API then authenticates and authorizes the various api calls based on the JWT that is presented.
Since you have the SPA working and getting tokens, all you need to do next is set up the Web API to accept the JWT from B2C. Using Microsoft.AspNetCore.Authentication.JwtBearer the following code should be close to getting you up and running
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(jwtOptions =>
{
jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
jwtOptions.Events = new JwtBearerEvents
{
OnAuthenticationFailed = AuthenticationFailed
};
});
(above code from azure samples, no longer maintained.
While Ideally both the SPA and API would be registered as separate clients with the STS, you could use the same client ID so you would not need to keep two sets of tokens around, one for the SPA client and one for the API. In the current project I am working on, multiple SPA's are registered as clients then the API has an AddJwtBearer
which accepts multiple audiences
.AddJwtBearer(options =>
{
options.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = true,
ValidAudiences = new List<string>
{
Configuration["AzureAdB2C:ClientId1"];,
Configuration["AzureAdB2C:ClientId2"];
}
};
}
This gives enough security to me for my app and the API does not need to be registered as a client with the IDP.