Auth.js (Next Auth) アプリケーションへ認証機能の追加
このガイドでは、以前は Next Auth として知られていた Auth.js を使用して、Next.js アプリケーションに Logto を統合する方法を紹介します。
- このガイドでは、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@betapnpm add next-auth@betayarn add next-auth@beta詳細については、 Auth.js のドキュメント を参照してください。
統合
Auth.js プロバイダーの設定
「App Secret」は管理コンソールのアプリケーション詳細ページから見つけてコピーできます:

Auth.js の API ルート設定を変更し、Logto を OIDC プロバイダーとして追加します:
- Auth.js v5
- Next Auth v4
環境変数を設定します:
AUTH_LOGTO_ISSUER=https://xxxx.logto.app/oidc
AUTH_LOGTO_ID=your-logto-app-id
AUTH_LOGTO_SECRET=your-logto-app-secret
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
import NextAuth from 'next-auth';
import Logto from 'next-auth/providers/logto';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [Logto],
});
セッションを維持するためにオプションのミドルウェアを追加することもできます:
export { auth as middleware } from '@/auth';
import NextAuth from 'next-auth';
const handler = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oauth',
// Logto アプリケーションの詳細ページから、"OpenID Provider configuration endpoint" フィールドで well-known URL を取得できます
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 };
wellKnownURL を Logto アプリケーションの "OpenID Provider configuration endpoint" に置き換えます。clientIdとclientSecretを Logto アプリケーションの ID とシークレットに置き換えます。profile関数をカスタマイズして、ユーザープロファイルを Next Auth ユーザーオブジェクトにマッピングします。デフォルトのマッピングは例に示されています。id_token_signed_response_algをES384に設定することを忘れないでください。
詳細は Auth.js ドキュメント を参照してください。
サインインリダイレクト URI の設定
詳細に入る前に、エンドユーザー体験の概要を簡単にご紹介します。サインインプロセスは次のようにシンプルにまとめられます:
- アプリがサインインメソッドを呼び出します。
- ユーザーは Logto のサインインページにリダイレクトされます。ネイティブアプリの場合は、システムブラウザが開かれます。
- ユーザーがサインインし、アプリ(リダイレクト URI として設定)に戻されます。
リダイレクトベースのサインインについて
- この認証 (Authentication) プロセスは 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 ドキュメント を参照してください。
チェックポイント
これで、アプリケーションをテストして、認証 (Authentication) が期待通りに動作するか確認できます。
ユーザー情報の取得
ユーザー情報の表示
ユーザーがサインインすると、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>
);
}
追加のクレーム (Claims) のリクエスト
auth() から返されるオブジェクトに一部のユーザー情報が欠けていることがあります。これは、OAuth
2.0 と OpenID Connect (OIDC) が最小特権の原則 (PoLP) に従うように設計されており、Logto
はこれらの標準に基づいて構築されているためです。
デフォルトでは、限られたクレーム (Claims) が返されます。より多くの情報が必要な場合は、追加のスコープ (Scopes) をリクエストして、より多くのクレーム (Claims) にアクセスできます。
「クレーム (Claim)」はサブジェクトについての主張であり、「スコープ (Scope)」はクレーム (Claims) のグループです。現在のケースでは、クレーム (Claim) はユーザーに関する情報の一部です。
スコープ (Scope) とクレーム (Claim) の関係の非規範的な例を示します:
「sub」クレーム (Claim) は「サブジェクト (Subject)」を意味し、ユーザーの一意の識別子(つまり、ユーザー ID)です。
Logto SDK は常に 3 つのスコープ (Scopes) をリクエストします:openid、profile、および offline_access。
追加のスコープをリクエストするには、Logto プロバイダーのパラメータを設定します:
import NextAuth from 'next-auth';
import Logto from 'next-auth/providers/logto';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Logto({
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
},
},
// ...
}),
],
});
ネットワークリクエストが必要なクレーム (Claims)
ID トークンの肥大化を防ぐために、一部のクレーム (Claims) は取得するためにネットワークリクエストが必要です。例えば、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 はセッションが作成されたときにのみ取得します。自動アクセス トークンリフレッシュを実装するには、リフレッシュ トークンのローテーション を参照してください。
スコープとクレーム (Claims)
Logto は OIDC の スコープ (Scope) とクレーム (Claim) の規約 を使用して、ID トークンおよび OIDC userinfo エンドポイント からユーザー情報を取得するためのスコープ (Scope) とクレーム (Claim) を定義しています。「スコープ (Scope)」と「クレーム (Claim)」は、OAuth 2.0 および OpenID Connect (OIDC) 仕様の用語です。
標準の OIDC クレーム (Claim) については、ID トークンへの含有はリクエストされたスコープ (Scope) によって厳密に決定されます。拡張クレーム (Claim)(例:custom_data や organizations)は、カスタム ID トークン 設定を通じて ID トークンに追加で表示するように構成できます。
こちらはサポートされているスコープと対応するクレーム (Claims) の一覧です:
標準 OIDC スコープ
openid(デフォルト)
| Claim name | Type | 説明 |
|---|---|---|
| sub | string | ユーザーの一意の識別子 |
profile(デフォルト)
| Claim name | Type | 説明 |
|---|---|---|
| name | string | ユーザーのフルネーム |
| username | string | ユーザー名 |
| picture | string | エンドユーザーのプロフィール画像の URL。この URL は画像ファイル(例:PNG、JPEG、GIF 画像ファイル)を指す必要があり、画像を含む Web ページではありません。この URL は、エンドユーザーを説明する際に表示するのに適したプロフィール写真を特に参照するべきであり、エンドユーザーが撮影した任意の写真ではありません。 |
| created_at | number | エンドユーザーが作成された時刻。Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。 |
| updated_at | number | エンドユーザー情報が最後に更新された時刻。Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。 |
その他の 標準クレーム (Standard Claims) には、family_name、given_name、middle_name、nickname、preferred_username、profile、website、gender、birthdate、zoneinfo、locale などがあり、これらも profile スコープに含まれます(userinfo エンドポイントをリクエストする必要はありません)。上記のクレームとの違いは、これらのクレームは値が空でない場合のみ返される点です。一方、上記のクレームは値が空の場合 null が返されます。
標準クレーム (Standard Claims) とは異なり、created_at および updated_at クレームは秒ではなくミリ秒を使用しています。
email
| Claim name | Type | 説明 |
|---|---|---|
string | ユーザーのメールアドレス | |
| email_verified | boolean | メールアドレスが認証済みかどうか |
phone
| Claim name | Type | 説明 |
|---|---|---|
| phone_number | string | ユーザーの電話番号 |
| phone_number_verified | boolean | 電話番号が認証済みかどうか |
address
アドレスクレームの詳細については OpenID Connect Core 1.0 を参照してください。
(デフォルト) と記載されたスコープは常に Logto SDK によってリクエストされます。標準 OIDC スコープ下のクレーム (Claims) は、対応するスコープがリクエストされた場合、常に ID トークン (ID token) に含まれます — 無効化できません。
拡張スコープ
以下のスコープは Logto によって拡張されており、userinfo エンドポイント を通じてクレーム (Claims) を返します。これらのクレームは Console > Custom JWT を通じて ID トークン (ID token) に直接含めるよう設定することもできます。詳細は カスタム ID トークン を参照してください。
custom_data
| Claim name | Type | 説明 | デフォルトで ID トークンに含まれるか |
|---|---|---|---|
| custom_data | object | ユーザーのカスタムデータ |
identities
| Claim name | Type | 説明 | デフォルトで ID トークンに含まれるか |
|---|---|---|---|
| identities | object | ユーザーのリンク済みアイデンティティ | |
| sso_identities | array | ユーザーのリンク済み SSO アイデンティティ |
roles
| Claim name | Type | 説明 | デフォルトで ID トークンに含まれるか |
|---|---|---|---|
| roles | string[] | ユーザーのロール | ✅ |
urn:logto:scope:organizations
| Claim name | Type | 説明 | デフォルトで ID トークンに含まれるか |
|---|---|---|---|
| organizations | string[] | ユーザーが所属する組織 ID | ✅ |
| organization_data | object[] | ユーザーが所属する組織データ |
これらの組織クレーム (Organization Claims) は、不透明トークン (Opaque token) を使用している場合でも userinfo エンドポイント経由で取得できます。ただし、不透明トークン (Opaque token) は組織トークン (Organization token) として組織固有リソースへのアクセスには使用できません。詳細は 不透明トークン (Opaque token) と組織 (Organizations) を参照してください。
urn:logto:scope:organization_roles
| Claim name | Type | 説明 | デフォルトで ID トークンに含まれるか |
|---|---|---|---|
| organization_roles | string[] | ユーザーが所属する組織ロール(<organization_id>:<role_name> 形式) | ✅ |
API リソース
まず 🔐 ロールベースのアクセス制御 (RBAC) を読むことをお勧めします。これにより、Logto の RBAC の基本概念と API リソースを適切に設定する方法を理解できます。
Logto プロバイダーの設定
API リソースを設定したら、アプリで Logto を設定する際にそれらを追加できます:
import NextAuth from 'next-auth';
import Logto from 'next-auth/providers/logto';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
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';
import Logto from 'next-auth/providers/logto';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
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';
import Logto from 'next-auth/providers/logto';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
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);
}
// ...
}
組織トークンの取得
組織 (Organization) が初めての場合は、🏢 組織 (マルチテナンシー) を読んで始めてください。
Logto クライアントを設定する際に、urn:logto:scope:organizations スコープを追加する必要があります:
import NextAuth from 'next-auth';
import Logto from 'next-auth/providers/logto';
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
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);
}
// ...
}