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

Demystifying Token Authentication in NgRx (Angular Denver 2019)

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.

Sam Julien

August 02, 2019
Tweet

More Decks by Sam Julien

Other Decks in Technology

Transcript

  1. Demystifying Token
    Authentication in NgRx

    View Slide

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

    View Slide

  3. What goes where?
    @samjulien

    View Slide

  4. Sam Julien
    @samjulien
    Technical Community Manager at Auth0
    GDE & Angular Collaborator
    UpgradingAngularJS.com
    @samjulien

    View Slide

  5. @samjulien
    State Actions Effects

    View Slide

  6. State

    View Slide

  7. What goes in the store?
    @samjulien

    View Slide

  8. SHARI
    @samjulien
    Shared Impacted
    Retrieved
    Available
    Hydrated

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  14. Actions

    View Slide

  15. @samjulien

    View Slide

  16. @samjulien

    View Slide

  17. Start login
    Handle redirect
    Log in to provider

    View Slide

  18. Handle redirect
    Success
    Error

    View Slide

  19. @samjulien
    Good Action Hygiene(TM)

    View Slide

  20. @samjulien
    Write actions first
    Don’t reuse actions
    Clarity > Brevity
    Good Action Hygiene(TM)

    View Slide

  21. export const login = createAction(
    '[Login Page] Start Log In’
    );
    @samjulien

    View Slide

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

    View Slide

  23. export const loginSuccess = createAction(
    '[Auth API] Login Success’,
    props()
    );
    export const loginFailure = createAction(
    '[Auth API] Login Failure',
    props()
    );
    @samjulien

    View Slide

  24. export const loginSuccess = createAction(
    '[Auth API] Login Success’,
    props()
    );
    export const loginFailure = createAction(
    '[Auth API] Login Failure',
    props()
    );
    @samjulien

    View Slide

  25. export const loginSuccess = createAction(
    '[Auth API] Login Success’,
    props()
    );
    export const loginFailure = createAction(
    '[Auth API] Login Failure',
    props()
    );
    @samjulien

    View Slide

  26. export const loginSuccess = createAction(
    '[Auth API] Login Success’,
    props()
    );
    export const loginFailure = createAction(
    '[Auth API] Login Failure',
    props()
    );
    @samjulien

    View Slide

  27. export const loginSuccess = createAction(
    '[Auth API] Login Success’,
    props()
    );
    export const loginFailure = createAction(
    '[Auth API] Login Failure',
    props()
    );
    @samjulien

    View Slide

  28. Effects

    View Slide

  29. Authentication service will be very thin.

    View Slide

  30. Start login
    Handle redirect
    Log in to provider

    View Slide

  31. Handle redirect
    Success
    Error

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 }))
    );

    View Slide

  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 }))
    );

    View Slide

  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 }))
    );

    View Slide

  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 }))
    );

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. @samjulien
    State Actions Effects

    View Slide

  53. samj.im/angular-denver
    @samjulien

    View Slide

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

    View Slide