adding authentication to angular appsapi security? sessions?cookies? tokens?! authentication basics...

Post on 30-Mar-2020

19 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

@KimMaida

ADDING AUTHENTICATION TO ANGULAR APPS

@KimMaida

Hello! I’m Kim Maida

Manager, Community & Technical Content at Auth0 Angular Google Developer Expert

@KimMaida

“How do I secure my Angular app?”

@KimMaida

“How can I properly add authentication to my app to protect my data from unauthorized access?”

@KimMaida

API security? Sessions? Cookies? Tokens?

@KimMaida

API security? Sessions? Cookies? Tokens?

😵

AUTHENTICATION BASICS FOR WEB APPS

@KimMaida

Traditional web app authentication

@KimMaida

Authentication with Cookie-based Sessions

@KimMaida

Authentication with Cookie-based Sessions

@KimMaida

Authentication with Cookie-based Sessions

@KimMaida

Authentication with Cookie-based Sessions

@KimMaida

Authentication with Cookie-based Sessions

@KimMaida

Authentication with Cookie-based Sessions

</>HTML + Data

@KimMaida

Authentication with Cookie-based Sessions

@KimMaida

Authentication with Cookie-based Sessions

302HTML Error

@KimMaida

Authentication with Cookie-based Sessions

302

302HTML Error

?

@KimMaida

Authentication with Cookie-based Sessions

</>HTML + Data

@KimMaida

What about Single Page Apps?

@KimMaida

Single Page Application

@KimMaida

Single Page Application

@KimMaida

Single Page Application

@KimMaida

Single Page Application

@KimMaida

Single Page Application

{;}JSON

@KimMaida

Why not use cookie-based auth?

🤔

@KimMaida

Two Issues

@KimMaida

Issue 1: APIs on Other Domains

my-domain.com

other-domain.com

X

@KimMaida

You (maybe): “No problem, everything is on one domain!”

$

@KimMaida

One more shortcoming

@KimMaida

Issue 2: Authentication via Redirects & Ajax Calls

App Using Cookie-based Sessions

302

@KimMaida

Issue 2: Authentication via Redirects & Ajax Calls

Single Page ApplicationApp Using Cookie-based Sessions

302?

@KimMaida

Address these issues with token-based auth

@KimMaida

Single Page Application

@KimMaida

Single Page Application

@KimMaida

Single Page Application

#

@KimMaida

Single Page Application

@KimMaida

Single Page Application

#

@KimMaida

Don’t Store Sensitive Data in Local Storage

@KimMaida

Local Storage Security Concerns

! XSS can be used to steal all data in local storage

! XSS can be used to load malicious data into local storage

! Local Storage is shared within an origin

https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet#Local_Storage

@KimMaida

@KimMaida

Single Page Application

@KimMaida

Single Page Application

#

@KimMaida

Single Page Application

@KimMaida

Use Tokens to Call APIs

{;}JSON Data

@KimMaida

Use Tokens to Call APIs

my-domain.com

other-domain.com

@KimMaida

Solves the 2 Issues

1. APIs on other domains

2. Authentication via Redirects & Ajax Calls✅

HOW DOES ANGULAR HELP?

@KimMaida

Angular Authentication TODO List

Manage authentication events

Attach token to API requests

Restrict access to certain routes

@KimMaida

Angular Authentication TODO List

Manage authentication events

Attach token to API requests

Restrict access to certain routes

@KimMaida

Angular Authentication TODO List

Manage authentication events

Send token with API requests

Restrict access to certain routes

@KimMaida

Angular Authentication TODO List

Manage authentication events

Send token with API requests

Restrict access to certain routes

@KimMaida

TODO: Manage authentication events

@KimMaida

@KimMaida

Create streams from callbacks

@KimMaida

Example: Bind Callbacks with RxJS

parseHash$ = bindNodeCallback( this.AuthSDK.parseHash .bind(this.Auth) );

parseHash$().subscribe( authData => this.setAuth(authData) );

@KimMaida

Example: Bind Callbacks with RxJS

parseHash$ = bindNodeCallback( this.AuthSDK.parseHash .bind(this.Auth) );

parseHash$().subscribe( authData => this.setAuth(authData) );

@KimMaida

Example: Bind Callbacks with RxJS

parseHash$ = bindNodeCallback( this.AuthSDK.parseHash .bind(this.Auth) );

parseHash$().subscribe( authData => this.setAuth(authData) );

@KimMaida

Example: Bind Callbacks with RxJS

parseHash$ = bindNodeCallback( this.AuthSDK.parseHash .bind(this.Auth) );

parseHash$().subscribe( authData => this.setAuth(authData) );

@KimMaida

Example: Bind Callbacks with RxJS

parseHash$ = bindNodeCallback( this.AuthSDK.parseHash .bind(this.Auth) );

parseHash$().subscribe( authData => this.setAuth(authData) );

@KimMaida

Create streams of auth data

@KimMaida

Create Auth Streams with RxJS

user$ = new BehaviorSubject<any>(null);

setAuth(authResult) { this.user$ .next(authResult.idTokenPayload); … }

@KimMaida

Create Auth Streams with RxJS

user$ = new BehaviorSubject<any>(null);

setAuth(authResult) { this.user$ .next(authResult.idTokenPayload); … }

@KimMaida

Create Auth Streams with RxJS

user$ = new BehaviorSubject<any>(null);

setAuth(authResult) { this.user$ .next(authResult.idTokenPayload); … }

@KimMaida

Create Auth Streams with RxJS

user$ = new BehaviorSubject<any>(null);

setAuth(authResult) { this.user$ .next(authResult.idTokenPayload); … }

@KimMaida

Use Auth Streams

<div *ngIf="(auth.user$ | async) as user"> <h1>{{ user.name }} </h1> </div>

@KimMaida

…and more

@KimMaida

Angular Authentication TODO List

Manage authentication events

Send token with API requests

Restrict access to certain routes

@KimMaida

TODO: Send token with API requests

@KimMaida

HTTP Interceptors

@KimMaida

HTTP Interceptors

Interceptor Service{Authorization:`Bearer `}

@KimMaida

App Routing NgModule

@NgModule({ ...,

providers: [

AuthService,

{

provide: HTTP_INTERCEPTORS,

useClass: TokenInterceptor,

multi: true

}

], ...

@KimMaida

App Routing NgModule

@NgModule({ ...,

providers: [

AuthService,

{

provide: HTTP_INTERCEPTORS,

useClass: TokenInterceptor,

multi: true

}

], ...

@KimMaida

App Routing NgModule

@NgModule({ ...,

providers: [

AuthService,

{

provide: HTTP_INTERCEPTORS,

useClass: TokenInterceptor,

multi: true

}

], ...

@KimMaida

App Routing NgModule

@NgModule({ ...,

providers: [

AuthService,

{

provide: HTTP_INTERCEPTORS,

useClass: TokenInterceptor,

multi: true

}

], ...

@KimMaida

TokenInterceptor Service

@Injectable()

export class TokenInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) { }

intercept(req: HttpRequest<any>, next: HttpHandler) {

const tokenReq = req.clone({

setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq);

}

}

@KimMaida

TokenInterceptor Service

@Injectable()

export class TokenInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) { }

intercept(req: HttpRequest<any>, next: HttpHandler) {

const tokenReq = req.clone({

setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq);

}

}

@KimMaida

TokenInterceptor Service

@Injectable()

export class TokenInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) { }

intercept(req: HttpRequest<any>, next: HttpHandler) {

const tokenReq = req.clone({

setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq);

}

}

@KimMaida

TokenInterceptor Service

@Injectable()

export class TokenInterceptor implements HttpInterceptor {

constructor(private auth: AuthService) { }

intercept(req: HttpRequest<any>, next: HttpHandler) {

const tokenReq = req.clone({

setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq);

}

}

@KimMaida

Conditional Interceptors

@KimMaida

Conditional Logic in Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { if (req.url.indexOf('private') > -1) { const tokenReq = req.clone({ setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq); } return next.handle(req);

}

@KimMaida

Conditional Logic in Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { if (req.url.indexOf('private') > -1) { const tokenReq = req.clone({ setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq); } return next.handle(req);

}

@KimMaida

Conditional Logic in Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { if (req.url.indexOf('private') > -1) { const tokenReq = req.clone({ setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq); } return next.handle(req);

}

@KimMaida

Conditional Logic in Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { if (req.url.indexOf('private') > -1) { const tokenReq = req.clone({ setHeaders: {Authorization: `Bearer ${this.auth.token}`}

});

return next.handle(tokenReq); } return next.handle(req);

}

@KimMaida

Asynchronous Interceptors

@KimMaida

Async Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { return this.auth.token$.pipe( mergeMap(token => { if (token) { const tokenReq = req.clone({ setHeaders: {Authorization:`Bearer ${token}`} }); return next.handle(tokenReq); } }) );} }

@KimMaida

Async Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { return this.auth.token$.pipe( mergeMap(token => { if (token) { const tokenReq = req.clone({ setHeaders: {Authorization:`Bearer ${token}`} }); return next.handle(tokenReq); } }) );} }

@KimMaida

Async Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { return this.auth.token$.pipe( mergeMap(token => { if (token) { const tokenReq = req.clone({ setHeaders: {Authorization:`Bearer ${token}`} }); return next.handle(tokenReq); } }) );} }

@KimMaida

Async Interceptors

intercept(req: HttpRequest<any>, next: HttpHandler) { return this.auth.token$.pipe( mergeMap(token => { if (token) { const tokenReq = req.clone({ setHeaders: {Authorization:`Bearer ${token}`} }); return next.handle(tokenReq); } }) );} }

@KimMaida

Angular Authentication TODO List

Manage authentication events

Send token with API requests

Restrict access to certain routes

@KimMaida

TODO: Restrict access to certain routes

@KimMaida

Route Guards

@KimMaida

Guard Interfaces - CanActivate

@KimMaida

Guard Interfaces - CanActivateChild

@KimMaida

Guard Interfaces - CanLoad

@KimMaida

Route Guards - CanActivate App Routes

import { AuthGuard } from './auth.guard';

export const ROUTES: Routes = [

{

path: 'profile',

component: ProfileComponent,

canActivate: [ AuthGuard ]

}

];

@KimMaida

Route Guards - CanActivate App Routes

import { AuthGuard } from './auth.guard';

export const ROUTES: Routes = [

{

path: 'profile',

component: ProfileComponent,

canActivate: [ AuthGuard ]

}

];

@KimMaida

Route Guards - CanActivate App Routes

import { AuthGuard } from './auth.guard';

export const ROUTES: Routes = [

{

path: 'profile',

component: ProfileComponent,

canActivate: [ AuthGuard ]

}

];

@KimMaida

Route Guards - CanActivate Service

@Injectable()

export class AuthGuard implements CanActivate {

constructor(private auth: AuthService, public router: Router) {}

canActivate(): boolean {

if (!this.auth.isAuthenticated()) {

this.router.navigate(['login']);

return false;

}

return true;

}

}

@KimMaida

Route Guards - CanActivate Service

@Injectable()

export class AuthGuard implements CanActivate {

constructor(private auth: AuthService, public router: Router) {}

canActivate(): boolean {

if (!this.auth.isAuthenticated()) {

this.router.navigate(['login']);

return false;

}

return true;

}

}

@KimMaida

Route Guards - CanActivate Service

@Injectable()

export class AuthGuard implements CanActivate {

constructor(private auth: AuthService, public router: Router) {}

canActivate(): boolean {

if (!this.auth.isAuthenticated()) {

this.router.navigate(['login']);

return false;

}

return true;

}

}

@KimMaida

Route Guards - CanActivate Service

@Injectable()

export class AuthGuard implements CanActivate {

constructor(private auth: AuthService, public router: Router) {}

canActivate(): boolean {

if (!this.auth.isAuthenticated()) {

this.router.navigate(['login']);

return false;

}

return true;

}

}

@KimMaida

Guarded Lazy Loading

@KimMaida

Lazy Loading with CanLoad Guard

export const ROUTES: Routes = [

{

path: 'admin',

loadChildren: './admin/admin.module#AdminModule',

canLoad: [ RoleGuard ],

data: { expectedRole: 'admin' }

}

];

@KimMaida

Lazy Loading with CanLoad Guard

export const ROUTES: Routes = [

{

path: 'admin',

loadChildren: './admin/admin.module#AdminModule',

canLoad: [ RoleGuard ],

data: { expectedRole: 'admin' }

}

];

@KimMaida

Lazy Loading with CanLoad Guard

export const ROUTES: Routes = [

{

path: 'admin',

loadChildren: './admin/admin.module#AdminModule',

canLoad: [ RoleGuard ],

data: { expectedRole: 'admin' }

}

];

@KimMaida

Lazy Loading with CanLoad Guard

export const ROUTES: Routes = [

{

path: 'admin',

loadChildren: './admin/admin.module#AdminModule',

canLoad: [ RoleGuard ],

data: { expectedRole: 'admin' }

}

];

@KimMaida

Route Guards - CanLoad

@Injectable()

export class RoleGuard implements CanLoad {

constructor(private auth: AuthService) {}

canLoad(route: Route): boolean {

if (!this.auth.isAuthenticated() ||

!this.auth.hasRole(route.data.expectedRole)) {

this.auth.login();

return false;

} …

@KimMaida

Route Guards - CanLoad

@Injectable()

export class RoleGuard implements CanLoad {

constructor(private auth: AuthService) {}

canLoad(route: Route): boolean {

if (!this.auth.isAuthenticated() ||

!this.auth.hasRole(route.data.expectedRole)) {

this.auth.login();

return false;

} …

@KimMaida

Route Guards - CanLoad

@Injectable()

export class RoleGuard implements CanLoad {

constructor(private auth: AuthService) {}

canLoad(route: Route): boolean {

if (!this.auth.isAuthenticated() ||

!this.auth.hasRole(route.data.expectedRole)) {

this.auth.login();

return false;

} …

@KimMaida

Angular Authentication TODO List

Manage authentication events

Send token with API requests

Restrict access to certain routes

@KimMaida

@KimMaida

Adding Authentication to Angular Apps

bit.ly/angularmix-adding-auth

Thank you!

top related