This article shows how to use Azure AD with an Angular application implemented using the Microsoft dotnet template and the angular-auth-oidc-client npm package to implement the OpenID Implicit Flow. The Angular app uses bootstrap 4 and Angular CLI.
Code: https://github.com/damienbod/dotnet-template-angular
Setting up Azure AD
Log into https://portal.azure.com and click the Azure Active Directory button
![]()
Click App registrations and then the New application registration
![]()
Add an application name and set the URL to match the application URL. Click the create button.
![]()
Open the new application.
![]()
Click the Manifest button.
![]()
Set the oauth2AllowImplicitFlow to true.
![]()
Click the settings button and add the API Access required permissions as needed.
![]()
Now the Azure AD is ready to go. You will need to add your users which you want to login with and add them as admins if required. For example, I have add damien@damienbod.onmicrosoft.com as an owner.
dotnet Angular template from Microsoft.
Install the latest version and create a new project.
Installation:
https://docs.microsoft.com/en-gb/aspnet/core/spa/index#installation
Docs:
https://docs.microsoft.com/en-gb/aspnet/core/spa/angular?tabs=visual-studio
The dotnet template uses Angular CLI and can be found in the ClientApp folder.
Update all the npm packages including the Angular-CLI, and do a npm install, or use yarn to update the packages.
Add the angular-auth-oidc-client which implements the OIDC Implicit Flow for Angular applications.
{
"name": "dotnet_angular",
"version": "0.0.0",
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --extract-css",
"build": "ng build --extract-css",
"build:ssr": "npm run build -- --app=ssr --output-hashing=media",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular-devkit/core": "0.0.28",
"@angular/animations": "^5.2.1",
"@angular/common": "^5.2.1",
"@angular/compiler": "^5.2.1",
"@angular/core": "^5.2.1",
"@angular/forms": "^5.2.1",
"@angular/http": "^5.2.1",
"@angular/platform-browser": "^5.2.1",
"@angular/platform-browser-dynamic": "^5.2.1",
"@angular/platform-server": "^5.2.1",
"@angular/router": "^5.2.1",
"@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.5",
"angular-auth-oidc-client": "4.0.0",
"aspnet-prerendering": "^3.0.1",
"bootstrap": "^4.0.0",
"core-js": "^2.5.3",
"es6-promise": "^4.2.2",
"rxjs": "^5.5.6",
"zone.js": "^0.8.20"
},
"devDependencies": {
"@angular/cli": "1.6.5",
"@angular/compiler-cli": "^5.2.1",
"@angular/language-service": "^5.2.1",
"@types/jasmine": "~2.8.4",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~9.3.0",
"codelyzer": "^4.1.0",
"jasmine-core": "~2.9.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-cli": "~1.0.1",
"karma-coverage-istanbul-reporter": "^1.3.3",
"karma-jasmine": "~1.1.1",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.2.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "~2.6.2"
}
}
Azure AD does not support CORS, so you have to GET the .well-known/openid-configuration with your tenant and add them to your application as a Json file.
https://login.microsoftonline.com/damienbod.onmicrosoft.com/.well-known/openid-configuration
Do the same for the jwt keys
https://login.microsoftonline.com/common/discovery/keys
Now change the URL in the well-known/openid-configuration json file to use the downloaded version of the keys.
{
"authorization_endpoint": "https://login.microsoftonline.com/a0958f45-195b-4036-9259-de2f7e594db6/oauth2/authorize",
"token_endpoint": "https://login.microsoftonline.com/a0958f45-195b-4036-9259-de2f7e594db6/oauth2/token",
"token_endpoint_auth_methods_supported": [ "client_secret_post", "private_key_jwt", "client_secret_basic" ],
"jwks_uri": "https://localhost:44347/jwks.json",
"response_modes_supported": [ "query", "fragment", "form_post" ],
"subject_types_supported": [ "pairwise" ],
"id_token_signing_alg_values_supported": [ "RS256" ],
"http_logout_supported": true,
"frontchannel_logout_supported": true,
"end_session_endpoint": "https://login.microsoftonline.com/a0958f45-195b-4036-9259-de2f7e594db6/oauth2/logout",
"response_types_supported": [ "code", "id_token", "code id_token", "token id_token", "token" ],
"scopes_supported": [ "openid" ],
"issuer": "https://sts.windows.net/a0958f45-195b-4036-9259-de2f7e594db6/",
"claims_supported": [ "sub", "iss", "cloud_instance_name", "cloud_instance_host_name", "cloud_graph_host_name", "msgraph_host", "aud", "exp", "iat", "auth_time", "acr", "amr", "nonce", "email", "given_name", "family_name", "nickname" ],
"microsoft_multi_refresh_token": true,
"check_session_iframe": "https://login.microsoftonline.com/a0958f45-195b-4036-9259-de2f7e594db6/oauth2/checksession",
"userinfo_endpoint": "https://login.microsoftonline.com/a0958f45-195b-4036-9259-de2f7e594db6/openid/userinfo",
"tenant_region_scope": "NA",
"cloud_instance_name": "microsoftonline.com",
"cloud_graph_host_name": "graph.windows.net",
"msgraph_host": "graph.microsoft.com"
}
This can now be used in the APP_INITIALIZER of the app.module. In the OIDC configuration, set the OpenIDImplicitFlowConfiguration object to match the Azure AD application which was configured before.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, APP_INITIALIZER } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { NavMenuComponent } from './nav-menu/nav-menu.component';
import { HomeComponent } from './home/home.component';
import {
AuthModule,
OidcSecurityService,
OpenIDImplicitFlowConfiguration,
OidcConfigService,
AuthWellKnownEndpoints
} from 'angular-auth-oidc-client';
import { AutoLoginComponent } from './auto-login/auto-login.component';
import { routing } from './app.routes';
import { ForbiddenComponent } from './forbidden/forbidden.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import { ProtectedComponent } from './protected/protected.component';
import { AuthorizationGuard } from './authorization.guard';
import { environment } from '../environments/environment';
export function loadConfig(oidcConfigService: OidcConfigService) {
console.log('APP_INITIALIZER STARTING');
// https://login.microsoftonline.com/damienbod.onmicrosoft.com/.well-known/openid-configuration
// jwt keys: https://login.microsoftonline.com/common/discovery/keys
// Azure AD does not support CORS, so you need to download the OIDC configuration, and use these from the application.
// The jwt keys needs to be configured in the well-known-openid-configuration.json
return () => oidcConfigService.load_using_custom_stsServer('https://localhost:44347/well-known-openid-configuration.json');
}
@NgModule({
declarations: [
AppComponent,
NavMenuComponent,
HomeComponent,
AutoLoginComponent,
ForbiddenComponent,
UnauthorizedComponent,
ProtectedComponent
],
imports: [
BrowserModule.withServerTransition({ appId: 'ng-cli-universal' }),
HttpClientModule,
AuthModule.forRoot(),
FormsModule,
routing,
],
providers: [
OidcSecurityService,
OidcConfigService,
{
provide: APP_INITIALIZER,
useFactory: loadConfig,
deps: [OidcConfigService],
multi: true
},
AuthorizationGuard
],
bootstrap: [AppComponent]
})
export class AppModule {
constructor(
private oidcSecurityService: OidcSecurityService,
private oidcConfigService: OidcConfigService,
) {
this.oidcConfigService.onConfigurationLoaded.subscribe(() => {
const openIDImplicitFlowConfiguration = new OpenIDImplicitFlowConfiguration();
openIDImplicitFlowConfiguration.stsServer = 'https://login.microsoftonline.com/damienbod.onmicrosoft.com';
openIDImplicitFlowConfiguration.redirect_url = 'https://localhost:44347';
openIDImplicitFlowConfiguration.client_id = 'fd87184a-00c2-4aee-bc72-c7c1dd468e8f';
openIDImplicitFlowConfiguration.response_type = 'id_token token';
openIDImplicitFlowConfiguration.scope = 'openid profile email ';
openIDImplicitFlowConfiguration.post_logout_redirect_uri = 'https://localhost:44347';
openIDImplicitFlowConfiguration.post_login_route = '/home';
openIDImplicitFlowConfiguration.forbidden_route = '/home';
openIDImplicitFlowConfiguration.unauthorized_route = '/home';
openIDImplicitFlowConfiguration.auto_userinfo = false;
openIDImplicitFlowConfiguration.log_console_warning_active = true;
openIDImplicitFlowConfiguration.log_console_debug_active = !environment.production;
openIDImplicitFlowConfiguration.max_id_token_iat_offset_allowed_in_seconds = 600;
const authWellKnownEndpoints = new AuthWellKnownEndpoints();
authWellKnownEndpoints.setWellKnownEndpoints(this.oidcConfigService.wellKnownEndpoints);
this.oidcSecurityService.setupModule(openIDImplicitFlowConfiguration, authWellKnownEndpoints);
this.oidcSecurityService.setCustomRequestParameters({ 'prompt': 'admin_consent', 'resource': 'https://graph.windows.net'});
});
console.log('APP STARTING');
}
}
Now an Auth Guard can be added to protect the protected routes.
import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { map } from 'rxjs/operators';
import { OidcSecurityService } from 'angular-auth-oidc-client';
@Injectable()
export class AuthorizationGuard implements CanActivate {
constructor(
private router: Router,
private oidcSecurityService: OidcSecurityService
) { }
public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
console.log(route + '' + state);
console.log('AuthorizationGuard, canActivate');
return this.oidcSecurityService.getIsAuthorized().pipe(
map((isAuthorized: boolean) => {
console.log('AuthorizationGuard, canActivate isAuthorized: ' + isAuthorized);
if (isAuthorized) {
return true;
}
this.router.navigate(['/unauthorized']);
return false;
})
);
}
}
You can then add an app.routes and protect what you require.
import { Routes, RouterModule } from '@angular/router';
import { ForbiddenComponent } from './forbidden/forbidden.component';
import { HomeComponent } from './home/home.component';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import { AutoLoginComponent } from './auto-login/auto-login.component';
import { ProtectedComponent } from './protected/protected.component';
import { AuthorizationGuard } from './authorization.guard';
const appRoutes: Routes = [
{ path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'autologin', component: AutoLoginComponent },
{ path: 'forbidden', component: ForbiddenComponent },
{ path: 'unauthorized', component: UnauthorizedComponent },
{ path: 'protected', component: ProtectedComponent, canActivate: [AuthorizationGuard] }
];
export const routing = RouterModule.forRoot(appRoutes);
The NavMenuComponent component is then updated to add the login, logout.
import { Component } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { OidcSecurityService } from 'angular-auth-oidc-client';
@Component({
selector: 'app-nav-menu',
templateUrl: './nav-menu.component.html',
styleUrls: ['./nav-menu.component.css']
})
export class NavMenuComponent {
isExpanded = false;
isAuthorizedSubscription: Subscription;
isAuthorized: boolean;
constructor(public oidcSecurityService: OidcSecurityService) {
}
ngOnInit() {
this.isAuthorizedSubscription = this.oidcSecurityService.getIsAuthorized().subscribe(
(isAuthorized: boolean) => {
this.isAuthorized = isAuthorized;
});
}
ngOnDestroy(): void {
this.isAuthorizedSubscription.unsubscribe();
}
login() {
this.oidcSecurityService.authorize();
}
refreshSession() {
this.oidcSecurityService.authorize();
}
logout() {
this.oidcSecurityService.logoff();
}
collapse() {
this.isExpanded = false;
}
toggle() {
this.isExpanded = !this.isExpanded;
}
}
Start the application and click login
![]()
Enter your user which is defined in Azure AD
![]()
Consent page:
![]()
And you are redircted back to the application.
![]()
Notes:
If you don’t use any Microsoft API use the id_token flow, and not the id_token token flow. The resource of the API needs to be defined in both the request and also the Azure AD app definitions.
Links:
https://docs.microsoft.com/en-gb/aspnet/core/spa/angular?tabs=visual-studio
https://portal.azure.com