I'm currently working on Azure AD authenticaton integration to Angular - .Net core 3.1 project. This is a project which was generated from Visual Studio 2019 template (ASP.NET Core Web App).
In Azure portal, I registered 2 application and configured by MS tutorial and this.
Two registed app:
But I published only one App service, which contains both SPA and API. After login, I got a token, which append to every api call with MSAL interceptor.
The problem is all of the calls return is: 401, due to 'audience is invalid'. in the auth token the audience value the client id of frontend_app.
How can I solve to accept the the audience? Is it correct to use 2 app registration for only one app service?
I was having the same problem as you and believe I have come up with a solution. All the guides I originally followed were using the implicit flow. As Carl pointed out in his answer (which I don't believe properly addresses your issue), there's an auth flow which is the recommended way to go. Unfortunately the standard MSAL libraries from all the samples and guides are 1.x and don't support auth flow. Instead, you'll need to use MSAL.js 2.0. The catch is that the angular library is still in alpha
So, here's what I did to make it all work. I'm using an Angular 10 front-end with an ASP.NET Core 3.1 backend.
First, you create your backend api app registration (which you may not need to change). Here's the documentation for that: Register Web API. Important notes:
Then follow the MSAL.js 2.0 documentation to create the frontend app registration. The important notes are as follows:
Here's what your app registrations should look similar to:
backend app registration expose an api
frontend app registration authentication
frontend app registration api permissions
Now for the code. For your angular app, first install the necessary modules:
npm install @azure/msal-browser @azure/msal-angular@alpha
Then add this to your app module:
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { tap } from 'rxjs/operators';
import {
IPublicClientApplication,
PublicClientApplication,
InteractionType,
BrowserCacheLocation,
LogLevel,
} from '@azure/msal-browser';
import {
MsalGuard,
MsalInterceptor,
MsalBroadcastService,
MsalInterceptorConfiguration,
MsalModule,
MsalService,
MSAL_GUARD_CONFIG,
MSAL_INSTANCE,
MSAL_INTERCEPTOR_CONFIG,
MsalGuardConfiguration,
} from '@azure/msal-angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
const PROTECTED_RESOURCE_MAP: Map<string, Array<string>> = new Map([
['https://graph.microsoft.com/v1.0/me', ['user.read']],
[
'api/admin/users',
['api://<backend app id>/access_as_admin'],
],
]);
const IS_IE =
window.navigator.userAgent.indexOf('MSIE ') > -1 ||
window.navigator.userAgent.indexOf('Trident/') > -1;
export function loggerCallback(logLevel, message) {
console.log(message);
}
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: '<frontend app id>',
authority:
'https://login.microsoftonline.com/<azure ad tenant id>',
redirectUri: 'http://localhost:4200',
postLogoutRedirectUri: 'http://localhost:4200/#/logged-out',
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: IS_IE, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Verbose,
piiLoggingEnabled: false,
},
},
});
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
return {
interactionType: InteractionType.Redirect,
protectedResourceMap: PROTECTED_RESOURCE_MAP,
};
}
export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return {
interactionType: InteractionType.Redirect,
};
}
export function initializeApp(appConfig: AppConfigService) {
const promise = appConfig
.loadAppConfig()
.pipe(tap((settings: IAppConfig) => {}))
.toPromise();
return () => promise;
}
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
AppRoutingModule,
HttpClientModule,
MsalModule,
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true,
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory,
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory,
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory,
},
MsalService,
MsalGuard,
MsalBroadcastService,
],
bootstrap: [AppComponent],
})
export class AppModule {}
Then you can simply toss the MsalGuard
onto any route you want to protect.
For the backend, first install the Microsoft.Identity.Web package:
dotnet add package Microsoft.Identity.Web --version 1.3.0
Here's the relevant code in my Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
// other stuff...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(options =>
{
Configuration.Bind("AzureAd", options);
})
.AddInMemoryTokenCaches();
services.AddCors((options =>
{
options.AddPolicy("FrontEnd", builder =>
builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
}));
// other stuff...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// other stuff...
app.UseCors("FrontEnd");
app.UseAuthentication();
app.UseAuthorization();
// other stuff...
}
appsettings.json contains:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<azure ad domain>",
"TenantId": "<azure ad tenant id>",
"ClientId": "<backend app id>"
}