Demystifying Token Authentication in NgRx (ngIndia)

7beed3a6fa39e12c9e873b903e4d9244?s=47 Sam Julien
February 29, 2020

Demystifying Token Authentication in NgRx (ngIndia)

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, the role of Effects, and keeping your application safe using authentication best practices.

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

February 29, 2020
Tweet

Transcript

  1. Demystifying Token Authentication in NgRx

  2. @samjulien

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

  4. None
  5. None
  6. Authentication is a Big Scary Subject. @samjulien

  7. …with lots of jargon. @samjulien

  8. …with lots of jargon vocab. @samjulien

  9. NgRx is a Big Scary Subject. @samjulien

  10. …with lots of jargon. @samjulien

  11. …with lots of jargon vocab. @samjulien

  12. None
  13. Is auth the same in NgRx as vanilla Angular? @samjulien

  14. Auth in NgRx looks different than auth in vanilla Angular.

    @samjulien
  15. Feature development in NgRx looks different than feature development in

    vanilla Angular. @samjulien
  16. @samjulien Sam Julien @samjulien @samjulien

  17. @samjulien Sam Julien @samjulien Sr. Developer Advocate Engineer at Auth0

    @samjulien
  18. @samjulien Sam Julien @samjulien Sr. Developer Advocate Engineer at Auth0

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

    GDE & Angular Collaborator UpgradingAngularJS.com, Thinkster, & Egghead @samjulien
  20. Auth in NgRx looks different than auth in vanilla Angular.

    @samjulien
  21. Feature development in NgRx looks different than feature development in

    vanilla Angular. @samjulien
  22. None
  23. @samjulien

  24. @samjulien

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

  26. Handle redirect Success Error @samjulien

  27. @samjulien

  28. Auth Service @samjulien

  29. Auth Service Components @samjulien

  30. Auth Service Components Data Services @samjulien

  31. @samjulien

  32. Auth Service Components Data Services @samjulien

  33. Reducers Components Effects Auth Service @samjulien

  34. Auth Service Components Data Services @samjulien

  35. Reducers Components Effects Auth Service @samjulien

  36. None
  37. None
  38. None
  39. None
  40. Reducers Components Effects Auth Service @samjulien

  41. @samjulien

  42. State Side Effects @samjulien

  43. State Side Effects @samjulien

  44. State What do I need to keep track of? Side

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

    Effects What events don’t directly change state? @samjulien
  46. State Side Effects @samjulien

  47. State Video Game Collection Side Effects @samjulien

  48. State Video Game Collection Side Effects Call the API to

    get the collection. @samjulien
  49. State Side Effects @samjulien

  50. State Game Ownership Side Effects @samjulien

  51. State Game Ownership Side Effects Call the API to add

    to the collection. @samjulien
  52. State @samjulien

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

  54. Where do I keep it? @samjulien

  55. The Store

  56. What goes in the store? @samjulien

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

  58. Handle redirect Success Error @samjulien

  59. Success @samjulien

  60. Success User Token Authenticated @samjulien

  61. Success User Token Authenticated Redirect @samjulien

  62. @samjulien

  63. Auth Service @samjulien

  64. export class AuthService { isAuthenticated: boolean = null; private userProfileSubject$

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

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

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

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

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

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

    = new BehaviorSubject<any>(null); userProfile$ = this.userProfileSubject$.asObservable(); private tokenSubject$ = new BehaviorSubject<any>(null); accessToken$ = this.userProfileSubject$.asObservable(); } @samjulien
  71. @samjulien

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

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

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

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

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

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

    } @samjulien
  78. What messages do we need about state? @samjulien

  79. What actions do we need? @samjulien

  80. Actions

  81. @samjulien

  82. @samjulien

  83. Events State Changes @samjulien

  84. Events @samjulien

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

  86. Handle redirect Success Error @samjulien

  87. export const logIn = createAction( ‘[Auth] Start Log In’ );

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

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

    @samjulien export const logOut = createAction( '[Auth] Log out' );
  90. export const handleRedirect = createAction( ‘[Auth] Handle redirect’ ); @samjulien

  91. export const handleRedirectSuccess = createAction( '[Auth] Handle redirect success', props<{

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

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

  94. State Changes @samjulien

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

  96. Handle redirect Success Error @samjulien

  97. Success @samjulien

  98. Success User Token Authenticated @samjulien

  99. Success User Token Authenticated Redirect @samjulien

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

    } @samjulien
  101. export const loadUser = createAction( '[Auth] Load user’ ); export

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

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

    const loadUserSuccess = createAction( '[Auth] Load user success', props<{ user: User }>() ); @samjulien
  104. 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
  105. 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
  106. 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
  107. 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
  108. 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
  109. 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
  110. 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
  111. 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
  112. We’ve got messages about changing state, but where does state

    actually change? @samjulien
  113. Reducers

  114. Defining Reducers @samjulien

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

  116. on( AuthActions.checkAuthSuccess, AuthActions.setNotAuthenticated, (state, { isAuthenticated }) => { return

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

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

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

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

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

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

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

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

    ...state, accessToken, }; } ) @samjulien
  125. How do we read state in components? @samjulien

  126. Selectors

  127. @samjulien

  128. Auth Service Components @samjulien

  129. @samjulien

  130. Reducers Components Effects Auth Service @samjulien

  131. Reducers Components Effects Auth Service @samjulien Selectors

  132. Defining Selectors @samjulien

  133. @samjulien export const selectUser = (state: State) => state.user; export

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

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

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

    const selectIsAuthenticated = (state: State) => state.isAuthenticated; export const selectAccessToken = (state: State) => state.accessToken;
  137. @samjulien export const selectUser = createSelector( selectAuthStatus, fromAuthStatus.selectUser ); export

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

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

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

  141. What about the auth calls? @samjulien

  142. Auth Service

  143. State Side Effects @samjulien

  144. None
  145. Reducers Components Effects Auth Service @samjulien

  146. @samjulien

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

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

  149. 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
  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. 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
  156. 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
  157. 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
  158. 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
  159. @samjulien

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

  161. 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
  162. 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
  163. 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
  164. 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
  165. 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
  166. The authentication service is a thin wrapper for the SDK.

    @samjulien
  167. None
  168. State Side Effects @samjulien

  169. Side Effects @samjulien

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

  171. Effects

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

  173. Handle redirect Success Error @samjulien

  174. Success @samjulien

  175. Success User Token Authenticated @samjulien

  176. Success User Token Authenticated Redirect @samjulien

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

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

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

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

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

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

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

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

    ) ); ... })
  185. exhaustMap(() => { @samjulien ...

  186. exhaustMap(() => { @samjulien // handle redirect and process tokens

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

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

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

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

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  192. handleRedirectSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.handleRedirectSucess), tap(({ redirectUrl })

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

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

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

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  196. 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
  197. 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
  198. 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
  199. 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
  200. 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
  201. 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
  202. 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
  203. 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
  204. on( AuthActions.checkAuthSuccess, AuthActions.setNotAuthenticated, (state, { isAuthenticated }) => { return

    { ...state, isAuthenticated, }; } ) @samjulien
  205. loadUser$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess), exhaustMap(() => this.authService.getUser$().pipe(

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

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

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

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

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

    ...state, user, }; }), @samjulien
  211. Success User Token Authenticated @samjulien

  212. User Token Authenticated @samjulien

  213. User Token Authenticated @samjulien

  214. Reducers Components Effects Auth Service @samjulien

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

  216. None
  217. Let’s Review

  218. None
  219. None
  220. Auth in NgRx looks different than auth in vanilla Angular.

    @samjulien
  221. Feature development in NgRx looks different than feature development in

    vanilla Angular. @samjulien
  222. None
  223. Start login Handle redirect Log in to provider @samjulien

  224. Handle redirect Success Error @samjulien

  225. @samjulien

  226. Auth Service @samjulien

  227. Auth Service Components Data Services @samjulien

  228. @samjulien

  229. State Side Effects @samjulien

  230. Auth Service Components Data Services @samjulien

  231. Reducers Components Effects Auth Service @samjulien

  232. None
  233. None
  234. None
  235. None
  236. Reducers Components Effects Auth Service @samjulien

  237. None
  238. None
  239. samj.im/ngindia @samjulien

  240. samj.im/ngindia Thank you! @samjulien