fix(auth): add Vite dev proxy to fix SameSite=Strict cookie on page reload
Chrome 89+ treats http://localhost and https://localhost as different sites (schemeful same-site). The refreshToken cookie (SameSite=Strict) was not sent when the Vite dev server (HTTP) made cross-origin fetch calls to the .NET backend (HTTPS), causing a 401 on every page reload. Fix: route /api/* through the Vite dev server proxy so all requests stay same-origin. The cookie is now always sent and auth sessions survive reloads. - vite.config.ts: proxy /api → https://localhost:7221 (secure:false for dev cert) - .env.example: document proxy vs direct mode, set default VITE_API_BASE_URL to empty - config.ts: accept empty string as valid apiBaseUrl alongside full URLs Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+11
-4
@@ -1,4 +1,11 @@
|
|||||||
# Base URL of the SlpModularCms .NET API (Unit 0 backend).
|
# Base URL of the SlpModularCms .NET API.
|
||||||
# The backend must be running with CORS configured to allow this origin
|
#
|
||||||
# and to send the httpOnly refresh-token cookie (credentials: include).
|
# LOCAL DEV (recommended): leave empty and configure the Vite proxy in
|
||||||
VITE_API_BASE_URL=http://localhost:5000
|
# vite.config.ts. The proxy forwards /api/* to the backend so all requests
|
||||||
|
# stay same-origin — required for the SameSite=Strict refresh-token cookie.
|
||||||
|
VITE_API_BASE_URL=
|
||||||
|
|
||||||
|
# DIRECT (no proxy): point to the backend URL. CORS must allow this origin
|
||||||
|
# with credentials, and SameSite may block the cookie on page reload if the
|
||||||
|
# schemes differ (e.g. http frontend → https backend).
|
||||||
|
# VITE_API_BASE_URL=https://localhost:7221
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { z } from 'zod';
|
|||||||
* once and only warns in development — production trusts the build-time env.
|
* once and only warns in development — production trusts the build-time env.
|
||||||
*/
|
*/
|
||||||
const configSchema = z.object({
|
const configSchema = z.object({
|
||||||
apiBaseUrl: z.string().url(),
|
// Empty string = use Vite proxy (same-origin); a full URL = direct mode.
|
||||||
|
apiBaseUrl: z.union([z.literal(''), z.string().url()]),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type AppConfig = z.infer<typeof configSchema>;
|
export type AppConfig = z.infer<typeof configSchema>;
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
proxy: {
|
||||||
|
// Route all /api calls through the Vite dev server so the browser
|
||||||
|
// sees a single origin. Without this the refreshToken cookie
|
||||||
|
// (SameSite=Strict) is not sent from http://localhost to
|
||||||
|
// https://localhost (different scheme = cross-site in Chrome 89+).
|
||||||
|
'/api': {
|
||||||
|
target: 'https://localhost:7221',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false, // allow self-signed dev cert
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
globals: true,
|
globals: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user