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

Demystifying NgRx Authentication (AngularMix)

Sam Julien
November 21, 2019

Demystifying NgRx Authentication (AngularMix)

Level: 200

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 completely forgotten about authentication. Isn't it the same as in a regular Angular application? How does real-world authentication in NgRx work, anyway?

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, how to determine what should end up in state, maintaining Good Action Hygiene with authentication, and keeping your application safe using authentication best practices.

Sam Julien

November 21, 2019
Tweet

More Decks by Sam Julien

Other Decks in Programming

Transcript

  1. Demystifying Token
    Authentication in NgRx

    View full-size slide

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

    View full-size slide

  3. Authentication is a Big Scary Subject.
    @samjulien

    View full-size slide

  4. …with lots of jargon.
    @samjulien

    View full-size slide

  5. …with lots of jargon vocab.
    @samjulien

    View full-size slide

  6. NgRx is a Big Scary Subject.
    @samjulien

    View full-size slide

  7. …with lots of jargon.
    @samjulien

    View full-size slide

  8. …with lots of jargon vocab.
    @samjulien

    View full-size slide

  9. Auth in NgRx looks different
    than auth in vanilla Angular.
    @samjulien

    View full-size slide

  10. Feature development in NgRx
    looks different than feature
    development in vanilla Angular.
    @samjulien

    View full-size slide

  11. @samjulien
    Auth in NgRx requires a
    different mental model than
    auth in vanilla Angular.

    View full-size slide

  12. @samjulien
    Sam Julien
    @samjulien
    @samjulien

    View full-size slide

  13. @samjulien
    Sam Julien
    @samjulien
    Developer Advocate Engineer at Auth0
    @samjulien

    View full-size slide

  14. @samjulien
    Sam Julien
    @samjulien
    Developer Advocate Engineer at Auth0
    GDE & Angular Collaborator
    @samjulien

    View full-size slide

  15. @samjulien
    Sam Julien
    @samjulien
    Developer Advocate Engineer at Auth0
    GDE & Angular Collaborator
    UpgradingAngularJS.com, Thinkster, & Egghead
    @samjulien

    View full-size slide

  16. Auth in NgRx looks different
    than auth in vanilla Angular.
    @samjulien

    View full-size slide

  17. Feature development in NgRx
    looks different than feature
    development in vanilla Angular.
    @samjulien

    View full-size slide

  18. @samjulien
    Auth in NgRx requires a
    different mental model than
    auth in vanilla Angular.

    View full-size slide

  19. Start login
    Handle redirect
    Log in to provider
    @samjulien

    View full-size slide

  20. Handle redirect
    Success
    Error
    @samjulien

    View full-size slide

  21. Auth Service
    @samjulien

    View full-size slide

  22. Auth Service
    Components
    @samjulien

    View full-size slide

  23. Auth Service
    Components
    Data Services
    @samjulien

    View full-size slide

  24. Auth Service
    Components
    Data Services
    @samjulien

    View full-size slide

  25. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  26. Auth Service
    Components
    Data Services
    @samjulien

    View full-size slide

  27. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  28. State Side Effects
    @samjulien

    View full-size slide

  29. State
    Side Effects
    @samjulien

    View full-size slide

  30. State What do I need to keep track of?
    Side Effects
    @samjulien

    View full-size slide

  31. State What do I need to keep track of?
    Side Effects What events don’t directly change state?
    @samjulien

    View full-size slide

  32. State
    Side Effects
    @samjulien

    View full-size slide

  33. State Video Game Collection
    Side Effects
    @samjulien

    View full-size slide

  34. State Video Game Collection
    Side Effects Call the API to get the collection.
    @samjulien

    View full-size slide

  35. State
    Side Effects
    @samjulien

    View full-size slide

  36. State Game Ownership
    Side Effects
    @samjulien

    View full-size slide

  37. State Game Ownership
    Side Effects Call the API to add to the collection.
    @samjulien

    View full-size slide

  38. State
    @samjulien

    View full-size slide

  39. What do I need to keep track of?
    @samjulien

    View full-size slide

  40. Where do I keep it?
    @samjulien

    View full-size slide

  41. What goes in the store?
    @samjulien

    View full-size slide

  42. Start login
    Handle redirect
    Log in to provider
    @samjulien

    View full-size slide

  43. Handle redirect
    Success
    Error
    @samjulien

    View full-size slide

  44. Success
    @samjulien

    View full-size slide

  45. Success
    User Token
    Authenticated
    @samjulien

    View full-size slide

  46. Success
    User Token
    Authenticated
    Redirect
    @samjulien

    View full-size slide

  47. Auth Service
    @samjulien

    View full-size slide

  48. export class AuthService {
    isAuthenticated: boolean = null;
    private userProfileSubject$ = new
    BehaviorSubject(null);
    userProfile$ = this.userProfileSubject$.asObservable();
    private tokenSubject$ = new
    BehaviorSubject(null);
    accessToken$ = this.userProfileSubject$.asObservable();
    }
    @samjulien

    View full-size slide

  49. export class AuthService {
    isAuthenticated: boolean = null;
    private userProfileSubject$ = new
    BehaviorSubject(null);
    userProfile$ = this.userProfileSubject$.asObservable();
    private tokenSubject$ = new
    BehaviorSubject(null);
    accessToken$ = this.userProfileSubject$.asObservable();
    }
    @samjulien

    View full-size slide

  50. export class AuthService {
    isAuthenticated: boolean = null;
    private userProfileSubject$ = new
    BehaviorSubject(null);
    userProfile$ = this.userProfileSubject$.asObservable();
    private tokenSubject$ = new
    BehaviorSubject(null);
    accessToken$ = this.userProfileSubject$.asObservable();
    }
    @samjulien

    View full-size slide

  51. export class AuthService {
    isAuthenticated: boolean = null;
    private userProfileSubject$ = new
    BehaviorSubject(null);
    userProfile$ = this.userProfileSubject$.asObservable();
    private tokenSubject$ = new
    BehaviorSubject(null);
    accessToken$ = this.userProfileSubject$.asObservable();
    }
    @samjulien

    View full-size slide

  52. export class AuthService {
    isAuthenticated: boolean = null;
    private userProfileSubject$ = new
    BehaviorSubject(null);
    userProfile$ = this.userProfileSubject$.asObservable();
    private tokenSubject$ = new
    BehaviorSubject(null);
    accessToken$ = this.userProfileSubject$.asObservable();
    }
    @samjulien

    View full-size slide

  53. export class AuthService {
    isAuthenticated: boolean = null;
    private userProfileSubject$ = new
    BehaviorSubject(null);
    userProfile$ = this.userProfileSubject$.asObservable();
    private tokenSubject$ = new
    BehaviorSubject(null);
    accessToken$ = this.userProfileSubject$.asObservable();
    }
    @samjulien

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  60. What messages do we need about state?
    @samjulien

    View full-size slide

  61. What actions do we need?
    @samjulien

    View full-size slide

  62. Events State Changes
    @samjulien

    View full-size slide

  63. Events
    @samjulien

    View full-size slide

  64. Start login
    Handle redirect
    Log in to provider
    @samjulien

    View full-size slide

  65. Handle redirect
    Success
    Error
    @samjulien

    View full-size slide

  66. Success
    @samjulien

    View full-size slide

  67. Success
    User Token
    Authenticated
    @samjulien

    View full-size slide

  68. Success
    User Token
    Authenticated
    Redirect
    @samjulien

    View full-size slide

  69. export const logIn = createAction(
    ‘[Auth] Start Log In’
    );
    @samjulien
    export const logOut = createAction(
    '[Auth] Log out'
    );

    View full-size slide

  70. export const handleRedirect = createAction(
    ‘[Auth] Handle redirect’
    );
    @samjulien

    View full-size slide

  71. export const handleRedirectSuccess = createAction(
    '[Auth] Handle redirect success',
    props<{ targetRoute: string }>()
    );
    @samjulien
    export const handleRedirectError = createAction(
    '[Auth] Handle redirect error’,
    props<{ error: string }>()
    );

    View full-size slide

  72. Events State Changes
    @samjulien

    View full-size slide

  73. State Changes
    @samjulien

    View full-size slide

  74. Start login
    Handle redirect
    Log in to provider
    @samjulien

    View full-size slide

  75. Handle redirect
    Success
    Error
    @samjulien

    View full-size slide

  76. Success
    @samjulien

    View full-size slide

  77. Success
    User Token
    Authenticated
    @samjulien

    View full-size slide

  78. Success
    User Token
    Authenticated
    Redirect
    @samjulien

    View full-size slide

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

    View full-size slide

  80. export const loadUser = createAction(
    '[Auth] Load user’
    );
    export const loadUserSuccess = createAction(
    '[Auth] Load user success',
    props<{ user: User }>()
    );
    @samjulien

    View full-size slide

  81. export const loadUser = createAction(
    '[Auth] Load user’
    );
    export const loadUserSuccess = createAction(
    '[Auth] Load user success',
    props<{ user: User }>()
    );
    @samjulien

    View full-size slide

  82. export const loadUser = createAction(
    '[Auth] Load user’
    );
    export const loadUserSuccess = createAction(
    '[Auth] Load user success',
    props<{ user: User }>()
    );
    @samjulien

    View full-size slide

  83. export const checkAuth = createAction('[Auth] Check auth’);
    export const checkAuthSuccess = createAction(
    '[Auth] Check auth success',
    props<{ isAuthenticated: boolean }>()
    );
    export const setNotAuthenticated = createAction(
    '[Auth] Not authenticated',
    props<{ isAuthenticated: boolean }>()
    );
    @samjulien

    View full-size slide

  84. export const checkAuth = createAction('[Auth] Check auth’);
    export const checkAuthSuccess = createAction(
    '[Auth] Check auth success',
    props<{ isAuthenticated: boolean }>()
    );
    export const setNotAuthenticated = createAction(
    '[Auth] Not authenticated',
    props<{ isAuthenticated: boolean }>()
    );
    @samjulien

    View full-size slide

  85. export const checkAuth = createAction('[Auth] Check auth’);
    export const checkAuthSuccess = createAction(
    '[Auth] Check auth success',
    props<{ isAuthenticated: boolean }>()
    );
    export const setNotAuthenticated = createAction(
    '[Auth] Not authenticated',
    props<{ isAuthenticated: boolean }>()
    );
    @samjulien

    View full-size slide

  86. export const checkAuth = createAction('[Auth] Check auth’);
    export const checkAuthSuccess = createAction(
    '[Auth] Check auth success',
    props<{ isAuthenticated: boolean }>()
    );
    export const setNotAuthenticated = createAction(
    '[Auth] Not authenticated',
    props<{ isAuthenticated: boolean }>()
    );
    @samjulien

    View full-size slide

  87. export const getToken = createAction('[Auth] Get token’);
    export const getTokenSuccess = createAction(
    '[Auth] Get token success',
    props<{ accessToken: Token }>()
    );
    export const getTokenFailure = createAction(
    '[Auth] Get token failure',
    props<{ error: string }>()
    );
    @samjulien

    View full-size slide

  88. export const getToken = createAction('[Auth] Get token’);
    export const getTokenSuccess = createAction(
    '[Auth] Get token success',
    props<{ accessToken: Token }>()
    );
    export const getTokenFailure = createAction(
    '[Auth] Get token failure',
    props<{ error: string }>()
    );
    @samjulien

    View full-size slide

  89. export const getToken = createAction('[Auth] Get token’);
    export const getTokenSuccess = createAction(
    '[Auth] Get token success',
    props<{ accessToken: Token }>()
    );
    export const getTokenFailure = createAction(
    '[Auth] Get token failure',
    props<{ error: string }>()
    );
    @samjulien

    View full-size slide

  90. export const getToken = createAction('[Auth] Get token’);
    export const getTokenSuccess = createAction(
    '[Auth] Get token success',
    props<{ accessToken: Token }>()
    );
    export const getTokenFailure = createAction(
    '[Auth] Get token failure',
    props<{ error: string }>()
    );
    @samjulien

    View full-size slide

  91. Where does state change?
    @samjulien

    View full-size slide

  92. Defining Reducers
    @samjulien

    View full-size slide

  93. export const reducer = createReducer(
    initialState,
    // on()...
    );
    @samjulien

    View full-size slide

  94. on(
    AuthActions.checkAuthSuccess,
    AuthActions.setNotAuthenticated,
    (state, { isAuthenticated }) => {
    return {
    ...state,
    isAuthenticated,
    };
    }
    )
    @samjulien

    View full-size slide

  95. on(
    AuthActions.loadUserSuccess,
    (state, { user }) => {
    return {
    ...state,
    user,
    };
    }),
    @samjulien

    View full-size slide

  96. on(
    AuthActions.getTokenSuccess,
    (state, { accessToken }) => {
    return {
    ...state,
    accessToken,
    };
    }
    )
    @samjulien

    View full-size slide

  97. How do I read state in components?
    @samjulien

    View full-size slide

  98. Auth Service
    Components
    @samjulien

    View full-size slide

  99. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  100. Reducers
    Components
    Effects
    Auth Service
    @samjulien
    Selectors

    View full-size slide

  101. Defining Selectors
    @samjulien

    View full-size slide

  102. @samjulien
    export const selectUser =
    (state: State) => state.user;
    export const selectIsAuthenticated =
    (state: State) => state.isAuthenticated;
    export const selectAccessToken =
    (state: State) => state.accessToken;

    View full-size slide

  103. @samjulien
    export const selectUser =
    (state: State) => state.user;
    export const selectIsAuthenticated =
    (state: State) => state.isAuthenticated;
    export const selectAccessToken =
    (state: State) => state.accessToken;

    View full-size slide

  104. @samjulien
    export const selectUser =
    (state: State) => state.user;
    export const selectIsAuthenticated =
    (state: State) => state.isAuthenticated;
    export const selectAccessToken =
    (state: State) => state.accessToken;

    View full-size slide

  105. @samjulien
    export const selectUser =
    (state: State) => state.user;
    export const selectIsAuthenticated =
    (state: State) => state.isAuthenticated;
    export const selectAccessToken =
    (state: State) => state.accessToken;

    View full-size slide

  106. @samjulien
    export const selectUser = createSelector(
    selectAuthStatus,
    fromAuthStatus.selectUser
    );
    export const selectAccessToken =
    createSelector(
    selectAuthStatus,
    fromAuthStatus.selectAccessToken
    );

    View full-size slide

  107. @samjulien
    export const selectUser = createSelector(
    selectAuthStatus,
    fromAuthStatus.selectUser
    );
    export const selectAccessToken =
    createSelector(
    selectAuthStatus,
    fromAuthStatus.selectAccessToken
    );

    View full-size slide

  108. @samjulien
    export const selectUser = createSelector(
    selectAuthStatus,
    fromAuthStatus.selectUser
    );
    export const selectAccessToken =
    createSelector(
    selectAuthStatus,
    fromAuthStatus.selectAccessToken
    );

    View full-size slide

  109. @samjulien
    export const selectIsAuthenticated =
    createSelector(
    selectAuthStatus,
    fromAuthStatus.selectIsAuthenticated
    );

    View full-size slide

  110. Okay, but what about the auth calls?
    @samjulien

    View full-size slide

  111. Auth Service

    View full-size slide

  112. State Side Effects
    @samjulien

    View full-size slide

  113. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  114. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    }
    @samjulien

    View full-size slide

  115. export class AuthService {⠀
    handleRedirect() {⠀
    }⠀
    }⠀
    @samjulien

    View full-size slide

  116. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  117. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  118. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  119. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  120. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  121. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  122. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  123. export class AuthService {
    handleRedirect() {
    if (weHaveACode) {
    let targetRoute: string;
    const authComplete$ = this.handleRedirectCallback$.pipe(
    tap(response => {
    targetRoute = this.processUrl(response);
    }),
    concatMap(() => {
    return combineLatest([
    this.getUser$(),
    this.isAuthenticated$
    ]);
    })
    );
    authComplete$.subscribe(([user, loggedIn]) => {
    this.router.navigate([targetRoute]);
    });
    }
    }
    }
    @samjulien

    View full-size slide

  124. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    }
    @samjulien

    View full-size slide

  125. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    getUser$(options) {
    return from(this.authClient.getUser(options));
    }
    login() {
    this.authClient.loginWithRedirect();
    }
    logout() {
    this.authClient$.logout();
    }
    } @samjulien

    View full-size slide

  126. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    getUser$(options) {
    return from(this.authClient.getUser(options));
    }
    login() {
    this.authClient.loginWithRedirect();
    }
    logout() {
    this.authClient$.logout();
    }
    } @samjulien

    View full-size slide

  127. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    getUser$(options) {
    return from(this.authClient.getUser(options));
    }
    login() {
    this.authClient.loginWithRedirect();
    }
    logout() {
    this.authClient$.logout();
    }
    } @samjulien

    View full-size slide

  128. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    getUser$(options) {
    return from(this.authClient.getUser(options));
    }
    login() {
    this.authClient.loginWithRedirect();
    }
    logout() {
    this.authClient$.logout();
    }
    } @samjulien

    View full-size slide

  129. export class AuthService {
    handleRedirectCallback$ =
    from(this.authClient.handleRedirectCallback());
    getUser$(options) {
    return from(this.authClient.getUser(options));
    }
    login() {
    this.authClient.loginWithRedirect();
    }
    logout() {
    this.authClient$.logout();
    }
    } @samjulien

    View full-size slide

  130. Authentication service will be very thin.
    @samjulien

    View full-size slide

  131. State Side Effects
    @samjulien

    View full-size slide

  132. Side Effects
    @samjulien

    View full-size slide

  133. How do we handle events don’t
    directly change state?
    @samjulien

    View full-size slide

  134. Start login
    Handle redirect
    Log in to provider
    @samjulien

    View full-size slide

  135. Handle redirect
    Success
    Error
    @samjulien

    View full-size slide

  136. Success
    @samjulien

    View full-size slide

  137. Success
    User Token
    Authenticated
    @samjulien

    View full-size slide

  138. Success
    User Token
    Authenticated
    Redirect
    @samjulien

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  147. exhaustMap(() => {
    @samjulien
    ...

    View full-size slide

  148. exhaustMap(() => {
    @samjulien
    // handle redirect and process tokens
    this.authService.handleRedirect.pipe(
    map(({ redirectUrl }) =>
    AuthActions.handleRedirectSuccess(
    { redirectUrl }
    )),
    catchError(({ error }) =>
    AuthActions.handleRedirectFailure(
    { error }
    ))
    );

    View full-size slide

  149. exhaustMap(() => {
    @samjulien
    // handle redirect and process tokens
    this.authService.handleRedirect.pipe(
    map(({ redirectUrl }) =>
    AuthActions.handleRedirectSuccess(
    { redirectUrl }
    )),
    catchError(({ error }) =>
    AuthActions.handleRedirectFailure(
    { error }
    ))
    );

    View full-size slide

  150. exhaustMap(() => {
    @samjulien
    // handle redirect and process tokens
    this.authService.handleRedirect.pipe(
    map(({ redirectUrl }) =>
    AuthActions.handleRedirectSuccess(
    { redirectUrl }
    )),
    catchError(({ error }) =>
    AuthActions.handleRedirectFailure(
    { error }
    ))
    );

    View full-size slide

  151. exhaustMap(() => {
    @samjulien
    // handle redirect and process tokens
    this.authService.handleRedirect.pipe(
    map(({ redirectUrl }) =>
    AuthActions.handleRedirectSuccess(
    { redirectUrl }
    )),
    catchError(({ error }) =>
    AuthActions.handleRedirectFailure(
    { error }
    ))
    );

    View full-size slide

  152. exhaustMap(() => {
    @samjulien
    // handle redirect and process tokens
    this.authService.handleRedirect.pipe(
    map(({ redirectUrl }) =>
    AuthActions.handleRedirectSuccess(
    { redirectUrl }
    )),
    catchError(({ error }) =>
    AuthActions.handleRedirectFailure(
    { error }
    ))
    );

    View full-size slide

  153. exhaustMap(() => {
    @samjulien
    // handle redirect and process tokens
    this.authService.handleRedirect.pipe(
    map(({ redirectUrl }) =>
    AuthActions.handleRedirectSuccess(
    { redirectUrl }
    )),
    catchError(({ error }) =>
    AuthActions.handleRedirectFailure(
    { error }
    ))
    );

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  158. checkAuth$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.checkAuth, AuthActions.handleRedirectSuccess),
    concatMap(() =>
    this.authService.isAuthenticated$.pipe(
    map(isAuthenticated =>
    isAuthenticated
    ? AuthActions.checkAuthSuccess({ isAuthenticated })
    : AuthActions.setNotAuthenticated({ isAuthenticated })
    )
    )
    )
    )
    );
    @samjulien

    View full-size slide

  159. checkAuth$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.checkAuth, AuthActions.handleRedirectSuccess),
    concatMap(() =>
    this.authService.isAuthenticated$.pipe(
    map(isAuthenticated =>
    isAuthenticated
    ? AuthActions.checkAuthSuccess({ isAuthenticated })
    : AuthActions.setNotAuthenticated({ isAuthenticated })
    )
    )
    )
    )
    );
    @samjulien

    View full-size slide

  160. checkAuth$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.checkAuth, AuthActions.handleRedirectSuccess),
    concatMap(() =>
    this.authService.isAuthenticated$.pipe(
    map(isAuthenticated =>
    isAuthenticated
    ? AuthActions.checkAuthSuccess({ isAuthenticated })
    : AuthActions.setNotAuthenticated({ isAuthenticated })
    )
    )
    )
    )
    );
    @samjulien

    View full-size slide

  161. checkAuth$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.checkAuth, AuthActions.handleRedirectSuccess),
    concatMap(() =>
    this.authService.isAuthenticated$.pipe(
    map(isAuthenticated =>
    isAuthenticated
    ? AuthActions.checkAuthSuccess({ isAuthenticated })
    : AuthActions.setNotAuthenticated({ isAuthenticated })
    )
    )
    )
    )
    );
    @samjulien

    View full-size slide

  162. checkAuth$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.checkAuth, AuthActions.handleRedirectSuccess),
    concatMap(() =>
    this.authService.isAuthenticated$.pipe(
    map(isAuthenticated =>
    isAuthenticated
    ? AuthActions.checkAuthSuccess({ isAuthenticated })
    : AuthActions.setNotAuthenticated({ isAuthenticated })
    )
    )
    )
    )
    );
    @samjulien

    View full-size slide

  163. checkAuth$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.checkAuth, AuthActions.handleRedirectSuccess),
    concatMap(() =>
    this.authService.isAuthenticated$.pipe(
    map(isAuthenticated =>
    isAuthenticated
    ? AuthActions.checkAuthSuccess({ isAuthenticated })
    : AuthActions.setNotAuthenticated({ isAuthenticated })
    )
    )
    )
    )
    );
    @samjulien

    View full-size slide

  164. loadUser$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess),
    exhaustMap(() =>
    this.authService.getUser$().pipe(
    map(user => {
    return AuthActions.loadUserSuccess({ user });
    })
    )
    )
    )
    );
    @samjulien

    View full-size slide

  165. loadUser$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess),
    exhaustMap(() =>
    this.authService.getUser$().pipe(
    map(user => {
    return AuthActions.loadUserSuccess({ user });
    })
    )
    )
    )
    );
    @samjulien

    View full-size slide

  166. loadUser$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess),
    exhaustMap(() =>
    this.authService.getUser$().pipe(
    map(user => {
    return AuthActions.loadUserSuccess({ user });
    })
    )
    )
    )
    );
    @samjulien

    View full-size slide

  167. loadUser$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess),
    exhaustMap(() =>
    this.authService.getUser$().pipe(
    map(user => {
    return AuthActions.loadUserSuccess({ user });
    })
    )
    )
    )
    );
    @samjulien

    View full-size slide

  168. loadUser$ = createEffect(() =>
    this.actions$.pipe(
    ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess),
    exhaustMap(() =>
    this.authService.getUser$().pipe(
    map(user => {
    return AuthActions.loadUserSuccess({ user });
    })
    )
    )
    )
    );
    @samjulien

    View full-size slide

  169. Success
    User Token
    Authenticated
    @samjulien

    View full-size slide

  170. User
    Token
    Authenticated
    @samjulien

    View full-size slide

  171. User
    Token
    Authenticated
    @samjulien

    View full-size slide

  172. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  173. Effects are the brain of the authentication flow.
    @samjulien

    View full-size slide

  174. Let’s Review

    View full-size slide

  175. Auth in NgRx looks different
    than auth in vanilla Angular.
    @samjulien

    View full-size slide

  176. Feature development in NgRx
    looks different than feature
    development in vanilla Angular.
    @samjulien

    View full-size slide

  177. Start login
    Handle redirect
    Log in to provider
    @samjulien

    View full-size slide

  178. Handle redirect
    Success
    Error
    @samjulien

    View full-size slide

  179. Auth Service
    @samjulien

    View full-size slide

  180. Auth Service
    Components
    Data Services
    @samjulien

    View full-size slide

  181. State Side Effects
    @samjulien

    View full-size slide

  182. Auth Service
    Components
    Data Services
    @samjulien

    View full-size slide

  183. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  184. Reducers
    Components
    Effects
    Auth Service
    @samjulien

    View full-size slide

  185. samj.im/ngrx-auth
    @samjulien

    View full-size slide

  186. samj.im/ngrx-auth
    Thank you!
    @samjulien

    View full-size slide