Careeroza
Real World Example
One Origin for SPA and API
Most production setups (Swiggy, Razorpay Dashboard, Zerodha Kite web) put the SPA and the API behind the same domain with a reverse proxy, so the browser sees one origin: no CORS in production, cookies are first-party, and CSRF cookies are readable. In development, Vite's proxy does the same job.
Browser --> https://shopease.in/ --> nginx --> React build (static files)
--> https://shopease.in/api/... --> nginx --> Spring Boot :8080// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
proxy: {
'/api': { target: 'http://localhost:8080', changeOrigin: true },
},
},
});ShopEase Final Config
Bearer header for the API (no CSRF needed there), cookie for the refresh/logout endpoints (CSRF token required there), CORS kept for other front-ends like the seller portal on another domain:
@Bean
SecurityFilterChain api(HttpSecurity http, JwtService jwtService) throws Exception {
// only the cookie-authenticated endpoints need a CSRF token
RequestMatcher cookieEndpoints = request ->
"POST".equals(request.getMethod())
&& (request.getRequestURI().equals("/api/auth/refresh")
|| request.getRequestURI().equals("/api/auth/logout"));
http
.cors(Customizer.withDefaults())
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler())
.requireCsrfProtectionMatcher(cookieEndpoints))
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/products/**", "/api/categories/**").permitAll()
.anyRequest().authenticated())
.addFilterBefore(new JwtAuthFilter(jwtService), UsernamePasswordAuthenticationFilter.class);
return http.build();
}| Defence | Protects against |
|---|---|
| Access token in memory, short TTL | Token theft from storage, long misuse |
| Refresh cookie HttpOnly + Secure | XSS reading the refresh token, sniffing on HTTP |
| SameSite=Strict + Path=/api/auth | Cross-site requests carrying the cookie |
| CSRF token on refresh/logout | CSRF from sub-domains or old browsers |
| Exact CORS origins | Other websites reading API responses with the user's cookies |