Angular and Azure AD with a Better UX
Hello, world! In this post I improve the user experience by adding Bootstrap and greeting the user by her name once she is logged in. It assumes you have read Angular6 and Azure AD.
This property is to be reflected in the AppComponent so the HTML template can use it:
The buttons are shown or hidden depending on whether the user is logged in or not:
With all the changes above applied, the page appears initially like this:
And when the user is logged in, like this:
Let's invite Bootstrap to the party and the new version of app.component.html...
...and we get a prettier UI :)...
loginPopup produces the encoded Identity Token that is printed out to the console for debugging and exploration purposes:
Let's copy and paste the Identity Token to the very useful JWT.io to see what's in it:
Identity Token is in the JSON Web Token (JWT) format and it is made of claims or key-value pairs containing information like the token issuer ("iss") and the user name ("name"). A new version of the AuthService uses MSAL.IdToken to decode the Identity Token, get the "name" claim value, and expose it through the username property:
The username is reflected in AppComponent...
...so it can be used in the template app.component.html like this:
I am sure that the application is working as intended because I created and ran my unit tests but here it is, also, the visual evidence:
That's it for now. Remember that the code can be downloaded from here (committed as "Better UX"). In my next post I am going to give a 101 intro on Microsoft Graph. See you there...
A Better UX
It makes little or no sense to have both Login and Logout buttons visible at the same time. Essential state management: the Login button must be visible when the user is not authenticated and Logout must be when she is. This will be accomplished by modifying AuthService and AppComponent. Let's start with AuthService where the userLoggedIn property is added and set when a successful authentication took place and unset when the user logs out:import * as Msal from 'msal'; import { Injectable } from '@angular/core'; import { Observable, from } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private userAgentApplication: Msal.UserAgentApplication; private _userLoggedIn: boolean; get userLoggedIn(): boolean { return this._userLoggedIn; } set userLoggedIn(value) { this._userLoggedIn = value; } constructor() { this.userAgentApplication = new Msal.UserAgentApplication( '55f4dfa9-2425-44f0-83a6-d3464b2a7eaa', 'https://login.microsoftonline.com/illyum.onmicrosoft.com', null ); } public login(): Observable{ const graphScopes = ['user.read']; const promise = this.userAgentApplication.loginPopup(graphScopes); promise .then(_ => this.userLoggedIn = true) .catch(error => console.log(`loginPopup error = ${error}`)); return from(promise); } public logout(): void { this.userAgentApplication.logout(); this.userLoggedIn = false; } }
import { Component } from '@angular/core'; import { AuthService } from './core/auth.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Angular and Azure AD'; get userLoggedIn() { return this.authService.userLoggedIn; } constructor(private authService: AuthService) { } login(): void { this.authService.login(); } logout(): void { this.authService.logout(); } }
<div style="text-align:center"> <h1> Welcome to {{ title }}! </h1> </div> <button *ngIf='!userLoggedIn' (click)='login()'>Login</button> <button *ngIf='userLoggedIn' (click)='logout()'>Logout</button>
And when the user is logged in, like this:
Let's invite Bootstrap to the party and the new version of app.component.html...
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">Home</a> <div class="collapse navbar-collapse"> </div> <ul class="nav navbar-nav navbar-right"> <li class="nav-item" *ngIf="!userLoggedIn"> <a class="nav-link" href="#" (click)='login()'>Login</a> </li> <li class="nav-item" *ngIf="userLoggedIn"> <a class="nav-link" href="#" (click)='logout()'>Logout</a> </li> </ul> </nav> <div style="text-align:center"> <h3> {{title}} </h3> </div>
Note: I am a big fan of Automated Testing and from the initial commit, the Angular project contains a set of unit tests that allows me to validate the application quality as it is being developed. Write unit tests and run them through ng test (or npm test) reveals if the application is going well or wrong. For example, I have a unit test asserting that the Logout menu appears when the user is logged in so I don't need to go visual and verify that.
IdToken and Claims
Up to this point, the application has used the OpenID protocol to authenticate the user through the abstraction provided by MSAL. Now it's time to use the OpenID data encapsulated in what is known as the Identity Token. To get a clearer idea of what the Identity Token conveys, let make the following modification to the AuthService login method:public login(): Observable{ const graphScopes = ['user.read']; const promise = this.userAgentApplication.loginPopup(graphScopes); promise .then(rawIdToken => { console.log(rawIdToken); this.userLoggedIn = true; }) .catch(error => console.log(`loginPopup error = ${error}`)); return from(promise); }
Let's copy and paste the Identity Token to the very useful JWT.io to see what's in it:
Identity Token is in the JSON Web Token (JWT) format and it is made of claims or key-value pairs containing information like the token issuer ("iss") and the user name ("name"). A new version of the AuthService uses MSAL.IdToken to decode the Identity Token, get the "name" claim value, and expose it through the username property:
import * as Msal from 'msal'; import { Injectable } from '@angular/core'; import { Observable, from } from 'rxjs'; import { IdToken } from 'msal/lib-commonjs/IdToken'; @Injectable({ providedIn: 'root' }) export class AuthService { private userAgentApplication: Msal.UserAgentApplication; private _userLoggedIn: boolean; get userLoggedIn(): boolean { return this._userLoggedIn; } set userLoggedIn(value) { this._userLoggedIn = value; } private _username: string; get username() { return this._username; } set username(value) { this._username = value; } constructor() { this.userAgentApplication = new Msal.UserAgentApplication( '55f4dfa9-2425-44f0-83a6-d3464b2a7eaa', 'https://login.microsoftonline.com/illyum.onmicrosoft.com', null ); } public login(): Observable{ const graphScopes = ['user.read']; const promise = this.userAgentApplication.loginPopup(graphScopes); promise .then(rawIdToken => { const idToken = new IdToken(rawIdToken); this.username = idToken.name; this.userLoggedIn = true; }) .catch(error => console.log(`loginPopup error = ${error}`)); return from(promise); } public logout(): void { this.userAgentApplication.logout(); this.userLoggedIn = false; } }
import { Component } from '@angular/core'; import { AuthService } from './core/auth.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'Angular and Azure AD'; get userLoggedIn() { return this.authService.userLoggedIn; } get username() { return this.authService.username; } constructor(private authService: AuthService) { } login(): void { this.authService.login(); } logout(): void { this.authService.logout(); } }
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <a class="navbar-brand" href="#">Home</a> <div class="collapse navbar-collapse"> </div> <ul class="nav navbar-nav navbar-right"> <li class="nav-item" *ngIf="!userLoggedIn"> <a class="nav-link" href="#" (click)='login()'>Login</a> </li> <li class="nav-item" *ngIf="userLoggedIn"> <span class="nav-link">Hola {{username}}!</span> </li> <li class="nav-item" *ngIf="userLoggedIn"> <a class="nav-link" href="#" (click)='logout()'>Logout</a> </li> </ul> </nav> <div style="text-align:center"> <h3> {{title}} </h3> </div>
That's it for now. Remember that the code can be downloaded from here (committed as "Better UX"). In my next post I am going to give a 101 intro on Microsoft Graph. See you there...
Looking forward to hear about Microsoft Graph. It looks an interesting topic to explore.
ReplyDelete