Protect .NET Core Angular App with Azure AD B2C

Protect .NET Core Angular App with Azure AD B2C

In this article, let’s have a look at how .NET Core Angular Web Application can be protected using Azure AD B2C.

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 need to have Azure AD B2C instance. Refer my previous blog for detailed steps for the same.

After creating Azure AD B2C, make sure you create the user flows as described in this post. We have created only sign up and sign in flow in this blog. But you can follow same steps to create a reset password flow.

Create Angular App

Create .NET Core Web Application using Visual Studio 2019.

Make sure Angular option is selected in the project template type. This would create the project.

For people who do not work with this type of project template, the Angular app is inside the ClientApp folder. The .NET Core web application has below middlewares which serve the Angular application.

Visual studio 2019 .NET Core Angular web application template uses Angular 8 as of today. If you want to use more latest version, you can delete the contents of ClientApp folder. Then create a new Angular app in same folder using Angular CLI.

Now, when we run our application, we should be able to see a web page like below. We will try to make Home page anonymous, but the Counter and Fetch data pages would require user to be logged in to our application.

Let’s get started.

Azure AD App Registration

Login to Azure portal and switch to the Azure AD B2C directory. Then search for Azure AD B2C in the search box form the navigation. A panel as shown below will open. Select App registrations.

Now, click on New registration to register the new Angular application we just created. Then we need to enter below details in the new application registration panel:

  • Name, readable name for our registered application
  • Supported account types, keep this to default. The default option means user can login using Azure AD B2C, but they cannot access Azure resources
  • Redirect URI, the application URI where user will be redirected to after successful login.
  • Permissions, let this also be default. This will grant permission for openid and offline_access permissions.

Then click the Register button. Note down the application id of this registration. This would be required in the authentication configurations in Angular app.

Azure AD Authentication

Next, the registered application details panel will open after you click on Register button. There, select Authentication option and Select both ID Tokens and Access Tokens check-boxes and hit Save button.

Install MSAL.js

Now, lets move to our angular application. Install the MSAL packages using below command. The below command will install two packages – MSAL and MSAL-Angular.

Make sure to run the below command in folder ClientApp.

npm install msal @azure/msal-angular --save

App-Config.ts

Add this TS file in ~/ClientApp/src/app folder. This file should have a code as shown below. Below are the main sections

  • User Flows and Authorities, here we need to specify the user flows which we have created in the Azure AD B2C. Also update the name of Azure AD B2C in the URLs
  • Web API Scopes and URLs, these are the protected APIs which our Angular app needs to call. This data is used to populated protectedResourcesMap. For the purpose of this post, we do not need this. So you can keep it as it is.
  • Authentication Configurations, here we need to update appropriate Application ID (i.e. client id) which we have got after registering this app in Azure AD B2C. Also, update application URLs.
  • login request and token requests, specify the scopes required for login and for access tokens required to access APIs respectively.
  • protectedResourcesMap, this is collection of APIs where each item has URL and its respective scope. These are the APIs which our application is supposed to call. The respective scopes are required to be granted in the access token in order to get access to API.
  • msalAngularConfig, these are configurations which are required to be specified in Angular module.
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 (Not Relevant For This Post)
***********************************************************/
export const apiConfig: { b2cScopes: string[], webApi: string } = {
b2cScopes: ['https://samplead.onmicrosoft.com/helloapi/demo.read'],
webApi: 'https://sampleadhello.azurewebsites.net/hello'
};
/***********************************************************
* 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

App.Module.ts

Next step is to add the above configurations in Angular app module. There are four things which are required to be done:

  • MsalModule, import this module
  • MsalInterceptor, add this interceptor in providers collection. This interceptor is an HTTP_INTERCEPTOR which will automatically get the access token and will embed it in the APIs that our application needs to call. This is out of scope for this blog.
  • MSAL_CONFIG, a function which will return authentication settings from app-config.ts
  • MSAL_CONFIG_ANGULAR, a function which will return angular specific settings from app-config.ts
  • MsalService, import this service as this service exposes APIs to login and logout.

The module should be as shown in below snippet:

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { msalConfig, msalAngularConfig } from './app-config';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import { CounterComponent } from './counter/counter.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';
import { Configuration } from 'msal';
import {
MsalModule,
MsalInterceptor,
MSAL_CONFIG,
MSAL_CONFIG_ANGULAR,
MsalService,
MsalAngularConfiguration
} from '@azure/msal-angular';
function MSALConfigFactory(): Configuration {
return msalConfig;
}
function MSALAngularConfigFactory(): MsalAngularConfiguration {
return msalAngularConfig;
}
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
CounterComponent,
FetchDataComponent
],
imports: [
BrowserModule,
HttpClientModule,
MsalModule,
AppRoutingModule
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_CONFIG,
useFactory: MSALConfigFactory
},
{
provide: MSAL_CONFIG_ANGULAR,
useFactory: MSALAngularConfigFactory
},
MsalService
],
bootstrap: [AppComponent]
})
export class AppModule { }
view raw app.module.ts hosted with ❤ by GitHub

App-Routing.Module.ts

By default, when a new .NET Core Angular application project is created, the routing configurations are in the app.module.ts. As you already might have realized, the routing configurations were not present in the app module file.

So, add this new file app-routing.module.ts. This file will specify that Counter and Fetch Data routing options will have MsalGuard, which checks if user is logged in. If not, it redirects user to login page.

The app routing file will be as shown below:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { FetchDataComponent } from './fetch-data/fetch-data.component';
import { CounterComponent } from './counter/counter.component';
import { MsalGuard } from '@azure/msal-angular';
const routes: Routes = [
{
path: '',
component: HomeComponent,
pathMatch: 'full'
},
{
path: 'counter',
component: CounterComponent,
canActivate: [
MsalGuard
]
},
{
path: 'fetch-data',
component: FetchDataComponent,
canActivate: [
MsalGuard
]
},
];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: false })],
exports: [RouterModule]
})
export class AppRoutingModule { }

App.Component.ts

Here, we need to setup few things on initialization of the app component. Some of those things are:

  • subscribe to msal:loginSuccess, this handler is called after successful login. Here the code checks the tfp claim from the token and check if this matches with the name of sign up and sign in flow name.
  • subscribe to msal:loginFailure, this handler is called if login is failed. This can be used to check if the error is any special code returned by Azure AD.
  • handleRedirectCallback, to check if the login was successful. If not, to log the error and show it to user.
  • setLogger, this method is to enable the logging during the authentication flow.

In addition to this, there are also few methods to initialize login and logout of users. Below is the complete code of the application:

import { Component, OnInit } from '@angular/core';
import { BroadcastService, MsalService } from '@azure/msal-angular';
import { Logger, CryptoUtils } from 'msal';
import { isIE, b2cPolicies } from './app-config';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
title = 'app';
isIframe = false;
loggedIn = false;
constructor(private broadcastService: BroadcastService, private authService: MsalService) { }
ngOnInit() {
this.isIframe = window !== window.parent && !window.opener;
this.checkAccount();
// event listeners for authentication status
this.broadcastService.subscribe('msal:loginSuccess', (success) => {
// We need to reject id tokens that were not issued with the default sign-in policy.
// "acr" claim in the token tells us what policy is used
// (NOTE: for new policies (v2.0), use "tfp" instead of "acr")
if (success.idToken.claims['tfp'] !== b2cPolicies.names.signUpSignIn) {
window.alert("Password has been reset successfully. \nPlease sign-in with your new password");
return this.authService.logout()
}
console.log('login succeeded. id token acquired at: ' + new Date().toString());
console.log(success);
this.checkAccount();
});
this.broadcastService.subscribe('msal:loginFailure', (error) => {
console.log('login failed');
console.log(error);
// Check for forgot password error
if (error.errorMessage.indexOf('AADB2C90118') > 1) {
if (isIE) {
this.authService.loginRedirect(b2cPolicies.authorities.resetPassword);
} else {
this.authService.loginPopup(b2cPolicies.authorities.resetPassword);
}
}
});
// redirect callback for redirect flow (IE)
this.authService.handleRedirectCallback((authError, response) => {
if (authError) {
console.error('Redirect Error: ', authError.errorMessage);
return;
}
console.log('Redirect Success: ', response);
});
this.authService.setLogger(new Logger((logLevel, message, piiEnabled) => {
console.log('MSAL Logging: ', message);
}, {
correlationId: CryptoUtils.createNewGuid(),
piiLoggingEnabled: false
}));
}
// other methods
checkAccount() {
this.loggedIn = !!this.authService.getAccount();
}
login() {
this.authService.loginRedirect();
}
logout() {
this.authService.logout();
}
}
view raw app.component.ts hosted with ❤ by GitHub

App.Component.html

For facilitating login and logout from the application, let’s add the buttons in the app component view as shown in below code.

<body>
<button *ngIf="!loggedIn" (click)="login()">Login</button>
<button color="warn" *ngIf="loggedIn" (click)="logout()">Logout</button>
<app-nav-menu></app-nav-menu>
<div class="container">
<router-outlet></router-outlet>
</div>
</body>
view raw app.component.html hosted with ❤ by GitHub

Run and Verify

Now, if we run the application, it will show the home page. If we click on login button or counter button from nav bar, it will redirect user to login page. We can sign up using our email ids and then sign in using those same email address.

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

This Post Has One Comment

Comments are closed.