Authentication flows are crucial for frontend applications to verify a user's identity and manage their access. Traditionally, server-side sessions managed user state, often using HTTP-only cookies to store a session ID. While robust against XSS, these can be vulnerable to CSRF without proper SameSite cookie policies. Modern frontends increasingly leverage stateless token-based authentication, where the server issues a token (like a JWT) upon successful login. OAuth 2.0, often combined with OpenID Connect (OIDC), is not an authentication protocol itself, but an authorization framework allowing users to grant third-party applications limited access to their resources without sharing credentials directly. For instance, "Login with Google" uses OAuth to delegate authorization, while OIDC provides the actual identity layer.
The secure storage of these tokens on the frontend is paramount. Direct storage in localStorage or sessionStorage is vulnerable to Cross-Site Scripting (XSS) attacks, where malicious JavaScript could steal the tokens and impersonate the user. For highly sensitive tokens like refresh tokens, which are used to obtain new, short-lived access tokens without re-authenticating, an HTTP-only and SameSite=Lax/Strict cookie is generally the most secure option. This prevents client-side JavaScript from accessing the token (XSS protection) and offers built-in CSRF protection. Access tokens, being shorter-lived, are often stored in memory (e.g., in a state management system) or sometimes in localStorage with careful consideration of XSS prevention and frequent rotation.
From a practical frontend perspective, your application will typically receive an access token and potentially a refresh token after a successful login (either direct or via an OAuth provider). The access token is then included in the Authorization header of subsequent API requests (e.g., Authorization: Bearer <access_token>). When the access token expires, your application must detect this (e.g., a 401 Unauthorized response from the API) and use the refresh token (if available and securely stored) to request a new access token from your backend. Implementing a robust refresh token flow, often with token rotation and short-lived access tokens, significantly enhances security by limiting the window of opportunity for token compromise.
Key Takeaways
- OAuth is an authorization delegation framework; OIDC builds on it for authentication.
- HTTP-only, SameSite cookies are generally the most secure way to store sensitive refresh tokens against XSS and CSRF.
- Access tokens are typically short-lived and sent in the
Authorizationheader for API calls. - Avoid storing sensitive tokens directly in
localStoragedue to XSS vulnerabilities. - Implement a refresh token rotation strategy to maintain user sessions securely without frequent re-logins.
Code Example
How this code works
This fetchWithAuth function provides a secure and automated way to make network requests on the frontend. Its main job is to ensure every outgoing request automatically carries a user's authentication token, proving their identity, and to intelligently handle common authentication issues like expired sessions.
The function first tries to retrieve the user's accessToken using localStorage.getItem. If no token is found, it logs a message and returns null, indicating the user isn't authenticated. If an accessToken exists, it constructs an Authorization header with Bearer ${accessToken}, which is then included in the options for the standard fetch call. A crucial and subtle part for beginners is the if (response.status === 401) check. This line actively monitors for an expired or invalid token response from the server. Receiving a 401 status signals that the accessToken is no longer valid, and as a simplified immediate response, the code redirects to /login to prompt re-authentication. If the request is successful and doesn't return a 401, the function returns the parsed response.json() data.