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. @samjulien Sam Julien @samjulien Developer Advocate Engineer at Auth0 GDE

    & Angular Collaborator UpgradingAngularJS.com, Thinkster, & Egghead @samjulien
  2. State What do I need to keep track of? Side

    Effects What events don’t directly change state? @samjulien
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. 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
  9. export const logIn = createAction( ‘[Auth] Start Log In’ );

    @samjulien export const logOut = createAction( '[Auth] Log out' );
  10. export const handleRedirectSuccess = createAction( '[Auth] Handle redirect success', props<{

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

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

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

    const loadUserSuccess = createAction( '[Auth] Load user success', props<{ user: User }>() ); @samjulien
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. @samjulien export const selectUser = (state: State) => state.user; export

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

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

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

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

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

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

    const selectAccessToken = createSelector( selectAuthStatus, fromAuthStatus.selectAccessToken );
  29. 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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. exhaustMap(() => { @samjulien // handle redirect and process tokens

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

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

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

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

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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