feat(auth): implement hybrid session model with HTTP-only cookies

Add HTTP-only cookie session support to backend for SSR compatibility
Update frontend fetch calls to include credentials
Maintain legacy bearer token support for SPA transition
This commit is contained in:
2026-05-06 09:48:18 +08:00
parent afed409127
commit fd1f3ef636
6 changed files with 97 additions and 31 deletions

View File

@@ -112,17 +112,14 @@ const navItems = computed<NavItem[]>(() => {
});
async function loadCurrentUser() {
if (!getAuthToken()) {
currentUser.value = null;
return;
}
try {
const response = await api.me();
currentUser.value = response.user;
} catch {
currentUser.value = null;
setAuthToken(null);
if (getAuthToken()) {
setAuthToken(null);
}
}
}

View File

@@ -1,4 +1,4 @@
import { api, getAuthToken, setAuthToken } from '../src/services/api';
import { api, setAuthToken } from '../src/services/api';
export default defineNuxtRouteMiddleware(async (to) => {
const requiredPermissions = to.matched
@@ -16,10 +16,6 @@ export default defineNuxtRouteMiddleware(async (to) => {
return;
}
if (!getAuthToken()) {
return navigateTo({ path: '/login', query: { redirect: to.fullPath } });
}
try {
const response = await api.me();
if (requiresVerified && !response.user.emailVerified) {

View File

@@ -1136,6 +1136,7 @@ async function getErrorMessage(response: Response): Promise<string> {
async function getJson<T>(path: string, signal?: AbortSignal): Promise<T> {
const response = await fetch(apiUrl(path), {
credentials: 'include',
headers: requestHeaders(),
signal
});
@@ -1149,6 +1150,7 @@ async function getJson<T>(path: string, signal?: AbortSignal): Promise<T> {
async function sendJson<T>(path: string, method: 'PATCH' | 'POST' | 'PUT', body: unknown): Promise<T> {
const response = await fetch(apiUrl(path), {
credentials: 'include',
method,
headers: {
'Content-Type': 'application/json',
@@ -1166,6 +1168,7 @@ async function sendJson<T>(path: string, method: 'PATCH' | 'POST' | 'PUT', body:
async function sendFormData<T>(path: string, body: FormData): Promise<T> {
const response = await fetch(apiUrl(path), {
credentials: 'include',
method: 'POST',
headers: requestHeaders(),
body
@@ -1180,6 +1183,7 @@ async function sendFormData<T>(path: string, body: FormData): Promise<T> {
async function postEmpty(path: string): Promise<void> {
const response = await fetch(apiUrl(path), {
credentials: 'include',
method: 'POST',
headers: requestHeaders()
});
@@ -1191,6 +1195,7 @@ async function postEmpty(path: string): Promise<void> {
async function deleteJson(path: string): Promise<void> {
const response = await fetch(apiUrl(path), {
credentials: 'include',
method: 'DELETE',
headers: requestHeaders()
});
@@ -1202,6 +1207,7 @@ async function deleteJson(path: string): Promise<void> {
async function deleteAndGetJson<T>(path: string): Promise<T> {
const response = await fetch(apiUrl(path), {
credentials: 'include',
method: 'DELETE',
headers: requestHeaders()
});