Securing an Angular application using Azure B2C

This article shows how to secure an Angular application using Azure B2C with OpenID Connect Code Flow and PKCE. The silent renew is supported using refresh tokens.

Code: Angular Azure B2C

History

2021-11-22 Updated to Angular OIDC 13.0.0

2021-07-20 Updated to Angular OIDC 12.0.2

Setting up Azure B2C

In the Azure portal, create a new App registration in your B2C tenant. Configure a mobile and desktop application and add the Angular redirect. We do this because we use a public client and cannot keep a secret in the Angular application. This configuration supports requesting the token using the code and no secret together with PKCE (Proof Key for Code Exchange). Also add your silent renew URL.

You could just add this directly to the manifest, if you want. Now add the Graph API scopes which can be requested.

Angular OpenID Connect Code flow with PKCE

The OpenID Connect flow is supported in the Angular application by using the angular-auth-oidc-client npm package. This needs to be added to your project in the package.json file.

"angular-auth-oidc-client": "12.0.2",

The Angular application is initialized in the App.Module. You can define the Azure B2C settings as configured for your tenant. The following example uses the id_token for the user profile data, and the session is renewed using an iframe and the file silent-renew.html. The session will refresh 60 seconds before it expires. The application uses both the access token (with the expires_in property) and the id_token to check this. The response type is ‘code’ and we want to use the OIDC Code flow with PKCE.

import { NgModule } from '@angular/core';
import { AuthModule, LogLevel } from 'angular-auth-oidc-client';

@NgModule({
  imports: [
    AuthModule.forRoot({
      config: {
        authority: 'https://b2cdamienbod.b2clogin.com/b2cdamienbod.onmicrosoft.com/B2C_1_sign_in/v2.0',
        authWellknownEndpointUrl:
          'https://b2cdamienbod.b2clogin.com/b2cdamienbod.onmicrosoft.com/B2C_1_sign_in/v2.0/.well-known/openid-configuration',
        redirectUrl: window.location.origin,
        postLogoutRedirectUri: window.location.origin,
        clientId: '00b1b58e-6193-4be1-bf15-3a37d6adefaf',
        scope: 'openid profile https://b2cdamienbod.onmicrosoft.com/ac9b845d-96d3-4410-9923-50ec7bc80db9/access_as_user',
        responseType: 'code',
        silentRenew: true,
        useRefreshToken: true,
        ignoreNonceAfterRefresh: true,
        maxIdTokenIatOffsetAllowedInSeconds: 600,
        issValidationOff: false, // this needs to be true if using a common endpoint in Azure
        autoUserInfo: false,
        logLevel: LogLevel.Debug,
        customParamsAuthRequest: {
          prompt: 'select_account', // login, consent
        },
      },
    }),
  ],
  exports: [AuthModule],
})
export class AuthConfigModule {}

And the App.Module

import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { routing } from './app.routes';
import { AuthConfigModule } from './auth-config.module';
import { ForbiddenComponent } from './forbidden/forbidden.component';
import { HomeComponent } from './home/home.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { ProtectedComponent } from './protected/protected.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';

@NgModule({
  declarations: [AppComponent, NavMenuComponent, HomeComponent, ForbiddenComponent, UnauthorizedComponent, ProtectedComponent],
  imports: [BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }), HttpClientModule, FormsModule, routing, AuthConfigModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

The checkAuth function, which will initialize the state of your auth is called in the app.component or using the auth guard. This ensures that the auth will work after an F5, of if you want to implement login logic here, this is possible.

import { RouterModule, Routes } from '@angular/router';
import { AutoLoginAllRoutesGuard } from 'angular-auth-oidc-client';
import { ForbiddenComponent } from './forbidden/forbidden.component';
import { HomeComponent } from './home/home.component';
import { ProtectedComponent } from './protected/protected.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';

const appRoutes: Routes = [
  { path: '', pathMatch: 'full', redirectTo: 'home' },
  { path: 'home', component: HomeComponent, canActivate: [AutoLoginAllRoutesGuard] },
  { path: 'forbidden', component: ForbiddenComponent, canActivate: [AutoLoginAllRoutesGuard] },
  { path: 'protected', component: ProtectedComponent, canActivate: [AutoLoginAllRoutesGuard] },
  { path: 'unauthorized', component: UnauthorizedComponent },
];

export const routing = RouterModule.forRoot(appRoutes);

The angular-auth-oidc-client npm package can be used anywhere in the application and a login can be sent to the STS. The isAuthenticated$ can be used to check the if you are authenticated, and the user data, profile data can be accessed using userData$.

import { Component, OnInit } from '@angular/core';
import { OidcSecurityService, UserDataResult } from 'angular-auth-oidc-client';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-home',
  templateUrl: 'home.component.html',
})
export class HomeComponent implements OnInit {
  userData$: Observable<UserDataResult>;
  isAuthenticated = false;

  constructor(public oidcSecurityService: OidcSecurityService) {}

  ngOnInit() {
    this.oidcSecurityService.isAuthenticated$.subscribe(({ isAuthenticated }) => {
      this.isAuthenticated = isAuthenticated;

      console.warn('authenticated: ', isAuthenticated);
    });
    this.userData$ = this.oidcSecurityService.userData$;
  }
}

The Angular application uses silent renew with refresh tokens to refresh the session with Angular B2C.

Now the application runs using Azure B2C as the token server.

Links

https://openid.net/specs/openid-connect-core-1_0.html

https://azure.microsoft.com/en-us/services/active-directory/external-identities/b2c/

https://github.com/damienbod/angular-auth-oidc-client

https://www.npmjs.com/package/angular-auth-oidc-client

https://oauth.net/2/pkce/

4 comments

  1. […] Securing an Angular application using Azure B2C (Damien Bowden) […]

  2. Francois Rousseau · · Reply

    The link to the code does not work anymore – has it changed for the following :

    https://github.com/damienbod/angular-auth-oidc-client/tree/main/projects/sample-code-flow-azure-b2c

    ?

    anyway thanks for all !

    1. Thanks, I’ll update this

      1. Updated, thanks for reporting

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.