728x90
NextAuth.js는 OAuth 2.0 및 OpenID Connect(OIDC) 제공자와의 복잡한 인증 과정을 추상화하여 간편하게 설정할 수 있도록 지원하는 라이브러리입니다. 이 글에서는 Nuxt 3 환경에서 Keycloak과 NextAuth.js를 연동하면서, 실제로 구현한 내용을 바탕으로 세부 동작을 설명합니다.
https://auth.sidebase.io/
NuxtAuth | Authentication for Nuxt 3
User authentication and sessions via authjs
auth.sidebase.io
NextAuth.js 인증 단계
NextAuth의 역할 | 자동 여부 |
로그인 버튼 클릭 (클라이언트에서 signIn() 호출) | 🔹커스텀 화면 사용시 개발자 구현 NextAuth 랜딩 화면 사용시 자동 처리 |
Keycloak 로그인 페이지로 리디렉션 | 자동 처리 |
로그인 성공 후 인증 코드를 callback으로 반환 | 자동 처리 |
callback에서 액세스 토큰, 리프레시 토큰 발급 | 자동 처리 |
발급된 토큰을 JWT 형태로 암호화하여 쿠키에 저장 | 자동 처리 |
SSR에서 세션 검증 (getServerSession) | 자동 처리 |
API 호출 시 JWT를 Authorization 헤더에 추가 | 🔹개발자 구현 |
액세스 토큰 만료 시 리프레시 토큰으로 갱신 | 🔹개발자 구현 |
토큰 만료시 세션 제거 | 🔹개발자 구현 |
nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sidebase/nuxt-auth'],
runtimeConfig: {
authSecret: '',
keycloakClientSecret: '',
keycloakClientId: '',
keycloakIssuer: '',
},
auth: {
provider: { type: 'authjs' },
sessionRefresh: {
enablePeriodically: 1000 * 60 * 5,
enableOnWindowFocus: true,
},
globalAppMiddleware: true,
},
});
- defaultProvider를 설정하지 않으면 nextAuth에서 제공하는 랜딩화면을 사용할 수 있다.
- defaultProvider를 설정하면 로그아웃 시에 다시 OAuth Provider에 로그인 상태를 확인하게 되므로 로그아웃이 되지 않는다. 필요하면 OAuth Provider의 세션을 제거해야한다.
/server/api/auth/[...].ts
import { NuxtAuthHandler } from '#auth';
import type { JWT } from 'next-auth/jwt';
import KeycloakProvider from 'next-auth/providers/keycloak';
export default NuxtAuthHandler({
secret: useRuntimeConfig().authSecret,
providers: [
KeycloakProvider.default({
clientId: useRuntimeConfig().keycloakClientId,
clientSecret: useRuntimeConfig().keycloakClientSecret,
issuer: useRuntimeConfig().keycloakIssuer,
}),
],
callbacks: {
async jwt({ token, user, account }) {
// 로그인 시에만 accout 값이 존재함.
// Access Token 및 Refresh Token 저장
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
token.accessTokenExpires = account.expires_at! * 1000;
}
if (token.accessTokenExpires && Date.now() < token.accessTokenExpires) {
return token;
}
// Access Token이 만료되었으면 Refresh Token을 사용해 갱신
return await refreshAccessToken(token);
},
async session({ session, token }) {
session.accessToken = token.accessToken;
session.accessTokenExpires = token.accessTokenExpires;
return session;
},
},
});
async function refreshAccessToken(token: JWT): Promise<JWT> {
const config = useRuntimeConfig();
try {
const response = await fetch(`${config.keycloakIssuer}/protocol/openid-connect/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
client_id: config.keycloakClientId,
client_secret: config.keycloakClientSecret,
grant_type: 'refresh_token',
refresh_token: token.refreshToken!,
}),
});
const data = await response.json();
if (!response.ok) {
const error = data.error_description || data.error || 'Failed to refresh access token';
throw new Error(error);
}
return {
...token,
accessToken: data.access_token,
accessTokenExpires: Date.now() + data.expires_in * 1000,
refreshToken: data.refresh_token ?? token.refreshToken,
};
} catch (error) {
console.error('토큰 갱신 실패:', error);
return { ...token };
}
}
token
- JWT에는 사용자의 기본 정보(예: 이름, 이메일, 사용자 ID 등)가 포함됩니다.
- 이 JWT는 next-auth.session-token이라는 이름의 쿠키에 암호화되어 저장되어, 클라이언트가 인증 상태를 유지할 수 있도록 합니다.
account
- 사용자가 OAuth 제공자(Keycloak 등)를 통해 로그인할 때, 해당 제공자로부터 받은 정보를 담고 있습니다. 예를 들어, 로그인 직후 Keycloak에서 전달받은 access_token, refresh_token, 만료 시간 등의 데이터가 포함됩니다.
- 초기 로그인 시에만 제공되고, 이후에는 개발자가 jwt 콜백 내에서 account 정보를 token에 병합(merge)하여 저장하도록 구현해야 합니다. 이렇게 함으로써 account의 정보가 휘발되지 않고, 향후 세션 관리에 사용될 수 있습니다.
session
- session 객체는 클라이언트 측에서 getServerSession() 또는 getSession() 함수를 호출할 때 반환되는 객체입니다.
- 이 session은 next-auth.session-token 쿠키에 저장된 JWT를 파싱하여 token 기반으로 만들어지며, 클라이언트가 인증 상태와 필요한 사용자 정보를 확인할 수 있도록 제공합니다.
- 토큰의 만료 시간과 세션의 만료 시간은 상이합니다. 때문에 토큰은 만료되었지만 세션은 살아있을 수 있으며 세션에 토큰 만료시간을 담아 미들웨어 등에서 확인 signOut()을 호출하여 세션과 토큰의 수명을 같게 할 수 있습니다.
/types/next-auth.d.ts
import 'next-auth';
import 'next-auth/jwt';
declare module 'next-auth' {
// getSession(), getServerSession()으로 반환되는 객체
interface Session {
accessToken?: string;
accessTokenExpires?: number;
}
}
declare module 'next-auth/jwt' {
// next-auth.session-token 으로 쿠키에 암호화 되어 저장되는 객체
interface JWT {
accessToken?: string;
refreshToken?: string;
accessTokenExpires?: number;
}
}
728x90
'Javascript > NuxtJS' 카테고리의 다른 글
[Nuxt] 빌드 후 동적 프록시 설정하기 (0) | 2025.03.26 |
---|---|
[Nuxt3] 환경 변수 관리 (0) | 2025.03.15 |
[Nuxt3] 스토리지: vueuse (0) | 2025.03.15 |
[Nuxt3] 헬스 체크 (1) | 2025.03.15 |
[Nuxt3] 데이터 요청 (0) | 2025.03.15 |