Authentication tips on multiple frontend domains
There are situations when the application has more than one frontend domain such as Pro6pp that uses both pro6pp.nl, and pro6pp.com. In these cases there are some limitations when performing authentication actions and also requests that require authentication on cross-site domains.
CORS
The backend server must always be aware of all domains from which requests can originate. Otherwise, these requests will be blocked by the browser due to the same-origin policy.
To configure Cross-Origin Resource Sharing (CORS), libraries such as Flask-CORS can be used to explicitly allow requests from specific domains.
Flask Backend Using Flask-JWT-Extended
The Flask-JWT-Extended library supports multiple strategies for handling JSON Web Tokens (JWT). These strategies depend on where the JWT is stored, such as:
- Headers
- Cookies
- Query String
- JSON Body
This guide will focus on the Cookies and JSON Body approaches and their implications when working with multiple frontend domains.
Storing JWT in Cookies
When storing JWTs in cookies, the set_access_cookies
function from Flask-JWT-Extended
can be used to append the token
to the response as part of the Set-Cookie
header. This allows the browser to automatically manage cookies without additional
frontend logic.
However, cross-domain scenarios present unique challenges:
Challenges with Cross-Domain Cookies
Consider the following setup:
- Frontend domains:
pro6pp.nl
andpro6pp.com
- API domain:
api.pro6pp.nl
When a user logs in, the API generates a JWT and sets it in the response cookies using set_access_cookies
. The response
header includes:
Set-Cookie: access_token_cookie=<token>; Path=/; Domain=pro6pp.nl; Secure; HttpOnly
Problem
While cookies for the .nl
domain are set successfully, the browser cannot set cookies for the .com
domain due to
cross-domain restrictions. Even if the Domain
attribute of the cookie is adjusted to pro6pp.com
, browsers generally
block such cookies for security reasons.
Current Implementation
To handle cross-domain requests, the backend determines the origin of the request using:
domain = flask.request.headers.get("Origin")
This domain value is processed to remove its scheme (https://
) and www
subdomain (if present) before being passed to
the set_access_cookies
function:
set_access_cookies( response, encoded_access_token, domain=domain, max_age=datetime.timedelta(hours=1),)
While this allows the API to return cookies for the correct domain, browsers still fail to set the cookies if they originate from a different source.
Limitations
- The browser automatically rejects cookies from cross-origin responses, even if they are valid source.
- A potential workaround involves avoiding
set_access_cookies
entirely and sending the token as part of the JSON response.
Recommended Strategy: JSON Body for JWT Tokens
Given the challenges with cookies, a better strategy for cross-domain scenarios is to include the JWT tokens in the JSON body of the response documentation. This approach involves:
-
Login Response:
- The API returns the
access_token
andrefresh_token
in the JSON response body:
{"access_token": "<jwt_access_token>","refresh_token": "<jwt_refresh_token>"} - The API returns the
-
Frontend Handling:
- The frontend receives the tokens and explicitly sets them in
localStorage
,sessionStorage
, or cookies (manually using JavaScript). For example:
document.cookie = `access_token=${data.access_token}; Secure; HttpOnly; SameSite=None;`; - The frontend receives the tokens and explicitly sets them in
-
Subsequent Requests:
- The tokens are included in the
Authorization
header of API requests:
Authorization: Bearer <jwt_access_token> - The tokens are included in the
Benefits
- Cross-Domain Compatibility: No restrictions on setting tokens since cookies are not managed automatically by the browser.
- Frontend Control: The frontend can decide how to store and use the tokens.
- Flexibility: Tokens can be easily refreshed using a designated
/refresh
endpoint without relying on cookies.
Conclusion
For applications with multiple frontend domains, the cookie-based approach can lead to cross-domain issues due to browser limitations. Using the JSON Body strategy provides greater flexibility and compatibility across domains, ensuring seamless authentication and user experience. Complete discussion over the problem and solution can be found here.