Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Demystifying Token Authentication in NgRx (Angular Denver 2019)

7beed3a6fa39e12c9e873b903e4d9244?s=47 Sam Julien
August 02, 2019

Demystifying Token Authentication in NgRx (Angular Denver 2019)

So, you’ve got a shiny new Angular application and you’re thrilled to be managing your state with NgRx. You’ve got your store, reducers, and actions set up, but your boss asks you an innocent question during your first demo: “How do I log in?” You stare blankly, realizing that you’ve never thought about that. Isn’t it the same as in a regular Angular application? How does authentication in NgRx work, anyway? (And not just tutorial authentication — real authentication using tokens!)

I’ve got your back in this talk. You’ll learn not only the HOW of token-based authentication in NgRx, but also the WHY. We’ll talk about managing app-wide authentication state through the store, dispatching actions through effects, and keeping your application safe using authentication best practices.

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

August 02, 2019
Tweet

Transcript

  1. Demystifying Token Authentication in NgRx

  2. Great, but how do I log in? @samjulien

  3. What goes where? @samjulien

  4. Sam Julien @samjulien Technical Community Manager at Auth0 GDE &

    Angular Collaborator UpgradingAngularJS.com @samjulien
  5. @samjulien State Actions Effects

  6. State

  7. What goes in the store? @samjulien

  8. SHARI @samjulien Shared Impacted Retrieved Available Hydrated

  9. export interface State { isAuthenticated: boolean; userProfile: UserProfile; accessToken: AccessToken;

    } @samjulien
  10. export interface State { isAuthenticated: boolean; userProfile: UserProfile; accessToken: AccessToken;

    } @samjulien
  11. export interface State { isAuthenticated: boolean; userProfile: UserProfile; accessToken: AccessToken;

    } @samjulien
  12. export interface State { isAuthenticated: boolean; userProfile: UserProfile; accessToken: AccessToken;

    } @samjulien
  13. Don’t keep access tokens in local storage! @samjulien

  14. Actions

  15. @samjulien

  16. @samjulien

  17. Start login Handle redirect Log in to provider

  18. Handle redirect Success Error

  19. @samjulien Good Action Hygiene(TM)

  20. @samjulien Write actions first Don’t reuse actions Clarity > Brevity

    Good Action Hygiene(TM)
  21. export const login = createAction( '[Login Page] Start Log In’

    ); @samjulien
  22. export const handleRedirect = createAction( '[Auth API] Handle Redirect' );

    @samjulien
  23. export const loginSuccess = createAction( '[Auth API] Login Success’, props<{

    redirectUrl: string }>() ); export const loginFailure = createAction( '[Auth API] Login Failure', props<{ error: Error }>() ); @samjulien
  24. export const loginSuccess = createAction( '[Auth API] Login Success’, props<{

    redirectUrl: string }>() ); export const loginFailure = createAction( '[Auth API] Login Failure', props<{ error: Error }>() ); @samjulien
  25. export const loginSuccess = createAction( '[Auth API] Login Success’, props<{

    redirectUrl: string }>() ); export const loginFailure = createAction( '[Auth API] Login Failure', props<{ error: Error }>() ); @samjulien
  26. export const loginSuccess = createAction( '[Auth API] Login Success’, props<{

    redirectUrl: string }>() ); export const loginFailure = createAction( '[Auth API] Login Failure', props<{ error: Error }>() ); @samjulien
  27. export const loginSuccess = createAction( '[Auth API] Login Success’, props<{

    redirectUrl: string }>() ); export const loginFailure = createAction( '[Auth API] Login Failure', props<{ error: Error }>() ); @samjulien
  28. Effects

  29. Authentication service will be very thin.

  30. Start login Handle redirect Log in to provider

  31. Handle redirect Success Error

  32. login$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.login), tap(() => this.authService.login())

    ), { dispatch: false } ); @samjulien
  33. login$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.login), tap(() => this.authService.login())

    ), { dispatch: false } ); @samjulien
  34. login$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.login), tap(() => this.authService.login())

    ), { dispatch: false } ); @samjulien
  35. login$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.login), tap(() => this.authService.login())

    ), { dispatch: false } ); @samjulien
  36. handleRedirect$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien

    ) ); ... })
  37. handleRedirect$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien

    ) ); ... })
  38. handleRedirect$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien

    ) ); ... })
  39. handleRedirect$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien

    ) ); ... })
  40. this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien }) // handle redirect

    and process tokens this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.loginSuccess({ redirectUrl })), catchError(({ error }) => AuthActions.loginFailure({ error })) );
  41. this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien }) // handle redirect

    and process tokens this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.loginSuccess({ redirectUrl })), catchError(({ error }) => AuthActions.loginFailure({ error })) );
  42. this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien }) // handle redirect

    and process tokens this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.loginSuccess({ redirectUrl })), catchError(({ error }) => AuthActions.loginFailure({ error })) );
  43. this.actions$.pipe( ofType(AuthActions.handleRedirect), exhaustMap(() => { @samjulien }) // handle redirect

    and process tokens this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.loginSuccess({ redirectUrl })), catchError(({ error }) => AuthActions.loginFailure({ error })) );
  44. loginSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginSucess), tap(({ redirectUrl })

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  45. loginSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginSucess), tap(({ redirectUrl })

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  46. loginSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginSucess), tap(({ redirectUrl })

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  47. loginSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginSucess), tap(({ redirectUrl })

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  48. loginFailure$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginFailure), tap(({ error })

    => { this.loggingService.error(error); this.router.navigate(['/']); }) ), { dispatch: false } ); @samjulien
  49. loginFailure$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginFailure), tap(({ error })

    => { this.loggingService.error(error); this.router.navigate(['/']); }) ), { dispatch: false } ); @samjulien
  50. loginFailure$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginFailure), tap(({ error })

    => { this.loggingService.error(error); this.router.navigate(['/']); }) ), { dispatch: false } ); @samjulien
  51. loginFailure$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.loginFailure), tap(({ error })

    => { this.loggingService.error(error); this.this.router.navigate(['/']); }) ), { dispatch: false } ); @samjulien
  52. @samjulien State Actions Effects

  53. samj.im/angular-denver @samjulien

  54. samj.im/angular-denver Thank you! @samjulien