From ea853ddb98494f42f72c123800d7e758fee38510 Mon Sep 17 00:00:00 2001 From: Sahil Malik Date: Wed, 5 Oct 2016 23:01:25 -0400 Subject: [PATCH] docs(cb-azure-ad-auth): Add new cookbook --- .../_examples/cb-azure-ad-auth/e2e-spec.ts | 15 ++ .../_examples/cb-azure-ad-auth/ts/.gitignore | 0 .../cb-azure-ad-auth/ts/app/app.component.ts | 9 + .../cb-azure-ad-auth/ts/app/app.module.ts | 40 ++++ .../cb-azure-ad-auth/ts/app/app.routing.ts | 13 ++ .../ts/app/authSettings.config.ts | 8 + .../ts/app/home/home.component.ts | 10 + .../ts/app/login/login.component.ts | 22 ++ .../_examples/cb-azure-ad-auth/ts/app/main.ts | 3 + .../ts/app/ngAuth/AuthenticatedHttpService.ts | 33 +++ .../ts/app/ngAuth/JwtHelper.ts | 30 +++ .../authenticators/AzureADAuthService.ts | 98 +++++++++ .../authenticators/AzureADServiceConstants.ts | 13 ++ .../ts/app/ngAuth/renewToken.html | 58 +++++ .../ts/app/status/status.component.ts | 43 ++++ .../cb-azure-ad-auth/ts/example-config.json | 0 .../_examples/cb-azure-ad-auth/ts/index.html | 23 ++ .../_examples/cb-azure-ad-auth/ts/plnkr.json | 9 + public/docs/ts/latest/cookbook/_data.json | 18 +- .../ts/latest/cookbook/azure-ad-auth.jade | 201 ++++++++++++++++++ 20 files changed, 633 insertions(+), 13 deletions(-) create mode 100644 public/docs/_examples/cb-azure-ad-auth/e2e-spec.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/.gitignore create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/app.component.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/app.module.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/app.routing.ts create mode 100755 public/docs/_examples/cb-azure-ad-auth/ts/app/authSettings.config.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/home/home.component.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/login/login.component.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/main.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/AuthenticatedHttpService.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/JwtHelper.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADServiceConstants.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/renewToken.html create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/app/status/status.component.ts create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/example-config.json create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/index.html create mode 100644 public/docs/_examples/cb-azure-ad-auth/ts/plnkr.json create mode 100644 public/docs/ts/latest/cookbook/azure-ad-auth.jade diff --git a/public/docs/_examples/cb-azure-ad-auth/e2e-spec.ts b/public/docs/_examples/cb-azure-ad-auth/e2e-spec.ts new file mode 100644 index 0000000000..8df56bc768 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/e2e-spec.ts @@ -0,0 +1,15 @@ +/// +'use strict'; +describe('Azure AD Auth E2E tests', function () { + + let expectedMsg = 'Simple app demonstrates'; + + beforeEach(function () { + browser.get(''); + }); + + it(`should display: ${expectedMsg}`, function () { + expect(element(by.css('p')).getText()).toContain(expectedMsg); + }); + +}); diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/.gitignore b/public/docs/_examples/cb-azure-ad-auth/ts/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/app.component.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.component.ts new file mode 100644 index 0000000000..cc38f89bf4 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; +@Component({ + selector: 'app', + template: ` + About | Login | Status
+ ` +}) + +export class AppComponent { } \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/app.module.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.module.ts new file mode 100644 index 0000000000..de9c44f3a4 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.module.ts @@ -0,0 +1,40 @@ +// #docregion +import { NgModule } from '@angular/core'; +import { HttpModule } from '@angular/http'; +import { BrowserModule } from '@angular/platform-browser'; +import { routing } from './app.routing'; +import { AppComponent } from './app.component'; + +import { HomeComponent } from './home/home.component'; +import { LoginComponent } from './login/login.component'; +import { StatusComponent } from './status/status.component'; + +import { AzureADServiceConstants } from './ngAuth/authenticators/AzureADServiceConstants'; +import { AzureADAuthService } from './ngAuth/authenticators/AzureADAuthService'; +import { AuthenticatedHttpService } from './ngAuth/AuthenticatedHttpService'; + +import { serviceConstants } from './authsettings.config'; + +let authenticator = new AzureADAuthService(serviceConstants); + +@NgModule({ + providers: [ + AuthenticatedHttpService, + { + provide: AzureADAuthService, + useValue: authenticator + }], + imports: [ + routing, + BrowserModule, + HttpModule + ], + declarations: [ + AppComponent, + HomeComponent, + LoginComponent, + StatusComponent + ], + bootstrap: [AppComponent] +}) +export class AppModule { } \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/app.routing.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.routing.ts new file mode 100644 index 0000000000..00e9dd769e --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.routing.ts @@ -0,0 +1,13 @@ +// #docregion +import { ModuleWithProviders } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; +import { LoginComponent } from './login/login.component'; +import {HomeComponent} from './home/home.component'; +import { StatusComponent } from './status/status.component'; + +export const routes: Routes = [ + { path: '', component: HomeComponent }, + { path: 'login', component: LoginComponent }, + { path: 'status', component: StatusComponent }, +]; +export const routing: ModuleWithProviders = RouterModule.forRoot(routes); \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/authSettings.config.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/authSettings.config.ts new file mode 100755 index 0000000000..deda0f8d31 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/authSettings.config.ts @@ -0,0 +1,8 @@ +import {AzureADServiceConstants} from './ngAuth/authenticators/AzureADServiceConstants'; + +export const serviceConstants: AzureADServiceConstants = new AzureADServiceConstants( + "e176a5f1-596e-44c9-91cb-46de9bb96ee1", + "angularad.onmicrosoft.com", + "http://localhost:3000/status", + "https://graph.windows.net" +); \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/home/home.component.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/home/home.component.ts new file mode 100644 index 0000000000..7b55c739cd --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/home/home.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; + +@Component({ + template:` + Simple app demonstrates logging into AzureAD and running a command against the Azure AD graph.
+ Click the login tab to login, and status tab to view your login status. + ` + }) +export class HomeComponent { } \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/login/login.component.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/login/login.component.ts new file mode 100644 index 0000000000..0fba10421f --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/login/login.component.ts @@ -0,0 +1,22 @@ +// #docregion +import { Component, Inject } from '@angular/core'; +import { Router } from '@angular/router'; + +import {AzureADAuthService} from '../ngAuth/authenticators/AzureADAuthService'; + +@Component({ + template: ` + ` +}) + +export class LoginComponent { + constructor( + @Inject(AzureADAuthService) private _authService: AzureADAuthService, + private _router: Router) { } + + logIn() { + this._authService.logIn("/"); + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/main.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/main.ts new file mode 100644 index 0000000000..6801c0441c --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/main.ts @@ -0,0 +1,3 @@ +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import {AppModule} from './app.module'; +platformBrowserDynamic().bootstrapModule(AppModule); \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/AuthenticatedHttpService.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/AuthenticatedHttpService.ts new file mode 100644 index 0000000000..8602cd0d5f --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/AuthenticatedHttpService.ts @@ -0,0 +1,33 @@ +// #docregion +import { Injectable, Inject } from '@angular/core'; +import { Http, Headers, Response } from '@angular/http'; +import { AzureADAuthService } from './authenticators/AzureADAuthService'; +import { Observable, Subscriber } from 'rxjs'; + +@Injectable() +export class AuthenticatedHttpService { + private _authenticator: AzureADAuthService; + private _http: Http; + constructor( @Inject(Http) http: Http, @Inject(AzureADAuthService) authenticator: AzureADAuthService) { + this._authenticator = authenticator; + this._http = http; + } + + createAuthorizationHeader(headers: Headers) { + headers.append('Authorization', 'Bearer ' + this._authenticator.getAccessToken()); + } + + get(url: string) { + let headers = new Headers(); + this.createAuthorizationHeader(headers); + return this._http.get(url, { headers: headers }); + } + + post(url: string, data: any) { + let headers = new Headers(); + this.createAuthorizationHeader(headers); + return this._http.post(url, data, { + headers: headers, + }); + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/JwtHelper.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/JwtHelper.ts new file mode 100644 index 0000000000..f8f4a595ed --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/JwtHelper.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class JwtHelper { + private urlBase64Decode(str: string) { + var output = str.replace(/-/g, '+').replace(/_/g, '/'); + switch (output.length % 4) { + case 0: { break; } + case 2: { output += '=='; break; } + case 3: { output += '='; break; } + default: { + throw 'Illegal base64url string!'; + } + } + return decodeURIComponent((window).escape(window.atob(output))); + } + + public decodeToken(token: string = "") { + if (token === null || token === "") return {"upn": ""}; + var parts = token.split('.'); + if (parts.length !== 3) { + throw new Error('JWT must have 3 parts'); + } + var decoded = this.urlBase64Decode(parts[1]); + if (!decoded) { + throw new Error('Cannot decode the token'); + } + return JSON.parse(decoded); + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts new file mode 100644 index 0000000000..9e5a21fc7e --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts @@ -0,0 +1,98 @@ +// #docregion +import { Injectable } from '@angular/core'; +import { Http, Headers } from '@angular/http'; +import { JwtHelper } from '../JwtHelper'; + +import { AzureADServiceConstants } from "./AzureADServiceConstants"; + +@Injectable() +export class AzureADAuthService { + public isUserAuthenticated(): boolean { + let access_token = this.getAccessToken(); + return access_token != null; + } + + public getAccessToken(): string { + return window.localStorage.getItem("access_token"); + } + + public getUserName(): string { + var jwtHelper = new JwtHelper(); + var parsedToken = jwtHelper.decodeToken(this.getAccessToken()); + + var expiryTime = new Date(parsedToken.exp * 1000); + var now = new Date(); + if (now > expiryTime) this.logOut(); + + return parsedToken.upn; + } + +// #docregion login + public logIn(state = "/") { + window.location.href = "https://login.microsoftonline.com/" + this._serviceConstants.tenantID + + "/oauth2/authorize?response_type=id_token&client_id=" + this._serviceConstants.clientID + + "&redirect_uri=" + encodeURIComponent(window.location.href) + + "&state=" + state + "&nonce=SomeNonce"; + } +// #enddocregion login + + public logOut(state = "/") { + window.localStorage.removeItem("id_token"); + window.localStorage.removeItem("access_token"); + window.location.href = state; + } + private parseQueryString = function (url: string) { + var params = {}; + var queryString = ""; + if (url.search("#") != -1) { + queryString = url.substring(url.search("#") + 1); + + } else { + queryString = url.substring(url.indexOf("?") + 1); + } + var a = queryString.split('&'); + for (var i = 0; i < a.length; i++) { + var b = a[i].split('='); + params[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || ''); + } + return params; + } + + private params = this.parseQueryString(location.hash); +// #docregion ctor + constructor(private _serviceConstants: AzureADServiceConstants) { + // do we have an access token, if so add the iframe renewer + if (window.localStorage.getItem("access_token")) { + var iframe = document.createElement('iframe'); + iframe.style.display = "none"; + iframe.src = "/app/ngAuth/renewToken.html?tenantID=" + + encodeURIComponent(this._serviceConstants.tenantID) + + "&clientID=" + encodeURIComponent(this._serviceConstants.clientID) + + "&resource=" + encodeURIComponent(this._serviceConstants.graphResource); + window.onload = function () { + document.body.appendChild(iframe); + } + } + if (this.params["id_token"] != null) { + window.localStorage.setItem("id_token", this.params["id_token"]); + // redirect to get access token here.. + window.location.href = "https://login.microsoftonline.com/" + this._serviceConstants.tenantID + + "/oauth2/authorize?response_type=token&client_id=" + this._serviceConstants.clientID + + "&resource=" + this._serviceConstants.graphResource + + "&redirect_uri=" + encodeURIComponent(window.location.href) + + "&prompt=none&state=" + this.params["state"] + "&nonce=SomeNonce"; + } + else if (this.params["access_token"] != null) { + window.localStorage.setItem("access_token", this.params["access_token"]); + // redirect to the original call URl here. + window.location.href = this.params["state"]; + } + } +// #enddocregion ctor +} + +function error(err: any) { + console.error(JSON.stringify(err, null, 4)); +} + +// #enddocregion \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADServiceConstants.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADServiceConstants.ts new file mode 100644 index 0000000000..a7fea333f7 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADServiceConstants.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class AzureADServiceConstants { + constructor( + public clientID:string, + public tenantID:string, + public redirectURL:string, + public backendUrl:string, + public graphResource = "https://graph.windows.net", + public isCordova = false, + public isElectron = false) {} +} \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/renewToken.html b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/renewToken.html new file mode 100644 index 0000000000..de71df64bf --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/renewToken.html @@ -0,0 +1,58 @@ + + + + Renew Token + + + + + + + + + \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/app/status/status.component.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/status/status.component.ts new file mode 100644 index 0000000000..a6fdda9666 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/status/status.component.ts @@ -0,0 +1,43 @@ +// #docregion +import { Component, Inject } from '@angular/core'; +import { Router } from '@angular/router'; + +import { AuthenticatedHttpService } from '../ngAuth/AuthenticatedHttpService'; +import { AzureADAuthService } from '../ngAuth/authenticators/AzureADAuthService'; + + +@Component({ + template: ` +
+ userName: {{this._authService.getUserName()}} +
+ +
+ +
{{_userData | json}}
+
+
+ User is not signed in. +
+ ` +}) + +export class StatusComponent { + private _userData: Object = {"intialValue":"Data will show here once you press RunCommand"}; + constructor( @Inject(AzureADAuthService) private _authService: AzureADAuthService, private _authenticatedHttpService: AuthenticatedHttpService) { } + + logOut() { + this._authService.logOut("/"); + } + + runCommand() { + this._authenticatedHttpService.get("https://graph.windows.net/me?api-version=1.6").subscribe((results => { + this._userData = results.json(); + })); + // this._authenticatedHttpService.get("https://graph.microsoft.com/v1.0/me/drive/recent").subscribe((results => { + // this._files = results.json().value; + // })); + } +} \ No newline at end of file diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/example-config.json b/public/docs/_examples/cb-azure-ad-auth/ts/example-config.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/index.html b/public/docs/_examples/cb-azure-ad-auth/ts/index.html new file mode 100644 index 0000000000..836a629352 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/index.html @@ -0,0 +1,23 @@ + + + + Angular 2 AzureAD/Office365 Auth + + + + + + + + + + + + + + + Loading... + + diff --git a/public/docs/_examples/cb-azure-ad-auth/ts/plnkr.json b/public/docs/_examples/cb-azure-ad-auth/ts/plnkr.json new file mode 100644 index 0000000000..a4449b444f --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/plnkr.json @@ -0,0 +1,9 @@ +{ + "description": "QuickStart", + "files": [ + "!**/*.d.ts", + "!**/*.js", + "!**/*.[1].*" + ], + "tags": ["quickstart"] +} \ No newline at end of file diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json index 7a45a91e77..11ce3bc244 100644 --- a/public/docs/ts/latest/cookbook/_data.json +++ b/public/docs/ts/latest/cookbook/_data.json @@ -4,65 +4,57 @@ "navTitle": "Overview", "description": "A collection of recipes for common Angular application scenarios" }, - "aot-compiler": { "title": "Ahead-of-Time Compilation", "intro": "Learn how to use Ahead-of-time compilation" }, - "a1-a2-quick-reference": { "title": "Angular 1 to 2 Quick Reference", "navTitle": "Angular 1 to 2 Quick Ref", "intro": "Learn how Angular 1 concepts and techniques map to Angular 2" }, - "ngmodule-faq": { "title": "Angular Module FAQs", "intro": "Answers to frequently asked questions about @NgModule" }, - + "azure-ad-auth": { + "title": "AzureAD Authentication", + "intro": "Explains how to use Angular to authenticate with AzureAD" + }, "component-communication": { "title": "Component Interaction", "intro": "Share information between different directives and components" }, - "component-relative-paths": { "title": "Component-relative Paths", "intro": "Use relative URLs for component templates and styles." }, - "dependency-injection": { "title": "Dependency Injection", "intro": "Techniques for Dependency Injection" }, - "dynamic-form": { "title": "Dynamic Forms", "intro": "Render dynamic forms with FormGroup" }, - "form-validation": { "title": "Form Validation", "intro": "Validate user's form entries" }, - "i18n": { "title": "Internationalization (i18n)", "intro": "Translate the app's template text into multiple languages" }, - "set-document-title": { "title": "Set the Document Title", "intro": "Setting the document or window title using the Title service." }, - "ts-to-js": { "title": "TypeScript to JavaScript", "intro": "Convert Angular TypeScript examples into ES5 JavaScript" }, - "visual-studio-2015": { "title": "Visual Studio 2015 QuickStart", "intro": "Use Visual Studio 2015 with the QuickStart files" } -} +} \ No newline at end of file diff --git a/public/docs/ts/latest/cookbook/azure-ad-auth.jade b/public/docs/ts/latest/cookbook/azure-ad-auth.jade new file mode 100644 index 0000000000..da29cec4b6 --- /dev/null +++ b/public/docs/ts/latest/cookbook/azure-ad-auth.jade @@ -0,0 +1,201 @@ +include ../_util-fns + +:marked + This cookbook describes how to write a simple Angular app + that allows you to authenticate against AzureAD and run a simple REST command. + The same principles can be extended to work against Office 365 APIs or any API protected by AzureAD. +a#toc +:marked + ## Table of Contents + +a#overview +.l-main-section +:marked + ## Overview + + Azure Active Directory (Azure AD) is Microsoft’s multi-tenant + cloud based directory and identity management service. + It is a cloud based service that lets you work with standards + based authentication providers, and offers standards based + authentication to both apps and users. + + ## Registering your app + + While covering the basics of AzureAD is beyond the scope of + [this article](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/), + I would encourage you to read this article on basics of how to register an app in Azure AD. + Specifically, you need to register, + 1. A native app your organization is developing + 2. This native app should have a redirect URI that you control, usually your app itself. For this example, we will use http://localhost:3000/status. + 3. Enable OAuth2 implicit flow ([how?](https://azure.microsoft.com/en-us/documentation/articles/active-directory-application-manifest/)) + 4. Allow this app to call the Azure AD graph + + ## OAuth2 implicit flow + The scenario we wish to use here is native application to web api. Azure AD graph APIs are Web APIs. + And native applications can encompass truly native applications, such as iOS, Android, + or Windows apps written in Swift, ObjectiveC, Java, C# etc. + Or cordova applications that take advantage of plugins written in such native languages. + Or, pure JavaScript applications, running in NodeJS, or a browser. + + In this article, we will use a JavaScript application written in Angular running in the browser. + + Could browser based apps use other authentication protocols? Certainly! + But OAuth2 implicit flow is the most logical choice. + Before we go much further, lets understand some basics about OAuth2 implicit flow. + + There are the following main steps involved in our authentication process. + Note that these steps are shown here with some details omitted for simplification reasons. + For instance, every request has a state, and a nonce, which I have not shown here. + + ### Step1, is to request an authorization code. + This is a simple HTTPS redirect to https://login.microsoftonline.com/{tenant}/oauth2/authorize + + Amongst the query strings is also the redirect URI and client ID (the ID of the app). + The purpose here is that you wish to inform AzureAD that your app is “listening” for the tokens at a certain URL. + AzureAD will not send back the token, unless such as URL is pre-registered for the requested app. + The v2 app model is similar with one big difference – that you can request for specific resources at this point, + or after authentication. So you can request for access when you need – not during registration of the app, and not just during login. + + ### Step2 – receive the authorization code. + + At this point, the user will need to login. + If the app is not registered in your tenancy, the user will be asked to grant access to the app. + As long as the user has the access that the app is asking for, and the administrator has not disabled + 3rd party registrations globally, the user can choose to trust the app. + The user will not be asked to trust the app if the app already has permissions to the Azure AD tenancy. + + Once the user is authenticated, and the app is trusted, AzureAD will reply back to the pre-registered redirect URI with an authorization code. + + The request looks like this, + +code-example(format='.'). + GET HTTP/1.1 302 Found + Location: http://{redirecturi}?code={authorization_code} + +:marked + ### Step3 - request access token + The authorization code, is like a refresh token – except it’s much shorter duration. + But it works very similar to a refresh token. Using this authorization code, you can request for access tokens for specific resources. + An example resource could be mail, or files, or calendar, or contacts etc. + In our example, the resource is simply AzureAD. + A good place to get started with the various resources and possibilities is [here](https://graph.microsoft.io/en-us/docs). + Using the authorization code, you can request for access tokens. + And you do so, using a request similar to as shown below, + +code-example(format='.'). + POST common/oauth2/token + Host: https://login.microsoftonline.com + Content-Type: application/x-www-form-urlencoded + grant_type=authorization_code& + client_id={guid}& + code={authorization_code}& + redirect_uri={your app redirect uri}& + resource={resource for access token} + +:marked + The reply to the above call is the access token. + + ### Step4 - make authenticated call. + And once you receive such an access token, you can make a call to the appropriate resource, with an authorization header as below, +code-example(format='.'). + authorization: Bearer access_token +:marked + Great! So with these basics behind us, lets write up a simple app that makes use of all this. + + ## Structure of the app + The app is intentionally very simple. It makes use two injected services, + 1. `AuthenticatedHttpService` – which allows you to make simple get/post requests, + but instead of making a plain HTTP call, it picks the latest available access_token from local storage, + and puts it in the authorization header. + This has been intentionally written generically, since the same service could be used with say, google API, or any other OAuth2 protected resource. + 2. `AzureADAuthService`: This service is responsible for putting the access token, along with the authentication dance that I described above. + + We pass both of these services into the app module as shown below, + ++makeExample('cb-azure-ad-auth/ts/app/app.module.ts', null, 'app.module.ts') + +:marked + As is evident, from the app.module.ts, we are passing in an instance of AzureADServiceConstants to the constructor of AzureADService. + This is information the AzureADService needs in order to provide authentication for you. This includes, + 1. The identity of the app (clientID) + 2. The tenant + 3. Redirect URL + 4. The API, for which you are requesting an access token. + + Now, this is a very simple example implementation. + In the real world, where your app may seamlessly call multiple APIs, you probably want to reuse the authorization code, for multiple APIs. + But I am keeping things exemplary and simple here. + + Additionally, our app uses routes, which can be seen in the app.routing.ts file + ++makeExample('cb-azure-ad-auth/ts/app/app.routing.ts', null, 'app.routing.ts') + +:marked + You could also use guards to protect entire routes here. But we are not doing that in this article. + With these in place, the app.component.ts simply is a collection of router links pointing to these routes, alongwith a router-outlet. + + 1. The login.component.ts shows a login UI, + 2. The status.component.ts shows whether or not the user is currently logged in, and allows you to make a simple authenticated call. + 3. The home.component.ts just shows a simple text message explaining what the app does. + + Lets look at the login component, and the status component in depth next. + ++makeExample('cb-azure-ad-auth/ts/app/login/login.component.ts', null, 'login.component.ts') +:marked + The login.component.ts simply calls the authentication service’s login method. + It is also sending a state in – our authentication service is wired so that after authentication it returns you to whatever URL you pass in as state. + ++makeExample('cb-azure-ad-auth/ts/app/status/status.component.ts', null, 'status.component.ts') +:marked + As can be seen from the template, we are showing a different UX depending upon the user’s login status, which the auth service makes it easy to get. + If the user is not logged in, we show them an appropriate message. + If the user is logged in, we allow them to either logout, or run an authenticated command. + Logout simply calls authService.logOut. The Run Command method calls the REST endpoint using the authenticated http service instance. + + With this place, now lets look at the more interesting bits, the authenticated http service, and our authentication service. + ++makeExample('cb-azure-ad-auth/ts/app/ngAuth/AuthenticatedHttpService.ts', null, 'AuthenticatedHttpService.ts') +:marked + The AuthenticatedHttpService is a really simple, standard Angular service. + It wraps get and post HTTP commands, and all it does, it stick the access token in the authorization header of those commands. That’s it! + + With all this behind us, now lets look at the meat of the authentication code, which is the AzureADAuthService.ts. + + ## AzureADAuthService + + And this is where things get interesting. This service has a constructor that accepts service constants. The constructor looks like as shown below, + ++makeExample('cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts', 'ctor', 'AzureADAuthService constructor') +:marked + If you observe closely, this contructor is parsing the querystring, and is trying to decipher, if + 1. We already have an access token – if we do, then create a hidden Iframe to renew the access token. + Remember, the access token is good for only a short duration. + So before it expires, you should silently renew it, and we achieve this with a simple hidden Iframe + that holds some simple javascript code that does a refresh of the access token, 5 minutes before the access token expires. + For sake of brevity, I am not showing that code here, but you can grab the code in entirety in the included code example. + 2. If we do not have an access token, then we check to see if the query string contains an id_token – this is your authorization code. + If we do get an authorization code, we do a redirect to request an access token. + 3. And if the querystring does contain an access token, we save it to local storage. + + With this in place, the login method is simply a call to the authentication endpoint, as shown below, + ++makeExample('cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts', 'login', 'AzureADAuthService login') +:marked + And the getUserName method is a matter of parsing the access token, the getAccessToken method is a + matter of reading it from the local storage variable, and the isUserAuthenticated is checking for the presence and validity of the access token. + The entire source code for the AzureADAuthService.ts is shown as below, ++makeExample('cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts', null, 'AzureADAuthService.ts') +:marked + With this much in place, you are now ready to authenticate against AzureAD, + and call protected APIs such as Azure AD Graph, Office 365 APIs and Microsoft Graph, or your own custom APIs registered in Azure AD. + You can see a full running example in the associated code for this article. + + ## Summary + This article demonstrated how you can write a simple application to authenticate against AzureAD and + call AzureAD protected APIs such as the Microsoft Graph. There are many further possible enhancements here. + For instance, your authentication could include claims, which could light up different parts of your application. + Your access token may contain roles, which could activate different guards in your routing logic. + You could enhance this code sample to support multiple resources. Or you could enhance it to support multiple authentication types. + For instance, wouldn’t it be amazing to write an app that authenticates against both google and Microsoft identities, + and reads and writes mails or contacts from both? There are numerous possibilities and enhancements possible. + But I hope this article got you started on a simple authentication example against AzureAD identities. \ No newline at end of file