I am trying to protect my web api using Azure B2C AD and consume the web api using an Angular 4 SPA. However, for some reason the scope claim is always null even though other claims are working just fine.
I am using the MSAL library version 0.1.6 in the Angular app and have been following this guide: https://github.com/Azure-Samples/active-directory-b2c-javascript-angular2.4-spa
This is my web api startup.auth:
public partial class Startup
{
// These values are pulled from web.config
public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
public static string SignUpSignInPolicy = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
public static string DefaultPolicy = SignUpSignInPolicy;
/*
* Configure the authorization OWIN middleware.
*/
public void ConfigureAuth(IAppBuilder app)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// Accept only those tokens where the audience of the token is equal to the client ID of this app
ValidAudience = ClientId,
AuthenticationType = Startup.DefaultPolicy
};
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(AadInstance, Tenant, DefaultPolicy))),
});
}
}
This is my controller:
[Authorize]
[EnableCors(origins: "*", headers: "*", methods: "*")] // tune to your needs
public class ValuesController : ApiController
{
// GET api/values
public IEnumerable<string> Get()
{
string owner = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
var scopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope");
return new string[] {"value1", "value2"};
}
}
The owner variable contains a GUID as expected, however the scopes variable is always NULL.
This is my auth.service.ts:
import { Injectable } from '@angular/core';
import environment from '../../../environments/environment';
import * as Msal from 'msal'
declare var bootbox: any;
// declare var Msal:any;
const B2CTodoAccessTokenKey = "b2c.api.access.token";
const tenantConfig = {
tenant: environment.b2cTenant,
clientID: environment.b2cClientID,
signUpSignInPolicy: environment.b2cSignUpSignInPolicy,
b2cScopes: environment.b2cScopes
};
@Injectable()
export class AuthService {
// Configure the authority for Azure AD B2C
private authority = "https://login.microsoftonline.com/tfp/" + tenantConfig.tenant + "/" + tenantConfig.signUpSignInPolicy;
private loggerCallback(logLevel, message, piiLoggingEnabled) {
console.log(message);
}
private logger = new Msal.Logger(this.loggerCallback, { level: Msal.LogLevel.Verbose });
clientApplication = new Msal.UserAgentApplication(
tenantConfig.clientID,
this.authority,
function(errorDesc: any, token: any, error: any, tokenType: any) {
console.log('calling acquireTokenSilent with scopes: ' + tenantConfig.b2cScopes);
console.log('idtoken: ' + token)
if (token) {
this.acquireTokenSilent(tenantConfig.b2cScopes).then(function (accessToken) {
// Change button to Sign Out
console.log('acquireTokenSilent');
sessionStorage.setItem("b2c.api.access.token", accessToken);
}, function (error) {
console.log(error);
this.acquireTokenPopup(tenantConfig.b2cScopes).then(function (accessToken) {
console.log('acquireTokenPopup');
sessionStorage.setItem("b2c.api.access.token", accessToken);
}, function (error) {
console.log(error);
});
});
}
else if (errorDesc || error) {
console.log(error + ':' + errorDesc);
}
},
{
logger: this.logger,
});
loginRedirect(): void {
console.log('scopes: ' + tenantConfig.b2cScopes);
this.clientApplication.loginRedirect(tenantConfig.b2cScopes);
}
login() : void {
var _this = this;
this.clientApplication.loginPopup(tenantConfig.b2cScopes).then(function (idToken: any) {
_this.clientApplication.acquireTokenSilent(tenantConfig.b2cScopes).then(
function (accessToken: any) {
_this.saveAccessTokenToCache(accessToken);
}, function (error: any) {
_this.clientApplication.acquireTokenPopup(tenantConfig.b2cScopes).then(
function (accessToken: any) {
_this.saveAccessTokenToCache(accessToken);
}, function (error: any) {
//bootbox.alert("Error acquiring the popup:\n" + error);
console.log("Error acquiring the popup:\n" + error)
});
})
}, function (error: any) {
//bootbox.alert("Error during login:\n" + error);
console.log("Error during login:\n" + error);
});
}
getTokenFromCache() : string {
return sessionStorage.getItem(B2CTodoAccessTokenKey);
}
saveAccessTokenToCache(accessToken: string): void {
sessionStorage.setItem(B2CTodoAccessTokenKey, accessToken);
}
logout(): void{
this.clientApplication.logout();
}
isLoggedIn(): boolean {
var user = this.clientApplication.getUser();
console.log('isLogged In: ' + (user != null));
console.log('token in cache ' + (this.getTokenFromCache() != null))
//console.log('token: ' + this.getTokenFromCache());
return this.clientApplication.getUser() != null && this.getTokenFromCache() != null;
}
}
Finally, this is my environment values:
export default {
b2cTenant: "[tenant].onmicrosoft.com",
b2cClientID: '[app-id]',
b2cSignUpSignInPolicy: "[policy]",
b2cScopes: ["https://[tenant].onmicrosoft.com/apidemo/read", "https://[tenant].onmicrosoft.com/apidemo/user_impersonation"]
};
Here is pictures of the Azure setup:
Why is the value of the scopes variable NULL? What did I miss? The owner variable contains a value!
Best regards