Angular App with Protected Web APIs using MSAL

Angular App with Protected Web APIs using MSAL

In previous articles, we have seen capabilities of Azure AD B2C and how to use those in our applications using MSAL.

In this article let’s try to implement the scenario, where protected Angular web application will try to call the protected web API, both protected using the same Azure AD B2C instance.

What we need ?

For following all steps in this article, we will need Azure Subscription. If you don’t have an Azure subscription, create a free account before you begin.

We will also need instance of Azure AD B2C instance. We will also need API and Angular App already registered in that instance.

Below is the list of those posts which explains all the steps to get there:

After following these posts, we will have a protected Angular app and a protected web API. In this article, let’s add code and configurations so that this Angular app can call the protected API.

What are next steps?

We have registered Angular app and web API app in Azure AD B2C instance. We also have exposed an API scope from API registration.

Now, we need to go to Angular app registration in Azure AD B2C and grant permission to the API Scope. Once this is done, we need to update Angular app for protectedResourcesMap, where the API URL and scope needs to be specified.

Then we will update Fetch Data component to call protected API.

We will also need to add CORS rule to allow call from the Angular application, as the API is getting called from different domain.

Let’s have a look at these things step by step.

Azure AD App Registration

Login to Azure Portal and switch to Azure AD B2C directory.

Search “Azure AD B2C” in the search box in top navigation and a panel as shown below should open. The snapshot is not showing any applications registered, but ideally you should have 2, My Web API and My Angular App.

Select App registrations and select registration with name “My Angular App” (this is the name you provided while registering Angular app). This should open the registration details panel. On the new panel, select API Permissions option from left navigation.

If you remember, we already have provided admin consent for openid and offline_access scopes. Now, we need to add permission for the API scope which we already have registered. So, click on Add a permission button.

The request permission panel will open, which has 3 tabs. Select the last tab with title “My APIs“. This tab shows only those APIs which are registered in the current Azure AD B2C instance. Select “My Web API” (which is name of API registration) from the list.

As soon as API is selected, panel shows more inputs:

  • Type of permission – Delegated vs Application, delegated permissions option is enabled and application permissions is disabled. Delegated permission means the API will execute in the logged-in user’s context. This is what we need for our scenario. Application permissions are for processes which run as background jobs, e.g. daemon applications.
  • Select Permission, this is the name of scope which we entered while registering the API.

After selecting these two inputs and click on Add permissions button.

Once these permissions are added, next step is to grant the admin consent to allow our Angular app to call the API. This can be done by clicking on Grant admin consent for (your tenant name) button. This will change the status of newly added permission to granted.

Angular Configurations

We are using MSAL in Angular application to interact with Azure AD B2C. MSAL configurations has protectedResourceMap configurations, which is an array where every item has an API URL and collection of scopes required to call that API.

Let’s add the API and scope in configuration. There are three main changes that we need to do :

  • apiConfig to update the correct scope and correct URL for the API
  • tokenRequest, to make sure that the apiConfig.b2cScopes are included in the token’s consent scopes.
  • protectedResourceMap, to make sure that apiConfig.webApi and apiConfig.b2cScopes are included. This configuration makes sure that MsalGuard is applied when Angular app tries to call the web API.

The completed file (with changes) should be as shown below:

import { Configuration } from 'msal';
import { MsalAngularConfiguration } from '@azure/msal-angular';
// this checks if the app is running on IE
export const isIE = window.navigator.userAgent.indexOf('MSIE ') > 1 ||
window.navigator.userAgent.indexOf('Trident/') > 1;
/***********************************************************
* STEP 1 – B2C Policies and User Flows
***********************************************************/
export const b2cPolicies = {
names: {
signUpSignIn: "B2C_1_SignUpSignIn",
resetPassword: "B2C_1_Reset",
},
authorities: {
signUpSignIn: {
authority: "https://samplead.b2clogin.com/samplead.onmicrosoft.com/B2C_1_SignUpSignIn"
},
resetPassword: {
authority: "https://samplead.b2clogin.com/samplead.onmicrosoft.com/b2c_1_reset"
}
}
}
/***********************************************************
* STEP 2 – Web API Scopes & URLs
***********************************************************/
export const apiConfig: { b2cScopes: string[], webApi: string } = {
b2cScopes: ['https://samplead.onmicrosoft.com/sample-api/api-scope'],
webApi: 'https://localhost:44379/weatherforecast'
};
/***********************************************************
* STEP 3 – Authentication Configurations
***********************************************************/
export const msalConfig: Configuration = {
auth: {
clientId: "3227a83e-c3bc-482d-82a1-9eee30b73609",
authority: b2cPolicies.authorities.signUpSignIn.authority,
redirectUri: "https://localhost:44361/",
postLogoutRedirectUri: "https://localhost:44361/",
navigateToLoginRequestUrl: true,
validateAuthority: false,
},
cache: {
cacheLocation: "localStorage",
// Set this to "true" to save cache in cookies
// to address trusted zones limitations in IE
storeAuthStateInCookie: isIE,
},
}
/***********************************************************
* STEP 4 – Scopes Required For Login
***********************************************************/
export const loginRequest: { scopes: string[] } = {
scopes: ['openid', 'profile'],
};
/***********************************************************
* STEP 5 – Scopes which will be used for the access token
* request for your web API
***********************************************************/
export const tokenRequest: { scopes: string[] } = {
scopes: apiConfig.b2cScopes
};
/***********************************************************
* STEP 6 – MSAL Angular Configurations
* Define protected API URLs and required scopes
***********************************************************/
export const protectedResourceMap: [string, string[]][] = [
[apiConfig.webApi, apiConfig.b2cScopes]
];
/***********************************************************
* STEP 7 – MSAL Angular specific configurations
*
***********************************************************/
export const msalAngularConfig: MsalAngularConfiguration = {
popUp: !isIE,
consentScopes: [
loginRequest.scopes,
tokenRequest.scopes,
],
// API calls to these coordinates will NOT activate MSALGuard
unprotectedResources: [],
// API calls to these coordinates will activate MSALGuard
protectedResourceMap,
extraQueryParameters: {}
}
view raw app-config.ts hosted with ❤ by GitHub

Angular Fetch Data

The project template for .NET Core Web Application for Angular has an API controller also. And the Fetch Data page calls this API controller to get the weather forecast data. We have to change this code to call the Web API project’s controller.

So, the fetch-data.component.ts should be as shown below:

import { Component, Inject, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-fetch-data',
templateUrl: './fetch-data.component.html'
})
export class FetchDataComponent implements OnInit {
public forecasts: WeatherForecast[];
constructor(private readonly http: HttpClient) {
}
ngOnInit(): void {
const apiUrl = "https://localhost:44379/weatherforecast";
this.http.get<WeatherForecast[]>(apiUrl).subscribe(result => {
this.forecasts = result;
}, error => console.error(error));
}
}
interface WeatherForecast {
date: string;
temperatureC: number;
temperatureF: number;
summary: string;
}

API CORS Policy

As our API is a separate web application, it needs to have a CORS policy to allow calls from the Angular web application. Below is snippet of CORS policy which can be inserted in Configure method of Startup.cs

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

Run and Verify

Now, let’s run both the applications (API as well as Angular app). When you click on login button, you would be asked to login. After successful login, if you click Fetch Data menu option, you should see the weather forecast data.

This time the data is not returned by the API controller in Angular app. It is returned by the controller in API project. You can verify this in the debugging tools by checking the Request URL.

You can also notice that the authorization header is already populated with bearer token. You may be wondering, HOW ?

This is happening because app module configuration has MsalInterceptor as HTTP_INTERCEPTORS. This interceptor automatically acquires the access tokens for protected API calls and adds it to the Authorization header.

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

Leave a Reply