I kept encountering CORS issue on my RedwoodJS program with my customize authentication connecting to Supabase. I noticed that when I pass a request with a header, CORS issue is seen: Access to fetch at 'http://localhost:8087/profile' from origin 'http://localhost:8910' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled. But when I removed the headers on my request. It works properly. Example of this is my /login API endpoint. This is my built in authentication with RedwoodJS: import { useState, useEffect, useCallback } from 'react' import { config } from 'src/config' import { logger } from 'src/loggers/logger' export const useAuth = () => { const [user, setUser] = useState(null) const [token, setToken] = useState(localStorage.getItem('token')) const [loading, setLoading] = useState(true) const logIn = async (email: string, password: string) => { try { const url = config.REDWOOD_ENV_API_ENDPOINT + '/login' logger.info(`Login URL is: ${url}`) logger.info( `JSON body: ${JSON.stringify({ email: email, password: password })}` ) const response = await fetch(url, { method: 'POST', body: JSON.stringify({ email: email, password: password }), }) console.log(response) const result = await response.json() if (result.error) { throw new Error(result.error) } const accessToken = result.access_token localStorage.setItem('token', accessToken) setToken(accessToken) setUser(result.user) return result } catch (error: any) { console.error('Login error:', error) throw error } } const logOut = useCallback(() => { localStorage.removeItem('token') setToken(null) setUser(null) }, []) const getProfile = useCallback(async () => { const url = config.REDWOOD_ENV_API_ENDPOINT + '/profile' if (!token) { setLoading(false) return } logger.info(`Fetching profile from ${url}`) try { const response = await fetch(url, { method: 'GET', headers: { Authorization: `bearer ${token}`, 'Content-Type': 'application/json', }, mode: 'cors', }) logger.info(`Profile response status: ${response.status}`) if (!response.ok) { const errorText = await response.text() logger.error(`Profile error: ${response.status} - ${errorText}`) throw new Error(`HTTP error ${response.status}: ${errorText}`) } const result = await response.json() if (result.error) { logger.error(`Profile result error: ${result.error}`) logOut() } else { setUser(result.data.user) } } catch (error) { console.error('Profile fetch error:', error) logOut() } setLoading(false) }, [token, logOut]) useEffect(() => { getProfile() }, [token, getProfile]) return { user, token, loading, logIn, logOut } } Then this is my Python back-end: def handle_cors_response(request: Request) -> Response: origin = request.headers.get("origin") allowed_origins = settings.REDWOOD_URL if isinstance(settings.REDWOOD_URL, list) else [settings.REDWOOD_URL] if origin in allowed_origins: return Response( status_code=204, headers={ "Access-Control-Allow-Origin": origin, "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", "Access-Control-Allow-Credentials": "true", }, ) return Response( status_code=403, headers={"Content-Type": "application/json"}, description=json.dumps({"error": "Origin not allowed"}), ) def get_response_headers(request: Request, content_type: str = "application/json") -> dict: origin = request.headers.get("origin") return { "Content-Type": content_type, "Access-Control-Allow-Origin": origin, "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", "Access-Control-Allow-Headers": "content-Type, authorization", "Access-Control-Allow-Credentials": "true", } @app.options("/{pathath}") async def handle_cors(request: Request): return Response( status_code=204, headers=get_response_headers(request), ) @app.get("/profile") async def profile(request): auth_header = request.headers.get("Authorization") or request.headers.get("authorization") if not auth_header or not auth_header.startswith("bearer "): return Response( status_code=401, headers=get_response_headers(request), description=json.dumps({"error": "Unauthorized"}) ) token = auth_header.split("bearer ", 1)[1] if "bearer " in auth_header.lower() else auth_header.split("Bearer ", 1)[1] try: decoded_token = jwt.decode(token, settings.JWT_SECRET, algorithms=["HS256"], audience="authenticated") logger.info(f'Decoded token: {decoded_token}') return Response( status_code=200, headers=get_response_headers(request), description=json.dumps({"data": {"user": decoded_token}}) ) except jwt.ExpiredSignatureError: return Response( status_code=401, headers=get_response_headers(request), description=json.dumps({"error": "Token expired"}) ) except jwt.InvalidTokenError: return Response( status_code=401, headers=get_response_headers(request), description=json.dumps({"error": "Invalid token"}) ) I've tried doing the following: Changed the get_response_headers to use * for debugging purposes. But error still persist: def get_response_headers(request: Request, content_type: str = "application/json") -> dict: origin = request.headers.get("origin") return { "Content-Type": content_type, "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "*", "Access-Control-Allow-Headers": "*", "Access-Control-Allow-Credentials": "true", } Used bruno to make a request with headers for Authorization as intended. Monitored to be working but CORS issue is shown on RedwoodJS app. Continue reading...