728x90
반응형
✅next-auth 란 무엇인가?
Auth.js(NextAuth.js)는 Next.js 프로젝트의 사용자 인증 및 세션 관리를 위한 라이브러리이다. Google, GitHub 등의 다양한 인증 공급자를 지원하며, (그중에서도 내가 구현한거는 Keycloak과의 인증이다 )Next.js의 서버와 클라이언트 측 모두에서 인증 및 세션 관리를 손쉽게 처리할 수 있다.
✅NextAuth.js를 이용한 Keycloak과의 인증 처리 과정
단계 | 파일명 | 동작 |
로그인 버튼 렌더링 | AuthStatus.js | 사용자가 로그인 상태인지 확인하고, 로그인 버튼을 클릭하면 signIn 함수를 호출하여 Keycloak으로 로그인 요청 전송. |
Keycloak 로그인 처리 | auth.js | Keycloak 제공자와의 통신을 설정하고, JWT 토큰을 사용한 인증 및 세션 관리를 처리. |
세션 및 토큰 관리 | auth.js | JWT 콜백에서 액세스 토큰과 리프레시 토큰을 처리하고, 세션에 필요한 정보를 저장하여 클라이언트와 서버에서 사용. |
로그아웃 버튼 렌더링 | AuthStatus.js | 로그아웃 버튼 클릭 시 keycloakSessionLogout 함수 호출, api/auth/logout로 서버에서 로그아웃 처리 요청 전송.(클라이언트 컴포넌트 -> 서버 컴포넌트로 : id_token을 클라이언트에 노출시키지 않기 위해) |
Keycloak 로그아웃 처리 | api/auth/logout/route.js | Keycloak 로그아웃 URL로 요청을 보내고, 세션에 저장된 id_token을 사용하여 로그아웃을 완료. |
스프링 서버로 API 요청 | RootLayout.js | 로그인 상태일 때 스프링 서버로 access_token을 포함한 요청을 전송하여 데이터를 가져옴. |
✅auth.js(루트) - NextAuth.js와 Keycloak Provider 설정
import NextAuth from "next-auth"
import Keycloak from "next-auth/providers/keycloak"
// 액세스 토큰이 만료되었을 경우 새로운 액세스 토큰을 발급받기 위해 리프레시 토큰 활용
async function refreshAccessToken(token) {
const response =
await fetch(`${process.env.REFRESH_TOKEN_URL}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
client_id: process.env.AUTH_KEYCLOAK_ID,
client_secret: process.env.AUTH_KEYCLOAK_SECRET,
grant_type: 'refresh_token',
refresh_token: token.refresh_token,
})
});
const data = await response.json();
return {
...token,
access_token: data.access_token,
id_token: data.id_token,
expires_at: Math.floor(Date.now() / 1000) + data.expires_in,
refresh_token: data.refresh_token,
}
}
const config = {
providers: [
Keycloak({
clientId: `${process.env.AUTH_KEYCLOAK_ID}`,
clientSecret: `${process.env.AUTH_KEYCLOAK_SECRET}`,
issuer: `${process.env.AUTH_KEYCLOAK_ISSUER}`
})
],
// TODO: 인증과 관련된 다른 처리 로직
callbacks: { // 인증 처리 과정에서 자동으로 호출되는 콜백들의 목록
session: {
strategy: 'jwt', // 세션 관리 방식을 jwt 전략으로 활용
maxAge: 60 * 60 * 24 // 세션 만료시간(초)
},
async jwt({ token, account }) {
const currentTime = Math.floor(Date.now() / 1000);
if (account) {
// token 객체에 개별 프로퍼티들로 할당
token.access_token = account.access_token;
token.id_token = account.id_token;
token.expires_at = account.expires_at;
token.refresh_token = account.refresh_token;
return token;
} else if (currentTime < token.expires_at) {
console.log('토큰이 아직 만료되지 않음');
return token; // 만료되지 않았기 때문에 기존 토큰 반환
} else { // 토큰이 만료되었을 경우,
// keycloak의 토큰 엔드포인트(REFRESH_TOKEN_URL)로 요청 전송
console.log('액세스토큰이 만료됨');
try {
const refreshedToken = await refreshAccessToken(token);
console.log('토큰이 갱신되었음');
return refreshedToken;
} catch (error) {
console.error('토큰 리프레싱 에러', error);
return { ...token, error: 'RefreshToken Error' }
}
}
return token;
},
// jwt 콜백이 반환하는 token을 전달받고,
// session을 확인하는 코드(ex. auth())가 호출될 때마다 session 콜백이 호출됨
async session({ session, token }) {
// keycloak 로그아웃 엔드포인트에 활용할 id_token을 세션에 저장
session.id_token = token.id_token;
// in Java Servlet - session.setAttribute("id_token", id_token);
// Spring 리소스 서버에 전송할 때 필요한 access_token을 세션에 저장
session.access_token = token.access_token;
return session;
}
}
}
export const { handlers, signIn, signOut, auth } = NextAuth(config)
/*
next-auth에서 제공하는 내장 함수
handlers - 인증과 관련된 API 라우트
signIn: 사용자 로그인 함수
signOut: 로그아웃 함수
auth : Next.js 서버가 가진 세션 정보를 반환하는 함수
*/
- auth.js에서 NextAuth의 Keycloak provider를 설정
- Keycloak() 함수에 clientId, clientSecret, issuer 등의 값을 지정하여 Keycloak과의 인증을 처리
- jwt() 콜백에서 토큰을 갱신하는 로직과 만료된 토큰을 새로 발급받는 과정이 포함되어 있다. 만료된 액세스 토큰은 refresh_token을 이용하여 갱신된다.
호출옵션
더보기
providers: Credentials, Google, GitHub 등의 인증 공급자를 지정
session: 세션 관리 방식을 지정
pages: 사용자 정의 페이지 경로를 지정하며, 로그인 페이지의 기본값은 /auth/signin
callbacks: 인증 및 세션 관리 중 호출되는 각 핸들러를 지정
callbacks.signIn: 사용자 로그인을 시도했을 때 호출되며, true를 반환하면 로그인 성공, false를 반환하면 로그인 실패로 처리
callbacks.redirect: 페이지 이동 시 호출되며, 반환하는 값은 리다이렉션될 URL
callbacks.jwt: JWT가 생성되거나 업데이트될 때 호출되며, 반환하는 값은 암호화되어 쿠키에 저장
callbacks.session: jwt 콜백이 반환하는 token을 받아, 세션이 확인될 때마다 호출되며, 반환하는 값은 클라이언트에서 확인할 수 있다. (2번 이상 호출될 수 있음)
✅components/AuthStatus.js - useSession() 훅을 사용하여 사용자의 세션 상태를 확인
'use client'
import React from 'react'
import { signIn, signOut, useSession } from 'next-auth/react';
// keycloak의 세션을 날리는 요청
async function keycloakSessionLogout() {
try {
// api/auth/logout/route.js의 GET() 호출
await fetch('/api/auth/logout', { method: 'GET' });
} catch (error) {
console.error(error);
}
}
const AuthStatus = () => {
// useSession() - 클라이언트 컴포넌트(use client)에서 세션 값을 가져오기 위한 훅
const { data: session, status } = useSession();
if (status === 'loading' && !session) {
return <div>Loading..</div>
} else if (session) { // 로그인 되었을 경우
return (
<div>
<div>
{`${session.user.email} 하이~`}
</div>
<button className='className="mt-2 px-2 py-1 font-bold text-white border rounded border-gray-50"'
onClick={() => keycloakSessionLogout().then(() => signOut({ callbackUrl: '/' }))}>
로그아웃
</button>
</div>
)
}
return (
<div>
<button
className="mt-2 px-2 py-1 font-bold text-white border rounded border-gray-50"
onClick={() => signIn("keycloak")}>
로그인
</button>
</div>
)
}
export default AuthStatus
- 클라이언트 컴포넌트에서는 useSession훅을 사용해 세션값을 가져오고 서버컴포넌트에서는 auth()함수를 사용해 세션값을 가져온다.
- signIn()과 signOut()을 사용하여 Keycloak에 대한 로그인 및 로그아웃을 처리하며, 로그아웃 시 Keycloak의 세션도 함께 제거하기 위한 keycloakSessionLogout()을 사용한다.
- 로그인 처리
- auth.js에서 NextAuth()를 통해 export해주는 signIn함수를 keycloak 프로바이더를 통해 호출
- 로그아웃 처리
- signOut() -> Next-auth에서 제공해주는 내장 함수, 클라이언트 애플리케이션용 로그아웃 처리
- Keycloak 서버 자체의 세션 정보도 지우는 처리 - 커스텀 함수, keycloakSessionLogout() - 선택
- next.js는 클라이어언트 애플리케이션이면서 서버 로직도 작성 가능하다. AuthStatus라는 브라우저에서 Next.js의 서버 로직인 GET:api/auth/logout로 요청을 전송할 수 있다.
- 그러면 GET:api/auth/logout는 요청을 받고, 응답 객체로 반환해주는 처리를 해야한다.
✅app/api/auth/[...nextauth]/route.js
import { handlers } from "@/auth" // 방금 생성한 auth.js에서 handlers를 불러옴
export const { GET, POST } = handlers
/*
Auth.js는 api/auth에 해당하는 경로를 통해 인증 처리를 수행
*/
✅app/api/auth/logout/route.js
// 라우트 핸들러, 서버 프로세스에서 동작
import { auth } from "@/auth";
export async function GET() {
// keycloak 엔드포인트로 로그아웃하기 위해서는 id_token이 필요
// id_token을 세션에서 꺼내야 함
const session = await auth();
if (session) {
const idToken = session.id_token;
// keycloak의 엔드포인트로 호출
const KEYCLOAK_URL = `${process.env.END_SESSION_URL}?id_token_hint=${idToken}&post_logout_redirect_uri=${encodeURIComponent(process.env.NEXTAUTH_URL)}`; // 로그아웃 처리 후 리다이렉트할 경로
// 요청 전송
try {
const response = await fetch(KEYCLOAK_URL, { method: 'GET' });
console.log('resp', response);
} catch (error) {
console.error(error);
return new Response({ status: 500 });
}
}
return new Response({ status: 200 });
}
- Keycloak의 로그아웃 엔드포인트로 요청을 전송하여 Keycloak 서버에 세션을 삭제
- id_token을 클라이언트에 노출시키지 않기 위해 클라이언트 컴포넌트에서 서버컴포넌트로 넘겨받는 과정이다.
✅layout.js(루트) - 백엔드 API와의 연동
import { auth } from "@/auth";
import "./globals.css";
import AuthStatus from "@/components/AuthStatus";
import { SessionProvider } from "next-auth/react";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({ children }) {
// 스프링 리소스 서버로 요청 전송
const session = await auth();
// 스프링 서버에게 요청을 전송할 때 필요한 값 - Access token
// 세션에 저장된 액세스 토큰을 꺼내서 Authorizaiton 헤더에 담아서 요청을 전송해서 담아야함
const response = await fetch(`${process.env.BACKEND_URL}/api/data`, {
method:"GET",
headers:{
"Authorization":'Bearer '+ session?.access_token,
"Content-type":"application/json",
}
});
return (
<html lang="en">
<body>
<div className="w-4/5 h-screen p-3 bg-white">{children}</div>
<h2 className="text-3xl">Next Client App</h2>
{/* 로그인 상태를 확인하는 용도의 컴포넌트 */}
<SessionProvider>
<AuthStatus/>
</SessionProvider>
</body>
</html>
);
}
- auth()를 통해 auth.js에서 설정된 callback 함수 안에 있는 session 정보 를 얻고, 토큰을 활용해 API 요청을 처리
참고사이트
- https://www.keycloak.org/
- https://authjs.dev/
- https://adoptium.net/temurin/releases/?arch=x64&version=17
※자바17로 되어 있어야 keycloak사용가능
728x90
반응형
'Frontend > Next.js' 카테고리의 다른 글
Next.js(14버전)- SSE 실시간 알림 구현 (0) | 2025.01.31 |
---|---|
Next.js - Infinite Scroll활용해서 데이터 처리하기 (1) | 2024.11.03 |
Next.js - next.js에서 session 활용하기 (0) | 2024.09.23 |
Next.js - Data fetching (React vs Next.js) (0) | 2024.02.11 |
Next.js 14버전 - Layouts,Metadata,Dynamic Routes (0) | 2024.02.08 |