Login & Auth Workflows

Single-Page Application Native Login To FusionAuth (Same Domain) With JWTs And Refresh Tokens

By Brian Pontarelli

This workflow example is used by single-page applications using a native login form inside the webapp. This login form uses an AJAX POST to send the user’s credentials (email and password) to the backend of the application. The application backend in turn calls to FusionAuth. Below is a backend-code login-page diagram that describes the primary components of this workflow and how they interact. Keep in mind that not every interaction is covered here, just the primary login interactions. At the bottom of the diagram is a discussion of the key steps.

For all of our examples, we use a store and a forum for the same company. The store requires a user to login to view their shopping cart and the forum requires the user to login to view forum posts. We also provide a couple of example attack vectors that hackers could use if portions of the system are compromised. These cases might be theoretical or based on known exploits such as XSS (cross-site scripting).

Diagram

Legend

() --> request/response bodies
{} --> request parameters
[] --> cookies
BrowserStoreForumsFusionAuthHackerIn this example FusionAuth is located at Example.com/login or Login.Example.com and returns cookies in its parent domain.InitializeLogin (inside SPA)Shopping cart loadJWT expiresRefresh JWTShopping cart loadRefresh tokenexpiresRe-login (inside SPA)SSO login to forums - not provided by FusionAuth for this workflowInitializeLogin (inside SPA)Forums loadAttack vectorsStolen Refresh tokenStolen JWTGET /1(SPA HTML, CSS & JavaScript)2AJAX GET /api/user[No cookies]3404 Missing4Render login form5AJAX POST /api/login6[Refresh token and JWT HttpOnly w/ domain: Example.com]7AJAX GET /api/load-shopping-cart[Refresh token and JWT HttpOnly w/ domain: Example.com]8(Shopping cart contents)9AJAX GET /api/load-shopping-cart[JWT HttpOnly w/ domain: store.example.com]10401 Unauthorized11AJAX POST /api/jwt/refresh[Refresh token and JWT HttpOnly w/ domain Example.com]12[JWT HttpOnly w/ domain: Example.com]13AJAX GET /api/load-shopping-cart[Refresh token and JWT HttpOnly w/ domain: Example.com]14(Shopping cart contents)15AJAX GET /api/load-shopping-cart[JWT HttpOnly w/ domain: store.example.com]16401 Unauthorized17AJAX POST /api/jwt/refresh[Refresh token and JWT HttpOnly w/ domain Example.com]18404 Missing19Login same as above20GET /[No cookies]21(SPA HTML, CSS & JavaScript)22GET /api/user[No cookies]23404 Missing24Render login form25AJAX POST /api/login[Refresh token and JWT HttpOnly w/ domain: Example.com - FOR WRONG APP]26[New Refresh token and JWT HttpOnly w/ domain: Example.com]27Refresh token and JWT cookies from Store getsclobbered by Refresh token and JWT for Forums28AJAX GET /api/load-posts[Refresh token and JWT HttpOnly w/ domain: Example.com]29(Forum posts)30POST /api/jwt/refresh[Refresh token HttpOnly w/ domain Example.com]31[JWT HttpOnly w/ domain: Example.com]32GET /api/load-shopping-cart[Refresh token and JWT HttpOnly w/ domain: Example.com]33(Shopping cart contents)34GET /api/load-shopping-cart[JWT HttpOnly w/ domain: Example.com]35(Shopping cart contents)36BrowserStoreForumsFusionAuthHacker

Explanation

  1. The browser requests the shopping cart single-page application from the application backend
  2. The application backend responds with the HTML, CSS & JavaScript of the application
  3. The browser loads the application and as part of the initialization process, it makes a request to the application backend to see if the user is logged in
  4. The application backend responds with a 404 indicating the user is not logged in
  5. The application renders the login form
  6. The user inputs their credentials and clicks the submit button. The browser AJAX POSTs the form data directly to the Login API in FusionAuth
  7. FusionAuth returns a 200 status code stating that the credentials were okay. It also returns a JWT and a refresh token in cookies with the same domain as the application
  8. The browser requests the user's shopping cart via AJAX from the application backend and includes the JWT and refresh token cookies
  9. The application backend verifies the JWT and then uses the JWT to identify the user. Once the user is identified, the backend looks up the user's shopping cart from the database (or similar location). Finally, the application backend returns the user's shopping cart contents (usually as JSON)
  10. A while later, the user's JWT expires and the user clicks on their shopping cart again. The browser requests the user's shopping cart via AJAX from the application backend and includes the JWT cookie
  11. The application backend responds with a 401, indicating that the JWT has expired
  12. The application recognizes that the JWT has expired and makes a request directly to the JWT refresh API in FusionAuth. This request includes the refresh token cookie
  13. FusionAuth looks up the refresh token and returns a new JWT (either in the body or as an HttpOnly cookie depending on the workflow)
  14. The browser requests the user's shopping cart via AJAX from the application backend and includes the JWT and refresh token cookies
  15. The application backend verifies the JWT and then uses the JWT to identify the user. Once the user is identified, the backend looks up the user's shopping cart from the database (or similar location). Finally, the application backend returns the user's shopping cart contents (usually as JSON)
  16. A while later, the user's JWT expires and the user clicks on their shopping cart again. The browser requests the user's shopping cart via AJAX from the application backend and includes the JWT cookie
  17. The application backend responds with a 401, indicating that the JWT has expired
  18. The application recognizes that the JWT has expired and makes a request directly to the JWT refresh API in FusionAuth. This request includes the refresh token cookie
  19. Since the refresh token has expired, FusionAuth returns a 404 status code
  20. At this point, the application can allow the user to log in the same way they did above
  21. The browser requests the forums single-page application from the application backend. This is a standard SSO login, but because of the way this workflow manages cookies and identities, FusionAuth does not provide SSO capabilities automatically
  22. The application backend responds with the HTML, CSS & JavaScript of the application
  23. The browser loads the application and as part of the initialization process, it makes a request to the application backend to see if the user is logged in
  24. The application backend responds with a 404 indicating the user is not logged in
  25. The application renders the login form
  26. The user inputs their credentials and clicks the submit button. The browser AJAX POSTs the form data directly to the Login API in FusionAuth. The refresh token cookie from the Store application is sent to FusionAuth here as well. **NOTE** this refresh token cookie is for the wrong application
  27. FusionAuth returns a 200 status code stating that the credentials were okay. It also returns a JWT and a refresh token in cookies with the same domain as the application
  28. The browser updates the cookie that stores the refresh token to the new cookie value for the forums. This clobbers the refresh token for the store and will force the user to log into the store next time they open that application
  29. The browser requests the user's forum posts from the application backend via AJAX and includes the JWT and refresh token cookies
  30. The application backend verifies the JWT and then uses the JWT to identify the user. Once the user is identified, the backend looks up the user's forum posts from the database (or similar location). Finally, the application backend returns the user's forum posts that the browser renders (usually as JSON)
  31. This is an attack vector where the attacker has stolen the user's refresh token. Here, the attacker can request directly to the JWT refresh API in FusionAuth since it is the same request the browser is making. The attacker includes the refresh token cookie in the request
  32. FusionAuth looks up the refresh token and returns a new JWT
  33. The attacker requests the user's shopping cart with the JWT
  34. The application backend uses the JWT to look up the user's shopping cart. It responds to the attacker with the user's shopping cart (usually as JSON)
  35. This is an attack vector where the attacker has stolen the user's JWT. Here, the attacker requests the user's shopping cart with the stolen JWT
  36. The application backend verifies the JWT and then uses the JWT to identify the user. Once the user is identified, the backend looks up the user's shopping cart from the database (or similar location). Finally, the application backend returns the user's shopping cart to the attacker (usually as JSON)

Security considerations

This workflow is more secure than the other workflows that call FusionAuth directly because the JWT and refresh token are both stored in HttpOnly cookies. The downside of this workflow is that there are no built in SSO capabilities provided by FusionAuth. It forces the developer to build out an SSO solution themselves.

Additionally, this workflow requires that FusionAuth be deployed at the top-level domain for the entire organization or that a proxy be used to rewrite the cookies that FusionAuth sends back as part of the Login API so that they are in the correct domain.

APIs used

Here are the FusionAuth APIs used in this example: