feat(auth): Keycloak RP-initiated 단일 로그아웃(SLO) 적용
feat/sso 작업을 라우트 개편된 현재 main 에 이식. - handleCallback 이 id_token 반환, 콜백에서 분리해 session.idToken 에만 보관(뷰/DB 비노출) - logoutUrl(): end_session_endpoint + id_token_hint 로 확인화면 없이 즉시 로그아웃 - /logout: 로컬 세션 destroy 후 Keycloak end-session 으로 리다이렉트해 SSO 세션까지 종료 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
13
src/auth.js
13
src/auth.js
@@ -56,9 +56,22 @@ export async function handleCallback(req) {
|
||||
email: claims.email,
|
||||
roles,
|
||||
isAdmin: roles.includes(config.adminRole),
|
||||
// SSO 로그아웃(end-session) 시 id_token_hint 로 사용
|
||||
id_token: tokenSet.id_token,
|
||||
};
|
||||
}
|
||||
|
||||
// SSO 로그아웃 — Keycloak end_session_endpoint 로 보내 로컬뿐 아니라
|
||||
// Keycloak SSO 세션까지 종료한다. id_token_hint 가 있으면 확인 화면 없이 즉시 로그아웃.
|
||||
// 종료 후 post_logout_redirect_uri 로 복귀.
|
||||
export function logoutUrl(idToken) {
|
||||
if (!client) return `${config.baseUrl}/login`;
|
||||
return client.endSessionUrl({
|
||||
id_token_hint: idToken,
|
||||
post_logout_redirect_uri: `${config.baseUrl}/login`,
|
||||
});
|
||||
}
|
||||
|
||||
// 로그인 필수 가드
|
||||
export function requireAuth(req, res, next) {
|
||||
if (req.session.user) return next();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
import { config } from "./config.js";
|
||||
import { toolsForUser } from "./sites.js";
|
||||
import { initOidc, loginRedirect, handleCallback, requireAuth, oidcReady } from "./auth.js";
|
||||
import { initOidc, loginRedirect, handleCallback, requireAuth, oidcReady, logoutUrl } from "./auth.js";
|
||||
import { icon, identicon, langColor, langFromTags } from "./helpers.js";
|
||||
import * as db from "./db.js";
|
||||
|
||||
@@ -58,8 +58,11 @@ app.get("/auth/login", (req, res) => loginRedirect(req, res));
|
||||
app.get("/auth/callback", async (req, res) => {
|
||||
try {
|
||||
const u = await handleCallback(req);
|
||||
await db.upsertUser(u);
|
||||
req.session.user = u; // { sabun, name, email, roles, isAdmin }
|
||||
// id_token 은 SSO 로그아웃용으로만 세션에 따로 보관(뷰/DB 에 노출 안 함)
|
||||
const { id_token, ...user } = u;
|
||||
req.session.idToken = id_token;
|
||||
await db.upsertUser(user);
|
||||
req.session.user = user; // { sabun, name, email, roles, isAdmin }
|
||||
res.redirect("/");
|
||||
} catch (e) {
|
||||
console.error("oidc callback error", e);
|
||||
@@ -67,7 +70,13 @@ app.get("/auth/callback", async (req, res) => {
|
||||
}
|
||||
});
|
||||
app.get("/logout", (req, res) => {
|
||||
req.session.destroy(() => res.redirect("/login"));
|
||||
const idToken = req.session.idToken;
|
||||
// 로컬 세션을 먼저 destroy 한 뒤 Keycloak end-session 으로 보내
|
||||
// SSO 세션까지 종료한다. (로컬만 지우면 재접속 시 자동 재로그인됨)
|
||||
req.session.destroy(() => {
|
||||
if (oidcReady()) return res.redirect(logoutUrl(idToken));
|
||||
return res.redirect("/login");
|
||||
});
|
||||
});
|
||||
|
||||
// --- 대시보드 ---
|
||||
|
||||
Reference in New Issue
Block a user