Fix auth flow: federated logout, login page move, and healthcheck
- Add federated logout endpoint that clears Auth.js session AND ends Zitadel SSO session via OIDC end_session endpoint - Move sign-in page from /auth/signin to /login to avoid Auth.js route conflict causing ERR_TOO_MANY_REDIRECTS - Add callbackUrl to all signIn calls so users land on /dashboard - Store id_token in session for federated logout id_token_hint - Fix Zitadel healthcheck using binary ready command (no curl needed) - Update post_logout_redirect_uri in setup script Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a22ba48709
commit
c0cb2d25a0
12 changed files with 54 additions and 15 deletions
2
apps/dashboard/src/app.d.ts
vendored
2
apps/dashboard/src/app.d.ts
vendored
|
|
@ -3,6 +3,7 @@
|
|||
declare module '@auth/sveltekit' {
|
||||
interface Session {
|
||||
accessToken?: string;
|
||||
idToken?: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ declare module '@auth/core/jwt' {
|
|||
interface JWT {
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
idToken?: string;
|
||||
expiresAt?: number;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,19 @@ export const { handle, signIn, signOut } = SvelteKitAuth({
|
|||
if (account) {
|
||||
token.accessToken = account.access_token;
|
||||
token.refreshToken = account.refresh_token;
|
||||
token.idToken = account.id_token;
|
||||
token.expiresAt = account.expires_at;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
session.accessToken = token.accessToken as string;
|
||||
session.idToken = token.idToken as string;
|
||||
return session;
|
||||
}
|
||||
},
|
||||
pages: {
|
||||
signIn: '/auth/signin'
|
||||
signIn: '/login'
|
||||
},
|
||||
trustHost: true
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</a>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => signIn('zitadel')}
|
||||
onclick={() => signIn('zitadel', { callbackUrl: '/dashboard' })}
|
||||
class="px-5 py-2 bg-blue-600 hover:bg-blue-500 rounded-lg font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
Sign In
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
</a>
|
||||
{:else}
|
||||
<button
|
||||
onclick={() => signIn('zitadel')}
|
||||
onclick={() => signIn('zitadel', { callbackUrl: '/dashboard' })}
|
||||
class="px-8 py-3 bg-blue-600 hover:bg-blue-500 rounded-lg text-lg font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
Get Started
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { env } from '$env/dynamic/private';
|
||||
|
||||
export const GET: RequestHandler = async (event) => {
|
||||
const session = await event.locals.auth();
|
||||
const idToken = session?.idToken;
|
||||
|
||||
// Build Zitadel end_session URL
|
||||
const issuer = env.AUTH_ZITADEL_ISSUER || 'http://localhost:8080';
|
||||
const endSessionUrl = new URL('/oidc/v1/end_session', issuer);
|
||||
|
||||
if (idToken) {
|
||||
endSessionUrl.searchParams.set('id_token_hint', idToken);
|
||||
}
|
||||
// Use the registered post_logout_redirect_uri
|
||||
endSessionUrl.searchParams.set('post_logout_redirect_uri', 'http://localhost:5173');
|
||||
|
||||
// Clear the Auth.js session cookies
|
||||
event.cookies.delete('authjs.session-token', { path: '/' });
|
||||
event.cookies.delete('__Secure-authjs.session-token', { path: '/' });
|
||||
event.cookies.delete('authjs.callback-url', { path: '/' });
|
||||
event.cookies.delete('authjs.csrf-token', { path: '/' });
|
||||
|
||||
throw redirect(302, endSessionUrl.toString());
|
||||
};
|
||||
|
|
@ -4,7 +4,7 @@ import type { LayoutServerLoad } from './$types';
|
|||
export const load: LayoutServerLoad = async (event) => {
|
||||
const session = await event.locals.auth();
|
||||
if (!session) {
|
||||
throw redirect(303, '/auth/signin');
|
||||
throw redirect(303, '/login');
|
||||
}
|
||||
return { session };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { signOut } from '@auth/sveltekit/client';
|
||||
|
||||
let { children } = $props();
|
||||
let session = $derived($page.data.session);
|
||||
let sidebarOpen = $state(false);
|
||||
|
|
@ -131,7 +129,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
<button
|
||||
onclick={() => signOut()}
|
||||
onclick={() => window.location.href = '/api/auth/federated-logout'}
|
||||
class="text-sm text-slate-500 hover:text-red-600 dark:text-slate-400 dark:hover:text-red-400 transition-colors cursor-pointer"
|
||||
title="Sign out"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { signOut } from '@auth/sveltekit/client';
|
||||
let session = $derived($page.data.session);
|
||||
|
||||
const zitadelAccountUrl = import.meta.env.PUBLIC_ZITADEL_ACCOUNT_URL || '#';
|
||||
|
|
@ -91,7 +90,7 @@
|
|||
Sign out of your PVM account on this device.
|
||||
</p>
|
||||
<button
|
||||
onclick={() => signOut({ callbackUrl: '/' })}
|
||||
onclick={() => window.location.href = '/api/auth/federated-logout'}
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
|
|
|
|||
9
apps/dashboard/src/routes/login/+page.server.ts
Normal file
9
apps/dashboard/src/routes/login/+page.server.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
const session = await event.locals.auth();
|
||||
if (session) {
|
||||
throw redirect(303, '/dashboard');
|
||||
}
|
||||
};
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
<div class="bg-white/5 border border-white/10 rounded-2xl p-8 backdrop-blur-sm">
|
||||
<!-- OIDC Sign In -->
|
||||
<button
|
||||
onclick={() => signIn('zitadel')}
|
||||
onclick={() => signIn('zitadel', { callbackUrl: '/dashboard' })}
|
||||
class="w-full flex items-center justify-center gap-3 px-4 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
<div class="space-y-3">
|
||||
<button
|
||||
onclick={() => signIn('zitadel')}
|
||||
onclick={() => signIn('zitadel', { callbackUrl: '/dashboard' })}
|
||||
class="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white/5 hover:bg-white/10 border border-white/10 text-white rounded-lg font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
|
@ -50,7 +50,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
onclick={() => signIn('zitadel')}
|
||||
onclick={() => signIn('zitadel', { callbackUrl: '/dashboard' })}
|
||||
class="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white/5 hover:bg-white/10 border border-white/10 text-white rounded-lg font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
onclick={() => signIn('zitadel')}
|
||||
onclick={() => signIn('zitadel', { callbackUrl: '/dashboard' })}
|
||||
class="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white/5 hover:bg-white/10 border border-white/10 text-white rounded-lg font-medium transition-colors cursor-pointer"
|
||||
>
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="#1877F2">
|
||||
|
|
@ -39,8 +39,9 @@ services:
|
|||
condition: service_healthy
|
||||
volumes:
|
||||
- ./machinekey:/machinekey
|
||||
- ./zitadel-healthcheck.yaml:/zitadel-healthcheck.yaml:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:8080/debug/healthz"]
|
||||
test: ["CMD", "/app/zitadel", "ready", "--config", "/zitadel-healthcheck.yaml"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 15
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ create_oidc_app() {
|
|||
-d '{
|
||||
"name": "PVM Dashboard",
|
||||
"redirectUris": ["http://localhost:5173/auth/callback/zitadel"],
|
||||
"postLogoutRedirectUris": ["http://localhost:5173"],
|
||||
"postLogoutRedirectUris": ["http://localhost:5173", "http://localhost:5173/login"],
|
||||
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
|
||||
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"],
|
||||
"appType": "OIDC_APP_TYPE_WEB",
|
||||
|
|
|
|||
2
docker/zitadel-healthcheck.yaml
Normal file
2
docker/zitadel-healthcheck.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
TLS:
|
||||
Mode: disabled
|
||||
Loading…
Add table
Reference in a new issue