I have a Spring Boot backend hosted on Render and a React frontend hosted on Vercel. I’m trying to store JWT tokens in cookies and send them cross-site, but my POST requests to protected endpoints return 403 Forbidden.
Backend setup:
Spring Boot cookie configuration:
@Value("${app.security.cookies-secure}")
private boolean cookiesSecure;
@Value("${app.security.cookies-same-site}")
private String cookieSameSite;
private ResponseCookie buildCookie(String name, String value, long maxAgeSeconds) {
return ResponseCookie.from(name, value)
.httpOnly(true)
.secure(cookiesSecure)
.path("/")
.sameSite(cookieSameSite)
.maxAge(maxAgeSeconds)
.build();
}
Properties/env variables:
# Production
app.security.cookies-secure=${APP_SECURITY_COOKIES_SECURE:true}
app.security.cookies.same-site=${APP_SECURITY_COOKIES_SAME_SITE:None}
app.security.cors.allowed-origins=${CORS_ALLOWED_ORIGINS:http://localhost:5173}
Spring Security CORS config:
@Value("${app.security.csrf-enabled}")
private boolean csrfEnabled;
@Value("${app.security.cors.allowed-origins:http://localhost:5173}")
private String allowedOrigins;
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Arrays.asList(allowedOrigins.split(",")));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
SecurityFilterChain:
Stateless sessions
CSRF disabled for JWT cookie setup
Frontend setup:
Axios client:
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api',
withCredentials: true,
headers: {
'Content-Type': 'application/json'
}
});
POST request example:
export const createMealItem = async (data: CreateMealRequest): Promise<MealResponse> => {
const response = await apiClient.post('/meals', data);
return response.data;
}
Locally (docker-compose up --build + npm run dev) everything works: cookies are set and sent.
On Render/Vercel however...
POST https://example.onrender.com/api/meals
Status: 403 Forbidden
Render env variables
| KEY | VALUE |
|---|---|
| APP_SECURITY_COOKIES_SAME_SITE | None |
| APP_SECURITY_COOKIES_SECURE | true |
| CORS_ALLOWED_ORIGINS | https://example.vercel.app |
What I’ve tried
Verified backend sets
Secure; SameSite=Nonecookies.Frontend requests include
withCredentials: true.CORS allows credentials and exact frontend origin.
CSRF disabled.
Why are my cookies not being sent with POST requests in production, causing 403 errors, even though they are set correctly in the response?
Are there any Spring Boot or browser-specific cross-site considerations I might be missing?
Edit: Had to disable Block third-party cookies, still get 403 from my backend on POST/PUT requests.