Auth.js (Next Auth) 애플리케이션에 인증 (Authentication)을 추가하세요
이 가이드는 이전에 Next Auth로 알려진 Auth.js를 사용하여 Logto를 Next.js 애플리케이션에 통합하는 방법을 보여줍니다.
- 이 가이드에서는 Next.js 프로젝트에 Next Auth를 설정했다고 가정합니다. 설정하지 않았다면, Next Auth 문서를 확인하여 시작하세요.
사전 준비 사항
- Logto Cloud 계정 또는 셀프 호스팅 Logto.
- Logto 전통적인 애플리케이션 생성.
- Auth.js가 포함된 Next.js 프로젝트, Auth.js 문서를 확인하세요.
설치
선호하는 패키지 관리자를 통해 Auth.js를 설치하세요:
- npm
- pnpm
- yarn
npm i next-auth@beta
pnpm add next-auth@beta
yarn add next-auth@beta
자세한 내용은 Auth.js 문서 를 참조하세요.
통합
Auth.js provider 설정하기
"App Secret"은 Admin Console의 애플리케이션 세부 정보 페이지에서 찾고 복사할 수 있습니다:

Auth.js의 API 경로 구성을 수정하고, Logto를 OIDC provider로 추가하세요:
- Auth.js v5
- Next Auth v4
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oidc',
// Logto 애플리케이션 세부 정보 페이지에서 발급자 값을 얻을 수 있습니다,
// "Issuer endpoint" 필드에서
issuer: 'https://xxxx.logto.app/oidc',
clientId: '<logto-app-id>',
clientSecret: '<logto-app-secret>',
authorization: {
params: { scope: 'openid offline_access profile email' },
},
profile(profile) {
// 여기서 사용자 프로필 매핑을 사용자 정의할 수 있습니다
return {
id: profile.sub,
name: profile.name ?? profile.username,
email: profile.email,
image: profile.picture,
};
},
},
],
});
issuer
URL을 Logto 애플리케이션의 "Issuer endpoint"로 교체하세요.clientId
와clientSecret
을 Logto 애플리케이션의 ID와 비밀로 교체하세요.profile
함수를 사용자 프로필을 Next Auth 사용자 객체로 매핑하도록 사용자 정의하세요. 기본 매핑은 예제에 표시되어 있습니다.
그런 다음 세션을 유지하기 위해 선택적으로 Middleware를 추가할 수 있습니다:
export { auth as middleware } from '@/auth';
import NextAuth from 'next-auth';
const handler = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oauth',
// Logto 애플리케이션 세부 정보 페이지에서 잘 알려진 URL을 얻을 수 있습니다,
// "OpenID Provider configuration endpoint" 필드에서
wellKnown: 'https://xxxx.logto.app/oidc/.well-known/openid-configuration',
authorization: { params: { scope: 'openid offline_access profile email' } },
clientId: '<logto-app-id>',
clientSecret: '<logto-app-secret>',
client: {
id_token_signed_response_alg: 'ES384',
},
profile(profile) {
// 여기서 사용자 프로필 매핑을 사용자 정의할 수 있습니다
return {
id: profile.sub,
name: profile.name ?? profile.username,
email: profile.email,
image: profile.picture,
};
},
},
],
});
export { handler as GET, handler as POST };
wellKnown
URL을 Logto 애플리케이션의 "OpenID Provider configuration endpoint"로 교체하세요.clientId
와clientSecret
을 Logto 애플리케이션의 ID와 비밀로 교체하세요.profile
함수를 사용자 프로필을 Next Auth 사용자 객체로 매핑하도록 사용자 정의하세요. 기본 매핑은 예제에 표시되어 있습니다.id_token_signed_response_alg
를ES384
로 설정하는 것을 잊지 마세요.
자세한 내용은 Auth.js 문서에서 확인할 수 있습니다.
로그인 리디렉션 URI 구성하기
세부 사항을 살펴보기 전에, 최종 사용자 경험에 대한 간단한 개요를 소개합니다. 로그인 과정은 다음과 같이 간소화될 수 있습니다:
- 귀하의 앱이 로그인 메서드를 호출합니다.
- 사용자는 Logto 로그인 페이지로 리디렉션됩니다. 네이티브 앱의 경우, 시스템 브라우저가 열립니다.
- 사용자가 로그인하고 귀하의 앱으로 다시 리디렉션됩니다 (리디렉션 URI로 구성됨).
리디렉션 기반 로그인에 관하여
- 이 인증 과정은 OpenID Connect (OIDC) 프로토콜을 따르며, Logto는 사용자 로그인을 보호하기 위해 엄격한 보안 조치를 시행합니다.
- 여러 앱이 있는 경우, 동일한 아이덴티티 제공자 (Logto)를 사용할 수 있습니다. 사용자가 한 앱에 로그인하면, Logto는 사용자가 다른 앱에 접근할 때 자동으로 로그인 과정을 완료합니다.
리디렉션 기반 로그인에 대한 이론적 배경과 이점에 대해 더 알고 싶다면, Logto 로그인 경험 설명을 참조하세요.
다음 코드 스니펫에서는, 여러분의 앱이 http://localhost:3000/
에서 실행되고 있다고 가정합니다.
Logto 콘솔의 애플리케이션 세부 정보 페이지로 전환하세요. 리디렉션 URI http://localhost:3000/api/auth/callback/logto
를 추가하고 "변경 사항 저장"을 클릭하세요.

로그인 및 로그아웃 구현하기
로그인 및 로그아웃 버튼 구현하기
import { signIn } from '@/auth';
export default function SignIn() {
return (
<form
action={async () => {
'use server';
await signIn('logto');
}}
>
<button type="submit">Sign In</button>
</form>
);
}
import { signOut } from '@/auth';
export function SignOut() {
return (
<form
action={async () => {
'use server';
await signOut();
}}
>
<button type="submit">Sign Out</button>
</form>
);
}
페이지에 로그인 및 로그아웃 버튼 표시하기
import SignIn from './components/sign-in';
import SignOut from './components/sign-out';
import { auth } from '@/auth';
export default function Home() {
const session = await auth();
return <div>{session?.user ? <SignOut /> : <SignIn />}</div>;
}
위는 간단한 예제이며, 자세한 내용은 Auth.js 문서에서 확인할 수 있습니다.
체크포인트
이제 애플리케이션을 테스트하여 인증이 예상대로 작동하는지 확인할 수 있습니다.
사용자 정보 가져오기
사용자 정보 표시
사용자가 로그인하면, auth()
의 반환 값은 사용자의 정보를 포함하는 객체가 됩니다. 이 정보를 앱에서 표시할 수 있습니다:
import { auth } from '@/auth';
export default async function Home() {
const session = await auth();
return (
<main>
{session?.user && (
<div>
<h2>클레임 (Claims):</h2>
<table>
<thead>
<tr>
<th>이름</th>
<th>값</th>
</tr>
</thead>
<tbody>
{Object.entries(session.user).map(([key, value]) => (
<tr key={key}>
<td>{key}</td>
<td>{String(value)}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</main>
);
}
추가 클레임 요청
auth()
에서 반환된 객체에 일부 사용자 정보가 누락된 것을 발견할 수 있습니다.
이는 OAuth 2.0 및 OpenID Connect (OIDC)가 최소 권한 원칙 (PoLP)을 따르도록 설계되었기 때문이며,
Logto는 이러한 표준을 기반으로 구축되었습니다.
기본적으로 제한된 클레임 (Claim)만 반환됩니다. 더 많은 정보를 원하시면, 추가적인 스코프 (Scope)를 요청하여 더 많은 클레임에 접근할 수 있습니다.
"클레임 (Claim)"은 주체에 대해 주장하는 내용이며, "스코프 (Scope)"는 클레임의 그룹입니다. 현재의 경우, 클레임은 사용자에 대한 정보입니다.
다음은 스코프 - 클레임 관계의 비규범적 예시입니다:
"sub" 클레임은 "주체"를 의미하며, 이는 사용자의 고유 식별자 (즉, 사용자 ID)입니다.
Logto SDK는 항상 세 가지 스코프를 요청합니다: openid
, profile
, 그리고 offline_access
.
추가 스코프를 요청하려면, Logto provider의 매개변수를 구성할 수 있습니다:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
},
},
// ...
},
],
});
네트워크 요청이 필요한 클레임
ID 토큰의 비대화를 방지하기 위해, 일부 클레임은 네트워크 요청을 통해 가져와야 합니다. 예를 들어, custom_data
클레임은 스코프에서 요청하더라도 사용자 객체에 포함되지 않습니다. 이러한 클레임에 접근하려면 사용자 정보를 가져오기 위해 네트워크 요청을 해야 합니다.
액세스 토큰 얻기
액세스 토큰을 얻을 수 있도록 NextAuth
구성을 업데이트하세요:
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
callbacks: {
async jwt({ token, account }) {
if (account) {
token.accessToken = account.access_token;
}
return token;
},
async session({ session, token }) {
// 세션 객체에 액세스 토큰을 주입합니다
session.accessToken = token.accessToken;
return session;
},
},
});
사용자 정보 가져오기
이제 액세스 토큰을 사용하여 OIDC 사용자 정보 엔드포인트에 접근하세요:
// ...
export default async function Home() {
const session = await auth();
// URL을 Logto 엔드포인트로 교체하세요, `/oidc/me`로 끝나야 합니다
const response = await fetch('https://xxx.logto.app/oidc/me', {
headers: {
Authorization: `Bearer ${session?.accessToken}`,
},
});
const user = await response.json();
console.log(user);
// ...
}
위는 간단한 예제입니다. 오류 사례를 처리하는 것을 잊지 마세요.
액세스 토큰 갱신
액세스 토큰은 짧은 기간 동안만 유효합니다. 기본적으로, Next.js는 세션이 생성될 때만 하나를 가져옵니다. 자동 액세스 토큰 갱신을 구현하려면, 리프레시 토큰 회전을 참조하세요.
스코프와 클레임
Logto는 OIDC 스코프 및 클레임 규약을 사용하여 ID 토큰 및 OIDC userinfo 엔드포인트에서 사용자 정보를 가져오기 위한 스코프와 클레임을 정의합니다. "스코프"와 "클레임"은 OAuth 2.0 및 OpenID Connect (OIDC) 사양의 용어입니다.
지원되는 스코프와 해당 클레임 (Claims) 목록은 다음과 같습니다:
openid
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
sub | string | 사용자의 고유 식별자 | 아니요 |
profile
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
name | string | 사용자의 전체 이름 | 아니요 |
username | string | 사용자의 사용자 이름 | 아니요 |
picture | string | 최종 사용자의 프로필 사진 URL. 이 URL은 이미지 파일 (예: PNG, JPEG, 또는 GIF 이미지 파일)을 참조해야 하며, 이미지를 포함하는 웹 페이지를 참조해서는 안 됩니다. 이 URL은 최종 사용자를 설명할 때 표시하기 적합한 프로필 사진을 구체적으로 참조해야 하며, 최종 사용자가 찍은 임의의 사진을 참조해서는 안 됩니다. | 아니요 |
created_at | number | 최종 사용자가 생성된 시간. 시간은 유닉스 에포크 (1970-01-01T00:00:00Z) 이후 밀리초 수로 표현됩니다. | 아니요 |
updated_at | number | 최종 사용자의 정보가 마지막으로 업데이트된 시간. 시간은 유닉스 에포크 (1970-01-01T00:00:00Z) 이후 밀리초 수로 표현됩니다. | 아니요 |
다른 표준 클레임인 family_name
, given_name
, middle_name
, nickname
, preferred_username
, profile
, website
, gender
, birthdate
, zoneinfo
, 및 locale
도 사용자 정보 엔드포인트를 요청할 필요 없이 profile
스코프에 포함됩니다. 위의 클레임과의 차이점은 이러한 클레임은 값이 비어 있지 않을 때만 반환되며, 위의 클레임은 값이 비어 있을 경우 null
을 반환한다는 점입니다.
표준 클레임과 달리, created_at
및 updated_at
클레임은 초 대신 밀리초를 사용합니다.
email
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
string | 사용자의 이메일 주소 | 아니요 | |
email_verified | boolean | 이메일 주소가 검증되었는지 여부 | 아니요 |
phone
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
phone_number | string | 사용자의 전화번호 | 아니요 |
phone_number_verified | boolean | 전화번호가 검증되었는지 여부 | 아니요 |
address
주소 클레임의 세부 사항은 OpenID Connect Core 1.0을 참조하세요.
custom_data
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
custom_data | object | 사용자의 사용자 정의 데이터 | 예 |
identities
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
identities | object | 사용자의 연결된 아이덴티티 | 예 |
sso_identities | array | 사용자의 연결된 SSO 아이덴티티 | 예 |
roles
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
roles | string[] | 사용자의 역할 | 아니요 |
urn:logto:scope:organizations
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
organizations | string[] | 사용자가 속한 조직 ID | 아니요 |
organization_data | object[] | 사용자가 속한 조직 데이터 | 예 |
urn:logto:scope:organization_roles
클레임 이름 | 유형 | 설명 | 사용자 정보 필요 여부 |
---|---|---|---|
organization_roles | string[] | 사용자가 속한 조직 역할, 형식은 <organization_id>:<role_name> | 아니요 |
성능과 데이터 크기를 고려할 때, "사용자 정보 필요 여부"가 "예"인 경우, 해당 클레임은 ID 토큰에 나타나지 않으며, userinfo 엔드포인트 응답에서 반환됩니다.
API 리소스
먼저 🔐 역할 기반 접근 제어 (RBAC)를 읽어 Logto RBAC의 기본 개념과 API 리소스를 적절히 설정하는 방법을 이해하는 것을 권장합니다.
Logto 제공자 구성
API 리소스를 설정한 후, 애플리케이션에서 Logto를 구성할 때 이를 추가할 수 있습니다:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
resource: 'https://shopping.your-app.com/api',
},
},
// ...
},
],
});
각 API 리소스는 자체 권한 (스코프)을 가지고 있습니다.
예를 들어, https://shopping.your-app.com/api
리소스는 shopping:read
및 shopping:write
권한을 가지고 있으며, https://store.your-app.com/api
리소스는 store:read
및 store:write
권한을 가지고 있습니다.
이러한 권한을 요청하려면, 애플리케이션에서 Logto를 구성할 때 추가할 수 있습니다:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email shopping:read shopping:write',
resource: 'https://shopping.your-app.com/api',
},
},
// ...
},
],
});
스코프가 API 리소스와 별도로 정의된 것을 알 수 있습니다. 이는 OAuth 2.0을 위한 리소스 지표가 요청의 최종 스코프가 모든 대상 서비스의 모든 스코프의 데카르트 곱이 될 것이라고 명시하기 때문입니다.
따라서 위의 경우, Logto에서 정의된 스코프를 단순화할 수 있으며, 두 API 리소스 모두 접두사 없이 read
및 write
스코프를 가질 수 있습니다. 그런 다음, Logto 구성에서:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile read write',
resource: 'https://shopping.your-app.com/api',
},
},
// ...
},
],
});
모든 API 리소스에 대해 read
및 write
스코프를 요청하게 됩니다.
API 리소스에 정의되지 않은 스코프를 요청해도 괜찮습니다. 예를 들어, API 리소스에 email
스코프가 없더라도 email
스코프를 요청할 수 있습니다. 사용 불가능한 스코프는 안전하게 무시됩니다.
성공적으로 로그인한 후, Logto는 사용자의 역할에 따라 API 리소스에 적절한 스코프를 발급합니다.
API 리소스를 위한 액세스 토큰 가져오기
Auth.js는 리소스 매개변수 없이 액세스 토큰을 한 번만 가져옵니다. 우리는 액세스 토큰 가져오기를 직접 구현해야 합니다.
리프레시 토큰 얻기
Logto 제공자 설정을 업데이트하고 "prompt" 매개변수를 추가하여 consent
로 설정하고 offline_access
스코프가 포함되어 있는지 확인하세요:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
authorization: {
params: {
prompt: 'consent',
scope: 'openid offline_access shopping:read shopping:write',
resource: 'https://shopping.your-app.com/api',
// ...
},
},
// ...
});
그런 다음 refresh_token
을 세션에 저장하는 콜백을 추가하세요:
export const { handlers, signIn, signOut, auth } = NextAuth({
// ...
callbacks: {
async jwt({ token, account }) {
if (account) {
// ...
token.refreshToken = account.refresh_token;
}
return token;
},
async session({ session, token }) {
// ...
session.refreshToken = token.refreshToken;
return session;
},
},
});
액세스 토큰 가져오기
refresh_token
을 사용하여 Logto의 OIDC 토큰 엔드포인트에서 액세스 토큰을 가져올 수 있습니다.
// ...
export default async function Home() {
const session = await auth();
if (session?.refreshToken) {
// 앱 ID와 비밀을 자신의 것으로 교체하세요. "Integration" 섹션을 확인할 수 있습니다.
const basicAuth = Buffer.from('<logto-app-id>:<logto-app-secret>').toString('base64');
// URL을 자신의 Logto 엔드포인트로 교체하세요. `/oidc/token`으로 끝나야 합니다.
const response = await fetch('https://xxx.logto.app/oidc/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: session.refreshToken,
resource: 'https://shopping.your-app.com/api',
}).toString(),
});
const data = await response.json();
console.log(data.access_token);
}
// ...
}
조직 토큰 가져오기
조직이 처음이라면, 시작하기 위해 🏢 조직 (다중 테넌시)을 읽어보세요.
Logto 클라이언트를 구성할 때 urn:logto:scope:organizations
스코프를 추가해야 합니다:
import NextAuth from 'next-auth';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access urn:logto:scope:organizations',
},
},
// ...
},
],
});
사용자가 로그인하면, 사용자에 대한 조직 토큰을 가져올 수 있습니다:
API 리소스를 위한 액세스 토큰과 유사하게, 리프레시 토큰을 사용하여 조직 액세스 토큰을 가져올 수 있습니다.
// ...
export default async function Home() {
const session = await auth();
if (session?.refreshToken) {
// 앱 ID와 비밀을 자신의 것으로 교체하세요. "Integration" 섹션을 확인할 수 있습니다.
const basicAuth = Buffer.from('<logto-app-id>:<logto-app-secret>').toString('base64');
// URL을 Logto 엔드포인트로 교체하세요. `/oidc/token`으로 끝나야 합니다.
const response = await fetch('https://xxx.logto.app/oidc/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: session.refreshToken,
resource: 'urn:logto:scope:organizations',
organization_id: 'organization-id',
}).toString(),
});
const data = await response.json();
console.log(data.access_token);
}
// ...
}
추가 읽을거리
최종 사용자 흐름: 인증 흐름, 계정 흐름, 및 조직 흐름 커넥터 구성 API 보호Logto 통합을 NextAuth.js v4에서 v5로 마이그레이션하기