Centralize authentication logic and integrate token refresh mechanism

- Introduce `AuthWrapper` component for streamlined session-based layouts and authentication handling.
- Add new utilities (`tokenUtils.ts`) for JWT decoding, token expiration checks, and refresh operations via Keycloak.
- Refactor `serverCall` and `authOptions` to use centralized token refresh logic, removing redundant implementations.
- Implement `ClientSessionProvider` for consistent session management across the client application.
- Simplify `RootLayout` by delegating authentication enforcement to `AuthWrapper`.
This commit is contained in:
2025-07-11 23:42:41 +02:00
parent 6aae06635d
commit bdbaf36456
6 changed files with 272 additions and 165 deletions

View File

@@ -1,5 +1,6 @@
import KeycloakProvider from "next-auth/providers/keycloak";
import type {NextAuthOptions} from "next-auth";
import {getAccessToken} from "@/lib/api/auth/tokenUtils";
interface TypedJWT {
access_token?: string;
@@ -20,9 +21,9 @@ if (!KEYCLOAK_CLIENT_SECRET) throw new Error("Missing KEYCLOAK_CLIENT_SECRET");
if (!KEYCLOAK_ISSUER) throw new Error("Missing KEYCLOAK_ISSUER");
if (!NEXTAUTH_SECRET) throw new Error("Missing NEXTAUTH_SECRET");
console.log("[auth] Using Keycloak provider:");
console.log(" - Client ID:", KEYCLOAK_CLIENT_ID);
console.log(" - Issuer:", KEYCLOAK_ISSUER);
// console.log("[auth] Using Keycloak provider:");
// console.log(" - Client ID:", KEYCLOAK_CLIENT_ID);
// console.log(" - Issuer:", KEYCLOAK_ISSUER);
async function isTokenValid(token: string): Promise<boolean> {
try {
@@ -60,22 +61,39 @@ export const authOptions: NextAuthOptions = {
return token;
}
const {access_token} = token as TypedJWT;
const {access_token, refresh_token} = token as TypedJWT;
if (access_token) {
const valid = await isTokenValid(access_token);
if (!valid) {
console.warn("[auth] Access token invalid — clearing session");
// Use centralized getAccessToken function
const tokenResult = await getAccessToken(access_token, refresh_token);
if (tokenResult.accessToken) {
token.access_token = tokenResult.accessToken;
if (tokenResult.refreshToken) {
token.refresh_token = tokenResult.refreshToken;
}
if (tokenResult.refreshed) {
console.log("[auth] Token refreshed successfully in JWT callback");
return token;
}
// If token wasn't refreshed, fall back to network validation
const valid = await isTokenValid(tokenResult.accessToken);
if (!valid) {
console.warn("[auth] Access token invalid — clearing session");
return {};
}
} else {
console.warn("[auth] No valid access token available — clearing session");
return {};
}
}
console.log("[auth] JWT callback: reusing existing token");
return token;
},
async session({session, token}) {
const {access_token, refresh_token} = token as TypedJWT;
console.log("[auth] Session callback: enriching session with tokens");
return {
...session,
accessToken: access_token,