Files
drivetimeplaner/www/fahrschultermin.de/public/index.php
Hermes Agent 5d87e4975c Add HttpOnly cookie auth for cross-domain SPA login
Problem: Frontend JS bundle used credentials:'same-origin' which
dropped cookies on cross-domain requests. Login worked (200) but
the subsequent /auth/me check returned 401, leaving the user stuck
on the login screen.

Fix:
- AuthController now sets a dtp_jwt HttpOnly cookie on login/refresh
- Cookie uses Domain=.fahrschultermin.de (shared between frontend
  and api subdomain), Secure, SameSite=Lax, Max-Age=8h
- JwtMiddleware reads JWT from Authorization header OR cookie
- Added AuthController::me() endpoint (was missing, caused 500)
- Logout endpoint clears the cookie
- Frontend index.php patches fetch() to use credentials:'include'
  for all /api/v1/* calls
2026-06-04 20:33:31 +02:00

64 lines
2.5 KiB
PHP
Executable File

<?php
/**
* fahrschultermin.de — Frontend entry point
* Patches fetch() to redirect /api/v1/* calls to api.fahrschultermin.de
* with credentials:'include' so cross-domain cookies (dtp_jwt) are sent.
*
* Authentication: The API sets a `dtp_jwt` HttpOnly cookie at login. With
* credentials:'include' the browser sends it automatically on every
* /api/v1/* call, so we never need to add an Authorization header (which
* would trigger a CORS preflight).
*/
const API_BASE = 'https://api.fahrschultermin.de';
$assetManifestPath = __DIR__ . '/assets/.vite/manifest.json';
$mainAsset = '/assets/index.js';
$styleAsset = '/assets/index.css';
if (is_file($assetManifestPath)) {
$manifest = json_decode(file_get_contents($assetManifestPath), true);
$entry = $manifest['src/main.jsx'] ?? null;
if (is_array($entry)) {
$mainAsset = '/assets/' . ltrim((string) $entry['file'], '/');
if (!empty($entry['css'][0])) {
$styleAsset = '/assets/' . ltrim((string) $entry['css'][0], '/');
}
}
}
?><!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DriveTime Planner</title>
<link rel="stylesheet" href="<?= htmlspecialchars($styleAsset, ENT_QUOTES) ?>">
</head>
<body>
<div id="root"></div>
<script>
// ===== Critical: Patch fetch BEFORE the module loads =====
// The bundled JS uses its own internal fetch wrapper with
// credentials:"same-origin", which would drop our dtp_jwt cookie
// on the cross-origin call to api.fahrschultermin.de. We override
// it here so cookies travel and the backend can identify the user.
window.__API_BASE__ = '<?= API_BASE ?>';
const _fetch = window.fetch.bind(window);
window.fetch = function (resource, options = {}) {
let url = typeof resource === 'string' ? resource : resource.url;
if (typeof url === 'string' && url.startsWith('/api/v1')) {
url = '<?= API_BASE ?>' + url;
// Make sure the API receives the dtp_jwt cookie
options = Object.assign({}, options, {
credentials: 'include',
mode: 'cors'
});
return _fetch(url, options);
}
return _fetch(resource, options);
};
</script>
<script type="module" src="<?= htmlspecialchars($mainAsset, ENT_QUOTES) ?>"></script>
</body>
</html>