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..5df8a1e836 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/.gitignore @@ -0,0 +1 @@ +authSettings.config.ts \ No newline at end of file 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..268ec4c49f --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.module.ts @@ -0,0 +1,39 @@ +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..22693909b6 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/app.routing.ts @@ -0,0 +1,12 @@ +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/home/home.component.ts b/public/docs/_examples/cb-azure-ad-auth/ts/app/home/home.component.ts new file mode 100644 index 0000000000..d317ce8bc4 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/home/home.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; + +@Component({ + template:'

Simple app demonstrates logging into AzureAD/Office365 and running a command against the MS graph

' +}) +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..150fde24f9 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/login/login.component.ts @@ -0,0 +1,21 @@ +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..5bcb744c5c --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/AuthenticatedHttpService.ts @@ -0,0 +1,32 @@ +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..4d386cc0a0 --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/authenticators/AzureADAuthService.ts @@ -0,0 +1,94 @@ +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; + } + + 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"; + } + + 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); + + 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"]; + } + } +} + +function error(err: any) { + console.error(JSON.stringify(err, null, 4)); +} \ 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..04a2e3fa22 --- /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.microsoft.com", + 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..c9c92791ea --- /dev/null +++ b/public/docs/_examples/cb-azure-ad-auth/ts/app/status/status.component.ts @@ -0,0 +1,44 @@ +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()}} +
+ +
+ +
    +
  • + Name: {{file.remoteItem.name}} + Size: {{file.size}} +
  • +
+
+
+ User is not signed in. +
+ ` +}) + +export class StatusComponent { + private _files: any[] = []; + constructor( @Inject(AzureADAuthService) private _authService: AzureADAuthService, private _authenticatedHttpService: AuthenticatedHttpService) { } + + logOut() { + this._authService.logOut("/"); + } + + runCommand() { + 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..2c56cc3c33 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 and Office365" + }, "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..05ca93eab1 --- /dev/null +++ b/public/docs/ts/latest/cookbook/azure-ad-auth.jade @@ -0,0 +1,13 @@ +include ../_util-fns + +:marked + This cookbook describes how to authenticate against AzureAD and Office365 using Angular + +a#toc +:marked + ## Table of Contents + +a#overview +.l-main-section +:marked + ## Overview \ No newline at end of file