This article will guide you through the process of configuring your Single Page Application (SPA) in TypeScript (or JavaScript) to use Azure Active Directory (AAD) authentication.
We will use adal.js Active Directory Authentication Library (ADAL) for JavaScript and ng2-adal (which is built upon adal-angular)
Prepare your application to use Azure Active Directory
- If your application doesn’t already use SSL, it is hightly recommended to do it now. AAD without SSL, thus running over an unsecure connection is not advisable and a real hassle to setup.
Note the SSL url of your application, as you will need it later to register the application in AAD.
Register your application in Azure Active Directory
If you haven’t already registered your application in the Azure Portal, follow the steps below:
- Sign in to the Azure portal
- Choose Azure Active Directory from your services (search using More Services if it isn’t shown yet)
- Choose App registrations and Add
- Enter a Name, choose Web app / API for Application Type and enter the URL of your web application under Sign-on URL (without the trailing slash)
The URL is the SSL url we got earlier when we enable SSL for our web application - Click Create
- Still in your application registrations, choose your application, choose All settings and Properties
- Copy the Application ID
- Enter the Logout URL as the Sign-on URL you entered earlier, followed by
/Account/EndSession
This will link to the single sign out URL of our application - Also from the Settings menu, add a Key with a duration of 1 or 2 years
Note down the key, as you will not be able to retrieve it afterwards.
Additional steps required for your SPA
Authentication happens using OAuth2 protocol. Applications provisioned in AAD are not by default enabled to use OAuth2, so you need to explicitly opt-in to do so:
- Still in the Azure Portal and in the page of the application you created before, click on Manifest to open the manifest editor.
Alternatively, you can download, edit and upload the manifest afterwards, but the inline manifest editor is much easier to use. - Look for the oauth2AllowImplicitFlow setting, which by default is set to false. Set it to true and save the manifest
"oauth2AllowImplicitFlow": true,
Implementing and configuring adal.js in your Angular 2 SPA – Overview
In general you will need to follow theses steps, I will explain them in detail further on:
- Acquire the adal.js resources
- Create a service that provides you with the AAD settings
- Create and use a routeguard
- Add the Adal services to your application and initialize them
- Create a component to login and logout
Acquire the adal.js resources
- If you’re using the Node Package Manager (npm) system, it’s as easy as executing 1 single command to pull in the ng2-adal package and all it’s dependencies
npm install ng2-adal --save
You can also pull in the ng2-adal package with another package manager or manually. Make sure to also pull in all the required dependencies. - If you’re using a module loader like SystemJS, you will need to add the modules to its configuration file, like it is shown for the systemjs.config.js file for SystemJS:
(function (global) { System.config({ paths: { // paths configuration }, map: { // existing map configuration // adal libraries 'ng2-adal': 'npm:ng2-adal', 'adal': 'npm:adal-angular/lib', 'adal-angular': 'npm:adal-angular/lib', }, packages: { // existing package configuration // adal packages 'ng2-adal': { main: 'core.js', defaultExtension: 'js' }, 'adal-angular': { main: 'adal-angular', defaultExtension: 'js' }, 'adal': { main: 'adal.js', defaultExtension: 'js' } } }); })(this);
Create a service that provides you with the AAD settings
This is a simple angular service that stores the AAD settings, so they are easily manageable and accessible
- create a file called secret.service.ts
import {Injectable} from '@angular/core'; @Injectable() export class SecretService { public get adalConfig(): any { return { tenant: '[your tenant]', clientId: '[a GUID, the application ID]', redirectUri: window.location.origin + '/', postLogoutRedirectUri: window.location.origin + '/' }; } }
Create and use a routeguard
A route guard is used to control the routers behavior and returns true or false to indicate whether the route can be followed or not
- Create an authentication guard (LoggedInGuard.ts) and implement the canActivate() method that checks whether the user is authenticated over Adal, if authenticated returning true, otherwise navigating to a login page
import { Injectable } from '@angular/core'; import { Router, CanActivate } from '@angular/router'; import { AdalService } from 'ng2-adal/core'; @Injectable() export class LoggedInGuard implements CanActivate { constructor(private adalService: AdalService, private router: Router) { } canActivate() { if (this.adalService.userInfo.isAuthenticated) { return true; } else { this.router.navigate(['/login']); return false; } } }
- Protect the route with the authentication guard in your routing configuration (fragmented code sample shown):
import { SecretService } from "./secret.service"; import { AdalService } from "ng2-adal/core"; import { LoggedInGuard } from './LoggedInGuard'; // ... { path: 'protected', component: protectedComponent, canActivate: [LoggedInGuard] }, // ... providers: [AdalService, SecretService, LoggedInGuard],
Add the Adal services to your application and initialize them
- In app.component.ts add following code to import the services
import { Component, OnInit } from '@angular/core'; import { SecretService } from './secret.service'; import { AdalService } from "ng2-adal/core";
- Still in app.component.ts initialize the Adal service in the constructor with the settings stored in the Secret service
export class AppComponent implements OnInit { profile: any; constructor( private adalService: AdalService, private secretService: SecretService) { this.adalService.init(this.secretService.adalConfig); } }
- To prevent the user having to log in every time again, the authentication token is stored in the browser cache. This allows us to try to retrieve this token and continue using the application without being redirected again to the Azure login page.
Add following code to app.component.ts to get the user object from cache:ngOnInit(): void { this.adalService.handleWindowCallback(); this.adalService.getUser(); }
Create a component to login and logout
This is a very straightforward way to add a login and logout button to your application. The essence is to call the adalService.login()
and adalService.logOut()
functions. Integrate them in your application to meet your requirements:
- Create a login component (login.component.ts)
import {Component, OnInit} from '@angular/core'; import {Router} from "@angular/router"; import {AdalService} from 'ng2-adal/core'; @Component({ selector: 'welcome', template: '<h1>You need to login first</h1><button (click)="logIn()">Login</button>' }) export class LoginComponent { constructor( private router: Router, private adalService: AdalService ) { if (this.adalService.userInfo.isAuthenticated) { this.router.navigate(['/home']); } } public logIn() { this.adalService.login(); } }
If the user is already logged in with valid Azure Active Directory credentials, he will immediately be redirected to the /home page.
Otherwise, the user is presented with the Azure login page to login first, and afterwards redirected to the home page URL you provided in the AAD application registration. - Create a logout component (logout.component.ts)
import {Component} from '@angular/core'; import {AdalService} from 'ng2-adal/core'; @Component({ selector: 'logout', template: '<div protected><h1>This is the logout page.</h1><button (click)="logOut()">Logout</button></div>' }) export class LogoutComponent { constructor( private adalService: AdalService ) { } public logOut() { this.adalService.logOut(); } }
This will sign out the user from Azure Active Directory, invalidate the users authentication token and redirect to the post logout URL.
- Add routes to the login and logout components
import { LoginComponent} from './login.component'; import { LogoutComponent} from './logout.component';
{ path: 'logout', component: LogoutComponent }, { path: 'login', component: LoginComponent },
declarations: [AppComponent, LoginComponent, LogoutComponent, /* ... */ ]
Elvin
Thank you for this tutorial. I got mine working!
You just need to watch out for the Signon URL in Azure as creating the application copies it as the Homepage URL and Reply URLs (which gave me an error). But everything else is fine. Good stuff!