Skip to content

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 and pro6pp.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.

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:

  1. Login Response:

    • The API returns the access_token and refresh_token in the JSON response body:
    {
    "access_token": "<jwt_access_token>",
    "refresh_token": "<jwt_refresh_token>"
    }
  2. 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;`;
  3. Subsequent Requests:

    • The tokens are included in the Authorization header of API requests:
    Authorization: Bearer <jwt_access_token>

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.