RedwoodJS自定义认证连接Supabase时带请求头触发CORS问题排查求助
RedwoodJS自定义认证连接Supabase时带请求头触发CORS问题排查求助
大家好,我在RedwoodJS项目里用自定义认证对接Supabase时碰到了一个棘手的CORS问题,想请各位帮忙分析下:
当我的前端请求携带请求头时(比如调用/profile接口时带上Authorization Bearer Token),浏览器就会抛出CORS错误:
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.
但如果去掉请求头(比如调用/login接口的时候),请求就能正常返回,完全没有CORS问题。
前端RedwoodJS的自定义认证实现(useAuth)
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 } }
后端Python的CORS处理及Profile接口实现
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"}) )
我已经尝试过的方法
- 临时修改
get_response_headers方法,把Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers都设为*来调试,但CORS错误依然存在:
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", }
- 用Bruno工具直接带Authorization头请求
/profile接口,接口能正常返回数据,但在RedwoodJS前端里还是会触发CORS错误。
现在实在搞不懂问题出在哪了,想请教各位有没有什么排查思路或者解决办法?
备注:内容来源于stack exchange,提问作者Elijah Leis
相关产品推荐
相关产品推荐

