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. 4.
  2. 5.
  3. 12.
  4. 19.

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

    & Angular Collaborator UpgradingAngularJS.com, Thinkster, & Egghead @samjulien
  5. 22.
  6. 24.
  7. 36.
  8. 37.
  9. 38.
  10. 39.
  11. 43.

    State What do I need to keep track of? Side

    Effects What events don’t directly change state? @samjulien
  12. 49.
  13. 53.
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 77.
  21. 87.

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

    @samjulien export const logOut = createAction( '[Auth] Log out' );
  22. 89.

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

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

    export const loadUser = createAction( '[Auth] Load user’ ); export

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

    export const loadUser = createAction( '[Auth] Load user’ ); export

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

    export const loadUser = createAction( '[Auth] Load user’ ); export

    const loadUserSuccess = createAction( '[Auth] Load user success', props<{ user: User }>() ); @samjulien
  26. 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
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 110.
  35. 115.
  36. 117.
  37. 118.
  38. 120.
  39. 124.

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

    const selectIsAuthenticated = (state: State) => state.isAuthenticated; export const selectAccessToken = (state: State) => state.accessToken;
  40. 125.

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

    const selectIsAuthenticated = (state: State) => state.isAuthenticated; export const selectAccessToken = (state: State) => state.accessToken;
  41. 126.

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

    const selectIsAuthenticated = (state: State) => state.isAuthenticated; export const selectAccessToken = (state: State) => state.accessToken;
  42. 127.

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

    const selectIsAuthenticated = (state: State) => state.isAuthenticated; export const selectAccessToken = (state: State) => state.accessToken;
  43. 128.

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

    const selectAccessToken = createSelector( selectAuthStatus, fromAuthStatus.selectAccessToken );
  44. 129.

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

    const selectAccessToken = createSelector( selectAuthStatus, fromAuthStatus.selectAccessToken );
  45. 130.

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

    const selectAccessToken = createSelector( selectAuthStatus, fromAuthStatus.selectAccessToken );
  46. 135.
  47. 137.
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 148.
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 156.
  63. 160.
  64. 175.

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  65. 176.

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  66. 177.

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  67. 178.

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  68. 179.

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  69. 180.

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 201.
  77. 204.
  78. 205.
  79. 207.
  80. 210.
  81. 213.
  82. 217.
  83. 218.
  84. 219.
  85. 220.
  86. 222.
  87. 223.