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("/{path:path}")
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.