fix(auth): align session lifecycle with Keycloak

This commit is contained in:
2026-08-05 17:10:59 +08:00
parent 5d9c40e454
commit e496fbe4b7
20 changed files with 339 additions and 24 deletions
+29 -1
View File
@@ -3,6 +3,9 @@ import { JWT } from "next-auth/jwt";
import KeycloakProvider from "next-auth/providers/keycloak";
import Avatar from "@assets/avatar/avatar-small.jpeg";
const SESSION_MAX_AGE_SECONDS = 12 * 60 * 60;
const ACCESS_TOKEN_REFRESH_SKEW_MS = 30_000;
type KeycloakTokenResponse = {
access_token: string;
expires_in: number;
@@ -50,6 +53,7 @@ const refreshAccessToken = async (token: JWT): Promise<JWT> => {
return {
...token,
accessToken: refreshed.access_token,
accessTokenIssuedAt: Date.now(),
accessTokenExpires: Date.now() + refreshed.expires_in * 1000,
refreshToken: refreshed.refresh_token ?? token.refreshToken,
error: undefined,
@@ -88,8 +92,10 @@ const authOptions: NextAuthOptions = {
}
if (account) {
token.sessionExpiresAt = Date.now() + SESSION_MAX_AGE_SECONDS * 1000;
if (account.access_token) {
token.accessToken = account.access_token;
token.accessTokenIssuedAt = Date.now();
}
if (account.refresh_token) {
token.refreshToken = account.refresh_token;
@@ -104,7 +110,19 @@ const authOptions: NextAuthOptions = {
return token;
}
if (typeof token.accessTokenExpires === "number" && Date.now() < token.accessTokenExpires - 30_000) {
if (
typeof token.sessionExpiresAt === "number" &&
Date.now() >= token.sessionExpiresAt
) {
return { ...token, error: "SessionExpired" };
}
const accessTokenIsFresh =
typeof token.accessTokenExpires === "number" &&
typeof token.accessTokenIssuedAt === "number" &&
Date.now() < token.accessTokenExpires - ACCESS_TOKEN_REFRESH_SKEW_MS;
if (accessTokenIsFresh) {
return token;
}
@@ -123,9 +141,19 @@ const authOptions: NextAuthOptions = {
if (token.error) {
session.error = token.error;
}
if (typeof token.sessionExpiresAt === "number") {
session.sessionExpiresAt = token.sessionExpiresAt;
}
return session;
},
},
session: {
strategy: "jwt",
maxAge: SESSION_MAX_AGE_SECONDS,
},
jwt: {
maxAge: SESSION_MAX_AGE_SECONDS,
},
};
export default authOptions;