Skip to content

Commit

Permalink
refactor(oauth2): add types to handlers, allow better inheritance, ad… (
Browse files Browse the repository at this point in the history
#1065)

* refactor(oauth2): add types to handlers, allow better inheritance, add examples
Closes #922

* feat(oauth2): add Azure example to docs
  • Loading branch information
nnixaa authored and tibing-old-email committed Dec 13, 2018
1 parent e3e524b commit be1e754
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 5 deletions.
18 changes: 18 additions & 0 deletions docs/articles/auth-azure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Configuring Azure OAuth2 with Nebular Auth

Using `NbOAuth2AuthStrategy` gives possibility to configure authentication with a lot of 3rd party authentication providers, such as Azure in our example.
There is no need in any backend implementation, as [OAuth2](https://tools.ietf.org/html/rfc6749) protocol enables completely server-less authentication flow as one of the options.

## Complete example

A complete code example could be found on [GitHub](https://github.com/akveo/nebular/tree/master/src/playground/azure).
And here the playground example available to play around with [Azure OAuth2 Nebular Example](/example/azure).

<hr>

## Related Articles

- [NbAuthService](/docs/auth/nbauthservice)
- [NbTokenService](/docs/auth/nbtokenservice)
- Receiving [user token after authentication](/docs/auth/getting-user-token)
- [NbOAuth2AuthStrategy](/docs/auth/nboauth2authstrategy)
2 changes: 1 addition & 1 deletion docs/articles/auth-oauth2.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Configuring Google OAuth2
# Configuring Google OAuth2 with Nebular Auth

Using `NbOAuth2AuthStrategy` gives possibility to configure authentication with a lot of 3rd party authentication providers, such as Google, Facebook, etc.
There is no need in any backend implementation, as [OAuth2](https://tools.ietf.org/html/rfc6749) protocol enables completely server-less authentication flow as one of the options.
Expand Down
11 changes: 11 additions & 0 deletions docs/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,17 @@ export const structure = [
},
],
},
{
type: 'page',
name: 'Configuring Azure OAuth2',
children: [
{
type: 'block',
block: 'markdown',
source: 'auth-azure.md',
},
],
},
{
type: 'page',
name: 'NbAuthService',
Expand Down
8 changes: 4 additions & 4 deletions src/framework/auth/strategies/oauth2/oauth2-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
return this.getOption('clientAuthMethod');
}

protected redirectResultHandlers = {
protected redirectResultHandlers: { [key: string]: Function } = {
[NbOAuth2ResponseType.CODE]: () => {
return observableOf(this.route.snapshot.queryParams).pipe(
switchMap((params: any) => {
Expand Down Expand Up @@ -168,7 +168,7 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
},
};

protected redirectResults = {
protected redirectResults: { [key: string]: Function } = {
[NbOAuth2ResponseType.CODE]: () => {
return observableOf(this.route.snapshot.queryParams).pipe(
map((params: any) => !!(params && (params.code || params.error))),
Expand All @@ -185,8 +185,8 @@ export class NbOAuth2AuthStrategy extends NbAuthStrategy {
protected defaultOptions: NbOAuth2AuthStrategyOptions = auth2StrategyOptions;

constructor(protected http: HttpClient,
private route: ActivatedRoute,
@Inject(NB_WINDOW) private window: any) {
protected route: ActivatedRoute,
@Inject(NB_WINDOW) protected window: any) {
super();
}

Expand Down
108 changes: 108 additions & 0 deletions src/playground/azure/azure-adb2c-auth-strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { Injectable } from '@angular/core';
import { of } from 'rxjs';

import { map, switchMap, catchError } from 'rxjs/operators';

import {
NbOAuth2AuthStrategy,
NbOAuth2ResponseType,
NbAuthOAuth2JWTToken,
NbOAuth2AuthStrategyOptions,
NbAuthStrategyClass,
NbAuthResult,
NbAuthIllegalTokenError,
} from '@nebular/auth';


// Create new token for Azure auth so it returns id_token instead of access_token
export class NbAuthAzureToken extends NbAuthOAuth2JWTToken {

// let's rename it to exclude name clashes
static NAME = 'nb:auth:azure:token';

getValue(): string {
return this.token.id_token;
}
}

@Injectable()
export class NbAzureADB2CAuthStrategy extends NbOAuth2AuthStrategy {

// we need this method for strategy setup
static setup(options: NbOAuth2AuthStrategyOptions): [NbAuthStrategyClass, NbOAuth2AuthStrategyOptions] {
return [NbAzureADB2CAuthStrategy, options];
}

protected redirectResultHandlers = {
[NbOAuth2ResponseType.CODE]: () => {
return of(this.route.snapshot.queryParams).pipe(
switchMap((params: any) => {
if (params.code) {
return this.requestToken(params.code);
}

return of(
new NbAuthResult(
false,
params,
this.getOption('redirect.failure'),
this.getOption('defaultErrors'),
[],
));
}),
);
},
id_token: () => {
const module = 'authorize';
const requireValidToken = this.getOption(`${module}.requireValidToken`);
return of(this.route.snapshot.fragment).pipe(
map(fragment => this.parseHashAsQueryParams(fragment)),
map((params: any) => {
if (!params.error) {
return new NbAuthResult(
true,
params,
this.getOption('redirect.success'),
[],
this.getOption('defaultMessages'),
this.createToken(params, requireValidToken));
}
return new NbAuthResult(
false,
params,
this.getOption('redirect.failure'),
this.getOption('defaultErrors'),
[],
);
}),
catchError(err => {
const errors = [];
if (err instanceof NbAuthIllegalTokenError) {
errors.push(err.message);
} else {
errors.push('Something went wrong.');
}
return of(
new NbAuthResult(
false,
err,
this.getOption('redirect.failure'),
errors,
));
}),
);
},
};


protected redirectResults: any = {
[NbOAuth2ResponseType.CODE]: () => of(null),

id_token: () => {
return of(this.route.snapshot.fragment).pipe(
map(fragment => this.parseHashAsQueryParams(fragment)),
map((params: any) => !!(params && (params.id_token || params.error))),
);
},
};
}
37 changes: 37 additions & 0 deletions src/playground/azure/azure-callback.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Component, OnDestroy } from '@angular/core';
import { NbAuthResult, NbAuthService } from '@nebular/auth';
import { Router } from '@angular/router';
import { takeWhile } from 'rxjs/operators';

@Component({
selector: 'nb-playground-azure-callback',
template: `
<nb-layout>
<nb-layout-column>Authenticating...</nb-layout-column>
</nb-layout>
`,
})
export class NbAzureCallbackComponent implements OnDestroy {

alive = true;

constructor(private authService: NbAuthService, private router: Router) {
this.authService.authenticate('azure')
.pipe(takeWhile(() => this.alive))
.subscribe((authResult: NbAuthResult) => {
if (authResult.isSuccess() && authResult.getRedirect()) {
this.router.navigateByUrl(authResult.getRedirect());
}
});
}

ngOnDestroy(): void {
this.alive = false;
}
}
64 changes: 64 additions & 0 deletions src/playground/azure/azure-login.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { Component, OnDestroy } from '@angular/core';
import { NbAuthResult, NbAuthService } from '@nebular/auth';
import { takeWhile } from 'rxjs/operators';
import { NbAuthAzureToken } from './azure-adb2c-auth-strategy';

@Component({
selector: 'nb-playground-azure',
template: `
<nb-layout>
<nb-layout-column>
<nb-card>
<nb-card-body>
<p>Current User Authenticated: {{ !!token }}</p>
<p>Current User Token: {{ token|json }}</p>
<button nbButton status="success" *ngIf="!token" (click)="login()">Sign In with Azure</button>
<button nbButton status="warning" *ngIf="token" (click)="logout()">Sign Out</button>
</nb-card-body>
</nb-card>
</nb-layout-column>
</nb-layout>
`,
})
export class NbAzureLoginComponent implements OnDestroy {

token: NbAuthAzureToken;

alive = true;

constructor(private authService: NbAuthService) {
this.authService.onTokenChange()
.pipe(takeWhile(() => this.alive))
.subscribe((token: NbAuthAzureToken) => {
this.token = null;
if (token && token.isValid()) {
this.token = token;
}
});
}

login() {
this.authService.authenticate('azure')
.pipe(takeWhile(() => this.alive))
.subscribe((authResult: NbAuthResult) => {
});
}

logout() {
this.authService.logout('azure')
.pipe(takeWhile(() => this.alive))
.subscribe((authResult: NbAuthResult) => {
});
}

ngOnDestroy(): void {
this.alive = false;
}
}
79 changes: 79 additions & 0 deletions src/playground/azure/azure.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { HttpClientModule } from '@angular/common/http';

import {
NbCardModule,
NbLayoutModule,
} from '@nebular/theme';

import { NbAuthModule } from '@nebular/auth';

import { NbAzureLoginComponent } from './azure-login.component';
import { NbAzureCallbackComponent } from './azure-callback.component';
import { NbAuthAzureToken, NbAzureADB2CAuthStrategy } from './azure-adb2c-auth-strategy';


@NgModule({
imports: [
CommonModule,
FormsModule,
HttpClientModule,
RouterModule,
RouterModule.forChild([
{
path: '',
component: NbAzureLoginComponent,
},
{
path: 'callback',
component: NbAzureCallbackComponent,
},
]),

NbAuthModule.forRoot({
strategies: [
NbAzureADB2CAuthStrategy.setup({
name: 'azure',
clientId: 'bde728e2-2809-4ff1-bc9c-7fcb23800ebe',
clientSecret: '',
authorize: {
endpoint: 'https://login.microsoftonline.com/01513fd2-16a0-453b-9fa0-c9089bfa023e/oauth2/authorize',
responseType: 'id_token',
scope: 'openid',
redirectUri: 'https://akveo.github.io/nebular/example/azure/callback',
params: {
p: 'b2c_1_nebular',
},
},
token: {
class: NbAuthAzureToken,
},
redirect: {
success: '/example/azure',
},
}),
],
}),

NbCardModule,
NbLayoutModule,
],
declarations: [
NbAzureLoginComponent,
NbAzureCallbackComponent,
],
providers: [
NbAzureADB2CAuthStrategy,
],
})
export class NbAzurePlaygroundModule {
}
4 changes: 4 additions & 0 deletions src/playground/playground-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,10 @@ export const routes: Routes = [
path: 'oauth2',
loadChildren: './oauth2/oauth2.module#NbOAuth2PlaygroundModule',
},
{
path: 'azure',
loadChildren: './azure/azure.module#NbAzurePlaygroundModule',
},
{
path: 'oauth2-password',
loadChildren: './oauth2-password/oauth2-password.module#NbOAuth2PasswordPlaygroundModule',
Expand Down

0 comments on commit be1e754

Please sign in to comment.