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

Demystifying Token Authentication in NgRx (ngIn...

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.

Sam Julien

February 29, 2020
Tweet

More Decks by Sam Julien

Other Decks in Technology

Transcript

  1. @samjulien Sam Julien @samjulien Sr. 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 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
  10. export const logIn = createAction( ‘[Auth] Start Log In’ );

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    const selectAccessToken = createSelector( selectAuthStatus, fromAuthStatus.selectAccessToken );
  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 { 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. exhaustMap(() => { @samjulien // handle redirect and process tokens

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

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

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

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

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

    this.authService.handleRedirect.pipe( map(({ redirectUrl }) => AuthActions.handleRedirectSuccess( { redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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