Skip to content
This repository was archived by the owner on Dec 4, 2017. It is now read-only.

[WIP] docs(cb-azure-ad-auth): Add new authentication cookbook #2552

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions public/docs/_examples/cb-azure-ad-auth/e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference path='../_protractor/e2e.d.ts' />
'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);
});

});
1 change: 1 addition & 0 deletions public/docs/_examples/cb-azure-ad-auth/ts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
authSettings.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Component } from '@angular/core';
@Component({
selector: 'app',
template: `
<a [routerLink]="['']">About</a> | <a [routerLink]="['login']">Login</a> | <a [routerLink]="['status']">Status</a> <br/>
<router-outlet></router-outlet>`
})

export class AppComponent { }
39 changes: 39 additions & 0 deletions public/docs/_examples/cb-azure-ad-auth/ts/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -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 { }
12 changes: 12 additions & 0 deletions public/docs/_examples/cb-azure-ad-auth/ts/app/app.routing.ts
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Component } from '@angular/core';
import { Inject, Injectable } from '@angular/core';

@Component({
template:'<p>Simple app demonstrates logging into AzureAD/Office365 and running a command against the MS graph</p>'
})
export class HomeComponent { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component, Inject } from '@angular/core';
import { Router } from '@angular/router';

import {AzureADAuthService} from '../ngAuth/authenticators/AzureADAuthService';

@Component({
template: `
<button (click)="logIn()">
Sign In
</button>`
})

export class LoginComponent {
constructor(
@Inject(AzureADAuthService) private _authService: AzureADAuthService,
private _router: Router) { }

logIn() {
this._authService.logIn("/");
}
}
3 changes: 3 additions & 0 deletions public/docs/_examples/cb-azure-ad-auth/ts/app/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
Original file line number Diff line number Diff line change
@@ -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,
});
}
}
30 changes: 30 additions & 0 deletions public/docs/_examples/cb-azure-ad-auth/ts/app/ngAuth/JwtHelper.ts
Original file line number Diff line number Diff line change
@@ -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((<any>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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
Original file line number Diff line number Diff line change
@@ -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) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<html>

<head>
<title>Renew Token</title>
<script>
// jwt_decode from https://raw.githubusercontent.com/auth0/jwt-decode/master/build/jwt-decode.min.js
!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b,c){function d(a){this.message=a}function e(a){var b=String(a).replace(/=+$/,"");if(b.length%4==1)throw new d("'atob' failed: The string to be decoded is not correctly encoded.");for(var c,e,g=0,h=0,i="";e=b.charAt(h++);~e&&(c=g%4?64*c+e:e,g++%4)?i+=String.fromCharCode(255&c>>(-2*g&6)):0)e=f.indexOf(e);return i}var f="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";d.prototype=new Error,d.prototype.name="InvalidCharacterError",b.exports="undefined"!=typeof window&&window.atob||e},{}],2:[function(a,b,c){function d(a){return decodeURIComponent(e(a).replace(/(.)/g,function(a,b){var c=b.charCodeAt(0).toString(16).toUpperCase();return c.length<2&&(c="0"+c),"%"+c}))}var e=a("./atob");b.exports=function(a){var b=a.replace(/-/g,"+").replace(/_/g,"/");switch(b.length%4){case 0:break;case 2:b+="==";break;case 3:b+="=";break;default:throw"Illegal base64url string!"}try{return d(b)}catch(c){return e(b)}}},{"./atob":1}],3:[function(a,b,c){"use strict";var d=a("./base64_url_decode");b.exports=function(a){if(!a)throw new Error("Invalid token specified");return JSON.parse(d(a.split(".")[1]))}},{"./base64_url_decode":2}],4:[function(a,b,c){var d="undefined"!=typeof self?self:"undefined"!=typeof window?window:{},e=a("./lib/index");"function"==typeof d.window.define&&d.window.define.amd?d.window.define("jwt_decode",function(){return e}):d.window&&(d.window.jwt_decode=e)},{"./lib/index":3}]},{},[4]);
</script>

<script>
'use strict';
var parsedToken = jwt_decode(window.localStorage.getItem("access_token"));
var timeoutMiliSeconds = (parsedToken.exp*1000 - 300000) - (new Date().getTime());
// parse QueryString
var params = {};
var queryString = "" ;
if (window.location.href.search("#") != -1) {
queryString = window.location.href.substring(window.location.href.search("#") + 1);

} else {
queryString = window.location.search.substring(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] || '');
}

if (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/" + params["tenantID"] +
"/oauth2/authorize?response_type=token&client_id=" + params["clientID"] +
"&resource=" + params["resource"] +
"&redirect_uri=" + encodeURIComponent(window.location.href) +
"&prompt=none&state=" + params["state"] + "&nonce=SomeNonce";
}
else if (params["access_token"] != null) {
window.localStorage.setItem("access_token", this.params["access_token"]);
window.location.href = params["state"];
} else {
window.setTimeout(function() {
var idToken = window.localStorage.getItem("id_token");
var urlWithoutQueryString = window.location.href.substring(0, window.location.href.length - window.location.search.length)
window.location.href = "https://login.microsoftonline.com/" + params["tenantID"] +
"/oauth2/authorize?response_type=token&client_id=" + params["clientID"] +
"&resource=" + params["resource"] +
"&redirect_uri=" + encodeURIComponent(window.location.href) +
"&prompt=none&state=" + encodeURIComponent(window.location.href) + "&nonce=SomeNonce";
}, timeoutMiliSeconds);
}
</script>
</head>

<body>
</body>

</html>
Loading