Pro Yearly is on sale from $80 to $50! »

Demystifying NgRx Authentication (AngularMix)

7beed3a6fa39e12c9e873b903e4d9244?s=47 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.

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

November 21, 2019
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. Auth in NgRx looks different than auth in vanilla Angular.

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

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

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

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

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

    & Angular Collaborator @samjulien
  19. @samjulien Sam Julien @samjulien 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 Auth in NgRx requires a different mental model than

    auth in vanilla Angular.
  24. None
  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. State Side Effects @samjulien

  41. State Side Effects @samjulien

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

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

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

  45. State Video Game Collection Side Effects @samjulien

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

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

  48. State Game Ownership Side Effects @samjulien

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

    to the collection. @samjulien
  50. State @samjulien

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

  52. Where do I keep it? @samjulien

  53. The Store

  54. What goes in the store? @samjulien

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

  56. Handle redirect Success Error @samjulien

  57. Success @samjulien

  58. Success User Token Authenticated @samjulien

  59. Success User Token Authenticated Redirect @samjulien

  60. @samjulien

  61. Auth Service @samjulien

  62. 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
  63. 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
  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. @samjulien

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

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

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

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

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

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

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

  76. What actions do we need? @samjulien

  77. Actions

  78. @samjulien

  79. @samjulien

  80. Events State Changes @samjulien

  81. Events @samjulien

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

  83. Handle redirect Success Error @samjulien

  84. Success @samjulien

  85. Success User Token Authenticated @samjulien

  86. Success User Token Authenticated Redirect @samjulien

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

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

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

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

  91. State Changes @samjulien

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

  93. Handle redirect Success Error @samjulien

  94. Success @samjulien

  95. Success User Token Authenticated @samjulien

  96. Success User Token Authenticated Redirect @samjulien

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

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

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

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

    const loadUserSuccess = createAction( '[Auth] Load user success', props<{ user: User }>() ); @samjulien
  101. 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
  102. 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
  103. 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
  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 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
  106. 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
  107. 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
  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. Where does state change? @samjulien

  110. Reducers

  111. Defining Reducers @samjulien

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

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

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

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

    ...state, accessToken, }; } ) @samjulien
  116. How do I read state in components? @samjulien

  117. Selectors

  118. @samjulien

  119. Auth Service Components @samjulien

  120. @samjulien

  121. Reducers Components Effects Auth Service @samjulien

  122. Reducers Components Effects Auth Service @samjulien Selectors

  123. Defining Selectors @samjulien

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

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

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

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

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

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

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

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

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

  133. Auth Service

  134. State Side Effects @samjulien

  135. None
  136. Reducers Components Effects Auth Service @samjulien

  137. @samjulien

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

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

  140. 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
  141. 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
  142. 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
  143. 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
  144. 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
  145. 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
  146. 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
  147. 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
  148. @samjulien

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

  150. 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
  151. 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
  152. 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
  153. 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
  154. 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
  155. Authentication service will be very thin. @samjulien

  156. None
  157. State Side Effects @samjulien

  158. Side Effects @samjulien

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

  160. Effects

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

  162. Handle redirect Success Error @samjulien

  163. Success @samjulien

  164. Success User Token Authenticated @samjulien

  165. Success User Token Authenticated Redirect @samjulien

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  185. 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
  186. 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
  187. 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
  188. 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
  189. 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
  190. 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
  191. loadUser$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.loadUser, AuthActions.checkAuthSuccess), exhaustMap(() => this.authService.getUser$().pipe(

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

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

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

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

    map(user => { return AuthActions.loadUserSuccess({ user }); }) ) ) ) ); @samjulien
  196. Success User Token Authenticated @samjulien

  197. User Token Authenticated @samjulien

  198. User Token Authenticated @samjulien

  199. Reducers Components Effects Auth Service @samjulien

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

  201. None
  202. Let’s Review

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

    @samjulien
  204. None
  205. None
  206. Feature development in NgRx looks different than feature development in

    vanilla Angular. @samjulien
  207. None
  208. Start login Handle redirect Log in to provider @samjulien

  209. Handle redirect Success Error @samjulien

  210. @samjulien

  211. Auth Service @samjulien

  212. Auth Service Components Data Services @samjulien

  213. @samjulien

  214. State Side Effects @samjulien

  215. Auth Service Components Data Services @samjulien

  216. Reducers Components Effects Auth Service @samjulien

  217. None
  218. None
  219. None
  220. None
  221. Reducers Components Effects Auth Service @samjulien

  222. None
  223. None
  224. samj.im/ngrx-auth @samjulien

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