Angular App and Azure AD Protected web API using MSAL

Angular App and Azure AD Protected web API using MSAL

In the previous blog post, we have seen how can we use MSAL.NET with .NET Core web applications. Many applications use Single Page applications as front end which call web APIs for achieving business functionality. Let’s have a look at the steps required to make this scenario work with MSAL.

Scenario

A solution which has two components:

Angular application is required to call Web APIs for achieving business functionality. We will use MSAL.js library to make this scenario working. This library enables Angular 6+ applications to authenticate users with Microsoft Azure Active Directory.

Protected Web API

Create a web API using Visual Studio 2019. Then Register it in the Azure AD. Then configure Web API to use the tenant id and client id settings from Azure AD.

Refer my previous blog article about securing web APIs for detailed steps.

Protected Angular App

Refer my previous blog article to get detailed steps about how to create Angular application and how to protect it using MSAL and Azure AD.

Azure AD App Permissions

Now, login to Azure Portal and go to Azure Active Directory from left side navigation menu. Then select App Registrations. In app registrations, we can see an App with name SecureApp. Click on it.

Then go to API permissions of that app. On this configured permissions panel, all the permissions which have been granted to this SecureApp can be seen. By default, user.read is the only permission available with the app, as it is required to read user details during sign-in.

Next, click on Add a permission button, which will open new panel as shown below. On this Request API permissions panel, select My APIs tab which will show all API app registrations in the selected Azure AD.

Select SampleWebApi from the list.

Next, you will be asked to select the permissions. There are two types of permissions as shown in below snapshot

  • Delegated permissions, select this when you want to call the API as the signed-in user. This is applicable for applications which allow end-users to sign in and perform some actions. This option means the API would be called on behalf of logged in user. This option is enabled and by default selected.
  • Application Permissions, this is disabled by default for us, because SecureApp is not a background service. This should be selected only if the application is running as background service / daemon.

Select Delegated Permissions option. Anyway Application Permissions option would be disabled as our application is web application.

Also select the API permission access_as_user and click on Add permissions button.

After adding it, the API permissions list would look similar to below snapshot.

Confidential vs Public App

If you have already seen my previous blog post which covers web app calling web API scenario, you must be aware of a concept called Confidential Client Application.

Confidential Client App

A confidential client application is an application which is considered to be difficult to get access of. It runs on a server. Some examples may include .NET Core web applications, Web APIs, Or even background jobs which are hosted on web server.

A confidential client application runs on server, far from reach of public users, thus making it capable of holding a client secret. The client ID is exposed through the web browser, but the secret is passed only in the back channel and never directly exposed.

Public Client App

A public client application is an application which runs on computers, tablets, phones or in web browsers (like Single Page Applications). These applications cannot be trusted to keep the application secrets safe.

Like confidential client apps, public client apps also maintain token cache. Public client apps have four ways to acquire a token (four authentication flows).

Confidential client apps have three ways to acquire a token (and one way to compute the URL of the identity provider authorize endpoint). For more information, see Acquiring tokens.

Why are we discussing this suddenly ?

You might have guessed this right. This is because we have single page application (Angular App). This app gets downloaded to the browser and that’s why this cannot have client secrets. Unlike web app calling API scenario discussed before in my blogs, this scenario involving Angular app does not need client secret.

Angular Configurations

We already have done everything to interact with Azure AD.

MSAL Angular provides an Interceptor class that automatically acquires tokens for outgoing requests that use the Angular http client to known protected resources.

MsalInterceptor can be configured as a provider in App module as shown in below snippet. This

Next, we need to specify protected resources and their scopes. This can be specified again in the app module. Provide a map of protected resources to MsalModule.forRoot() as protectedResourceMap and include those scopes in consentScopes.

For our case, we need to specify API URL and API scope (api://{guid}/{scope-name}). This again you can refer in below code snippet.

Complete Angular App module file is shown below

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { MsalModule, MsalInterceptor } from '@azure/msal-angular';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AuthService } from './auth.service';
@NgModule({
declarations: [
AppComponent,
HomeComponent
],
imports: [
MsalModule.forRoot(
{
auth: {
clientId: 'd471db94-db21-4103-aefa-679cd7435745', // This is your client ID
authority: 'https://login.microsoftonline.com/da41245a5-11b3-996c-00a8-4d99re19f292', // This is your tenant ID
redirectUri: 'http://localhost:4200', // This is your redirect URI
postLogoutRedirectUri: "http://localhost:4200",
navigateToLoginRequestUrl: true,
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false, // Set to true for Internet Explorer 11
},
},
{
popUp: false,
consentScopes: [
"user.read",
"openid",
"profile",
"api://5e971e5c-a661-4d82-ba97-935480492129/access_as_user"
],
unprotectedResources: ["https://www.microsoft.com/en-us/"],
protectedResourceMap:[
['https://localhost:44389/weatherforecast', ['api://5e971e5c-a661-4d82-ba97-935480492129/access_as_user']],
['https://graph.microsoft.com/v1.0/me', ['user.read']]
],
extraQueryParameters: {}
}
),
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
AuthService
],
bootstrap: [AppComponent]
})
export class AppModule { }
view raw app.module.ts hosted with ❤ by GitHub

Call API From Angular App

Everything is setup, only one thing is pending. We need to make an API call from app component. For simplicity, I will not update view, but just add console.log and alert statements to log the API response.

In app.component.ts, we can inject HttpClient, and that instance can be used to make the GET call from ngOnInit method.. The MsalInterceptor will automatically get the access token and include that token in the HTTP call.

import { Component, OnInit } from '@angular/core';
import { AuthService } from './auth.service';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'SecureApp';
constructor(private authService: AuthService, private http: HttpClient) { }
ngOnInit() {
this.authService.initializeAuth();
let endpoint = "https://localhost:44389/weatherforecast";
this.http.get(endpoint).toPromise()
.then(data => {
console.log(data);
alert(JSON.stringify(data));
});
}
login() {
this.authService.logIn();
}
logOut() {
this.authService.logOut();
}
}
view raw app.component.ts hosted with ❤ by GitHub

Why API call may fail ?

When I tried all above steps, my API call was failing because of below error.

This call was failing because these are two different applications. So the API application was not allowing Angular application to make the call and was throwing CORS error.

In order to fix this error, I had to add CORS middleware in the Configure method of API Startup class. This middleware allows all calls from specified origins (in our case http://localhost:4200 for local angular app).

app.UseCors(builder =>
{
builder.WithOrigins("http://localhost:4200")
.AllowCredentials()
.AllowAnyMethod()
.AllowAnyHeader();
});
view raw Startup.cs hosted with ❤ by GitHub

Verification

We will have to run the API first and then run the Angular application. If login is successful, we will be able to see the API result in the alert as shown in below snapshot. That means our solution is working.

GitHub Repository

You can brows the code samples created by me on my GitHub repository.

I hope you enjoyed this article. Let me know your thoughts.

Leave a Reply

This Post Has 9 Comments

  1. Cornu Aspersum

    Hello. Must the Angular SPA and Web API (both protected with Azure AD) be in two separate ASP.NET Core projects for it to work? I’ve found that I was able to produce an Angular SPA with protected routes and also have web APIs running on the same web server, but have not figured out to protect the APIs (and so the APIs, that are actually meant for the SPA, are still accessible without authorization)

    1. Manoj Choudhari

      If you have a look at the documentation about template, it says the template have been provided to ensure that you can use Angular as FrontEnd and APIs as backend from the same project. This template provides convenience to publish your APIs and angular application as a single unit.

      So technically – yes, you can have them both in a single project. (Reference: https://docs.microsoft.com/en-us/aspnet/core/client-side/spa/angular?view=aspnetcore-3.1&tabs=visual-studio)

      But, I prefer having two projects Project.UI and Project.API – to separate both the things. Advantage, I can change site structure / styles without redeploying APIs. The only minor disadvantage in this case CORS needs to be enabled on API project for allowing calls from UI project.

  2. Aditya Chouhan

    Nicely articulated. I followed your all three articles on AAD- API, Angular app and this one. While implementing this one (angular-app-and-azure-ad-protected-web-api-using-msa) my angular app is getting 500 while calling my API.I enabled appinsights for my API and got to know the exact error. I can see System.Net.Sockets.SocketException exceptions and inner most error is- “Unable to obtain configuration from:XXX” and Unable to retrieve document from XXX. No such host is known. Can you please assist me. Thanks!

    1. Manoj Choudhari

      I think it might be because the instance (authority) is not correctly set in the configurations of API. Make sure that you are passing the token to API (i am assuming the API is also protected. Also, validate if all permissions are correctly set.

  3. Vinod

    Very well explained. This helped me complete my POC. Thank you very much Manoj

  4. Diego Peppert

    Thank you very much Manoj, this tutorial is pure gold! Greetings from Argentina!

  5. Vikas Malik

    Hi Manoj,

    I tried to call DOT NET CORE webAPI which is registered with Azure AD having OAuth Protocol. Same way my Angular APP is also configured. I am getting an error while trying to call API, which is ( Error when acquiring token for scopes: api://XXXXXXXXXXXXXXXXX/access_as_user ClientAuthError: The idToken is null or empty. Please review the trace to determine the root cause. Raw ID Token Value: null. )

    I am using the token acquired silently in my app.component.ts using acquireTokenSilent method of MSAL. I am appending the same token to my header as bearer token which results in unauthorized access error. Could you, Please help me to resolve the same ?

    Thanks

    1. Manoj Choudhari

      Hi Vikash,

      It is very difficult for me to guess what is the error without knowing Azure AD configurations. But if I guess, there might be two areas which you should check in my blog post.
      Firstly, check if the API permissions are correctly configured on Angular app registration. It should have delegated permissions configured for APIs.
      The other thing to check is to make sure that you have specified the scopes correctly as suggested in Angular Configurations section of this article.
      Hope this helps. Thanks !