The Role of Effects in NgRx Authentication

The Role of Effects in NgRx Authentication

Building token authentication into an NgRx application can be overwhelming and confusing. Authentication is already a big, scary subject and so is NgRx. When you put them together, things get confusing fast. In this talk, we’ll do a comparison of how authentication in a vanilla Angular app differs from auth in an NgRx app. We’ll then see how the central nervous system of authentication shifts from a service in regular Angular to Effects in NgRx.

This talk will be practical and code-driven, leaving you equipped to tackle adding this feature to your NgRx application at work on Monday. You’ll learn how Effects can handle loading authentication state, navigate users to protected routes, and process tokens received from an identity provider. By the end, you’ll feel way better about tackling auth in NgRx!

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

April 03, 2020
Tweet

Transcript

  1. The Role of Effects in NgRx Authentication

  2. @samjulien

  3. Effects are one of the hardest parts of learning NgRx.

    @samjulien
  4. Effects are powerful and can do many things. @samjulien

  5. Effects are powerful and can do many things. @samjulien Effects

    aren’t as concrete as Actions or Reducers.
  6. @samjulien

  7. @samjulien +

  8. Effects are a huge part of implementing auth in NgRx.

    @samjulien
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. The Role of Effects in NgRx Auth @samjulien

  16. The Role of Effects in NgRx Auth @samjulien Effects are

    the DJ of NgRx
  17. The Role of Effects in NgRx Auth @samjulien Effects are

    the DJ of NgRx Common Struggles with Effects
  18. The Role of Effects in NgRx Auth @samjulien Effects are

    the DJ of NgRx Common Struggles with Effects Effects in the Log In Flow
  19. @samjulien Sam Julien @samjulien

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

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

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

    GDE & Angular Collaborator UpgradingAngularJS.com, Thinkster, & egghead
  23. The Role of Effects in NgRx Auth @samjulien Effects are

    the DJ of NgRx Common Struggles with Effects Effects in the Log In Flow
  24. None
  25. @samjulien

  26. Services Components @samjulien

  27. @samjulien

  28. Services Components @samjulien

  29. Reducers Components Effects @samjulien

  30. Reducers Components Effects @samjulien

  31. Effects @samjulien

  32. Effects @samjulien Decide when to call services

  33. Effects @samjulien Decide when to call services Communicate with APIs

  34. Effects @samjulien Handle control flow logic Decide when to call

    services Communicate with APIs
  35. @samjulien Handle control flow logic

  36. Effects function as a task runner. @samjulien

  37. Effects are the brain of NgRx. @samjulien

  38. None
  39. The Role of Effects in NgRx Auth @samjulien Effects are

    the DJ of NgRx Common Struggles with Effects Effects in the Log In Flow
  40. Effects: The Struggle @samjulien

  41. Effects: The Struggle @samjulien Actions that only trigger Effects

  42. @samjulien Actions that only trigger Effects Actions that don’t touch

    the Reducer to modify state.
  43. Effects: The Struggle @samjulien Actions that only trigger Effects

  44. Effects: The Struggle @samjulien Actions that only trigger Effects Non-dispatching

    Effects
  45. @samjulien Effects that don’t return an Action. Non-dispatching Effects

  46. Effects: The Struggle @samjulien Actions that only trigger Effects Non-dispatching

    Effects
  47. Effects: The Struggle @samjulien Actions that only trigger Effects Non-dispatching

    Effects Writing complex Effects
  48. @samjulien Trying to handle multiple side effects at once. Writing

    complex Effects
  49. @samjulien +

  50. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  51. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  52. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  53. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  54. The Role of Effects in NgRx Auth @samjulien Effects are

    the DJ of NgRx Common Struggles with Effects Effects in the Log In Flow
  55. @samjulien +

  56. @samjulien

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

  58. Handle redirect Success Error @samjulien

  59. Success User Token @samjulien

  60. Success User Token Redirect @samjulien

  61. @samjulien

  62. Auth Service @samjulien

  63. @samjulien Auth Service

  64. @samjulien Store token and user in memory Auth Service

  65. @samjulien Store token and user in memory Interact with auth

    SDK Auth Service
  66. @samjulien Store token and user in memory Interact with auth

    SDK Handle all redirect logic Auth Service
  67. Auth Service Components @samjulien

  68. Auth Service Components Data Services @samjulien

  69. @samjulien

  70. Auth Service Components Data Services @samjulien

  71. Reducers Components Effects Auth Service @samjulien

  72. @samjulien +

  73. @samjulien

  74. Which parts of the auth flow directly change State? @samjulien

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

  76. Handle redirect Success Error @samjulien

  77. Success @samjulien

  78. Success User Token @samjulien

  79. Success User Token Redirect @samjulien

  80. Success User Token Redirect @samjulien

  81. export interface State { userProfile: UserProfile; accessToken: AccessToken; } @samjulien

  82. Everything else is a side effect! @samjulien

  83. Everything else needs Effects! @samjulien

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

  85. Start login Log in to provider @samjulien

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

    @samjulien
  87. logIn$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.logIn), tap(() => this.authService.logIn()) ),

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

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

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

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

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

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

    ), { dispatch: false } ); @samjulien logIn()
  94. export class AuthService { // other methods hidden logIn() {

    this.authClient.loginWithRedirect(); } } @samjulien
  95. export class AuthService { // other methods hidden logIn() {

    this.authClient.loginWithRedirect(); } } @samjulien
  96. logIn$ = createEffect(() => this.actions$.pipe( ofType(AuthActions.logIn), tap(() => this.authService. )

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

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

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

    { dispatch: false } ); @samjulien
  100. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  101. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  102. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  103. Effects function as a task runner. @samjulien

  104. None
  105. “Hey DJ, play my fave song!” @samjulien

  106. “Hey DJ, play my fave song!” @samjulien

  107. “Hey DJ, play my fave song!” @samjulien Grab a record

  108. “Hey DJ, play my fave song!” @samjulien Grab a record

    Put it on the player
  109. “Hey DJ, play my fave song!” @samjulien Grab a record

    Put it on the player Play the song
  110. “Hey DJ, play my fave song!” @samjulien Grab a record

    Put it on the player Play the song
  111. @samjulien “Hey app, log me in!”

  112. “Hey app, log me in!” @samjulien

  113. “Hey app, log me in!” @samjulien Call the auth service

    to log in
  114. “Hey app, log me in!” @samjulien Call the auth service

    to log in Handle the redirect flow
  115. “Hey app, log me in!” @samjulien Call the auth service

    to log in Handle the redirect flow Update the store
  116. “Hey app, log me in!” @samjulien Call the auth service

    to log in Handle the redirect flow Update the store
  117. Start login Handle redirect Log in to provider @samjulien

  118. Handle redirect Success Error @samjulien

  119. Success User Token Redirect @samjulien

  120. export const handleRedirect = createAction( ‘[Auth] Handle redirect’ ); @samjulien

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

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

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

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

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

    ) ); ... })
  126. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  127. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  128. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) ); Calls Auth SDK to get *
  129. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  130. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  131. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  132. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  133. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  134. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  135. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  136. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  137. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  138. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  139. Find the hidden side effect. @samjulien

  140. Handle redirect Success Error @samjulien

  141. Success User Token Redirect @samjulien

  142. Break a complex Effect into smaller pieces. @samjulien

  143. Let one Effect handle one side effect. @samjulien

  144. Handle Redirect Flow @samjulien

  145. Handle Redirect Flow @samjulien Call the auth service to process

  146. Handle Redirect Flow @samjulien Call the auth service to process

    Update the store
  147. Handle Redirect Flow @samjulien Call the auth service to process

    Update the store Redirect the user
  148. Handle Redirect Flow @samjulien Call the auth service to process

    Update the store Redirect the user
  149. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  150. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  151. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    { // Set the user & token somewhere?? // Navigate to the redirectUrl?? return AuthActions.handleRedirectSuccess(); }), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  152. exhaustMap(() => { @samjulien this.authService.handleRedirect.pipe( map(({ payload, redirectUrl }) =>

    AuthActions.handleRedirectSuccess( { payload, redirectUrl } )), catchError(({ error }) => AuthActions.handleRedirectFailure( { error } )) );
  153. @samjulien Back to the reducer…

  154. on(AuthActions.handleRedirectSuccess, (state, { payload }) => { return { ...state,

    token: payload.accessToken, user: payload.userProfile }; }); @samjulien
  155. on(AuthActions.handleRedirectSuccess, (state, { payload }) => { return { ...state,

    token: payload.accessToken, user: payload.userProfile }; }); @samjulien
  156. on(AuthActions.handleRedirectSuccess, (state, { payload }) => { return { ...state,

    token: payload.accessToken, user: payload.userProfile }; }); @samjulien
  157. @samjulien …and then create a new Effect.

  158. handleRedirectSuccess$ = createEffect( () => this.actions$.pipe( ofType(AuthActions.handleRedirectSuccess), tap(({ redirectUrl })

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

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

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

    => this.router.navigate([redirectUrl])) ), { dispatch: false } ); @samjulien
  162. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  163. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects ✅
  164. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects ✅ ✅
  165. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects ✅ ✅ ✅
  166. Let’s Review

  167. Effects are one of the hardest parts of learning NgRx.

    @samjulien
  168. Effects are a huge part of implementing auth in NgRx.

    @samjulien
  169. @samjulien

  170. Auth Service Components Data Services @samjulien

  171. @samjulien

  172. Reducers Components Effects Auth Service @samjulien

  173. Effects @samjulien

  174. Effects @samjulien Decide when to call services

  175. Effects @samjulien Decide when to call services Communicate with APIs

  176. Effects @samjulien Handle control flow logic Decide when to call

    services Communicate with APIs
  177. None
  178. Effects: The Struggle @samjulien Actions that only trigger Effects Non-dispatching

    Effects Writing complex Effects
  179. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  180. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  181. Start login Log in to provider @samjulien

  182. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  183. Effects function as a task runner. @samjulien

  184. “Hey app, log me in!” @samjulien Call the auth service

    to log in Handle the redirect flow Update the store
  185. Handle redirect Success Error @samjulien

  186. Success User Token Redirect @samjulien

  187. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  188. Find the hidden side effect. @samjulien

  189. Break a complex Effect into smaller pieces. @samjulien

  190. Let one Effect handle one side effect. @samjulien

  191. Handle Redirect Flow @samjulien Call the auth service to process

    Update the store Redirect the user
  192. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects
  193. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects ✅
  194. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects ✅ ✅
  195. Effects in NgRx Auth @samjulien Actions that only trigger Effects

    Non-dispatching Effects Writing complex Effects ✅ ✅ ✅
  196. None
  197. None
  198. None
  199. None
  200. @samjulien NgRx Conf November 5-6, 2020 U.S. Space & Rocket

    Center, Huntsville, AL https://ti.to/ngrx-conf/2020/discount/ngconf
  201. samj.im/ngconf-hardwired @samjulien

  202. samj.im/ngconf-hardwired Thank you! @samjulien