メインコンテンツまでスキップ

あなたの Auth.js (Next Auth) アプリケーションに認証 (Authentication) を追加する

このガイドでは、以前は Next Auth として知られていた Auth.js を使用して、Logto を Next.js アプリケーションに統合する方法を紹介します。

ヒント:
  • このガイドでは、Next.js プロジェクトに Next Auth を設定していることを前提としています。まだ設定していない場合は、Next Auth ドキュメント を参照して始めてください。

前提条件

インストール

お気に入りのパッケージマネージャーを使用して Auth.js をインストールします:

npm i next-auth@beta

詳細については、 Auth.js のドキュメント を参照してください。

統合

Auth.js プロバイダーを設定する

ヒント:

「App Secret」は管理コンソールのアプリケーション詳細ページから見つけてコピーできます:

App Secret

Auth.js の API ルート設定を変更し、Logto を OIDC プロバイダーとして追加します:

./app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/auth';
export const { GET, POST } = handlers;
./auth.ts
import NextAuth from 'next-auth';

export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',
name: 'Logto',
type: 'oidc',
// Logto アプリケーションの詳細ページから発行者 (Issuer) の値を取得できます。
// フィールド「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,
};
},
},
],
});
  1. issuer URL をあなたの Logto アプリケーションの「Issuer endpoint」に置き換えます。
  2. clientIdclientSecret をあなたの Logto アプリケーションの ID とシークレットに置き換えます。
  3. ユーザープロファイルを Next Auth ユーザーオブジェクトにマッピングするために profile 関数をカスタマイズします。デフォルトのマッピングは例に示されています。

次に、セッションを維持するためのオプションのミドルウェアを追加することもできます:

./middleware.ts
export { auth as middleware } from '@/auth';

詳細は Auth.js ドキュメント を参照してください。

サインインリダイレクト URI を設定する

詳細に入る前に、エンドユーザーの体験について簡単に説明します。サインインプロセスは次のように簡略化できます:

  1. あなたのアプリがサインインメソッドを呼び出します。
  2. ユーザーは Logto のサインインページにリダイレクトされます。ネイティブアプリの場合、システムブラウザが開かれます。
  3. ユーザーがサインインし、あなたのアプリにリダイレクトされます(リダイレクト URI として設定されています)。

リダイレクトベースのサインインについて

  1. この認証 (Authentication) プロセスは OpenID Connect (OIDC) プロトコルに従い、Logto はユーザーのサインインを保護するために厳格なセキュリティ対策を講じています。
  2. 複数のアプリがある場合、同じアイデンティティプロバイダー (Logto) を使用できます。ユーザーがあるアプリにサインインすると、Logto は別のアプリにアクセスした際に自動的にサインインプロセスを完了します。

リダイレクトベースのサインインの理論と利点について詳しく知るには、Logto サインイン体験の説明を参照してください。


注記:

以下のコードスニペットでは、あなたのアプリが http://localhost:3000/ で実行されていると仮定しています。

Logto コンソールのアプリケーション詳細ページに切り替えましょう。リダイレクト URI http://localhost:3000/api/auth/callback/logto を追加し、「変更を保存」をクリックします。

Logto コンソールのリダイレクト URI

サインインとサインアウトを実装する

サインインとサインアウトボタンを実装する

app/components/sign-in.tsx
import { signIn } from '@/auth';

export default function SignIn() {
return (
<form
action={async () => {
'use server';
await signIn('logto');
}}
>
<button type="submit">Sign In</button>
</form>
);
}
app/components/sign-out.tsx
import { signOut } from '@/auth';

export function SignOut() {
return (
<form
action={async () => {
'use server';
await signOut();
}}
>
<button type="submit">Sign Out</button>
</form>
);
}

ページにサインインとサインアウトボタンを表示する

app/page.tsx
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() の戻り値はユーザー情報を含むオブジェクトになります。この情報をアプリで表示できます:

app/page.tsx
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) をリクエストします:openidprofile、および offline_access

追加のスコープをリクエストするには、Logto プロバイダーのパラメータを設定します:

./auth.ts
import NextAuth from 'next-auth';

export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
{
id: 'logto',,
// ...
authorization: {
params: {
scope: 'openid offline_access profile email',
},
},
// ...
},
],
});

ネットワークリクエストが必要なクレーム (Claims)

ID トークンの肥大化を防ぐために、一部のクレーム (Claims) は取得するためにネットワークリクエストが必要です。例えば、custom_data クレーム (Claim) はスコープでリクエストされてもユーザーオブジェクトに含まれません。これらのクレーム (Claims) にアクセスするには、ユーザー情報を取得するためのネットワークリクエストを行う必要があります。

アクセス トークンを取得する

NextAuth の設定を更新して、アクセス トークンを取得できるようにします:

./auth.ts
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 ユーザー情報エンドポイントにアクセスします:

./app/page.tsx
// ...

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 の スコープとクレームの規約 を使用して、ID トークンおよび OIDC userinfo エンドポイント からユーザー情報を取得するためのスコープとクレームを定義します。「スコープ」と「クレーム」は、OAuth 2.0 および OpenID Connect (OIDC) 仕様からの用語です。

サポートされているスコープと対応するクレーム (Claims) のリストはこちらです:

openid

クレーム名タイプ説明ユーザー情報が必要か?
substringユーザーの一意の識別子いいえ

profile

クレーム名タイプ説明ユーザー情報が必要か?
namestringユーザーのフルネームいいえ
usernamestringユーザーのユーザー名いいえ
picturestringエンドユーザーのプロフィール写真の URL。この URL は、画像を含む Web ページではなく、画像ファイル(例えば PNG、JPEG、または GIF 画像ファイル)を指す必要があります。この URL は、エンドユーザーを説明する際に表示するのに適したプロフィール写真を特に参照するべきであり、エンドユーザーが撮影した任意の写真を参照するべきではありません。いいえ
created_atnumberエンドユーザーが作成された時間。時間は Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。いいえ
updated_atnumberエンドユーザーの情報が最後に更新された時間。時間は Unix エポック(1970-01-01T00:00:00Z)からのミリ秒数で表されます。いいえ

その他の 標準クレーム には、family_namegiven_namemiddle_namenicknamepreferred_usernameprofilewebsitegenderbirthdatezoneinfo、および locale が含まれ、ユーザー情報エンドポイントを要求することなく profile スコープに含まれます。上記のクレームと異なる点は、これらのクレームは値が空でない場合にのみ返されることであり、上記のクレームは値が空の場合に null を返します。

注記:

標準クレームとは異なり、created_atupdated_at クレームは秒ではなくミリ秒を使用しています。

email

クレーム名タイプ説明ユーザー情報が必要か?
emailstringユーザーのメールアドレスいいえ
email_verifiedbooleanメールアドレスが確認済みかどうかいいえ

phone

クレーム名タイプ説明ユーザー情報が必要か?
phone_numberstringユーザーの電話番号いいえ
phone_number_verifiedboolean電話番号が確認済みかどうかいいえ

address

住所クレームの詳細については、OpenID Connect Core 1.0 を参照してください。

custom_data

クレーム名タイプ説明ユーザー情報が必要か?
custom_dataobjectユーザーのカスタムデータはい

identities

クレーム名タイプ説明ユーザー情報が必要か?
identitiesobjectユーザーのリンクされたアイデンティティはい
sso_identitiesarrayユーザーのリンクされた SSO アイデンティティはい

urn:logto:scope:organizations

クレーム名タイプ説明ユーザー情報が必要か?
organizationsstring[]ユーザーが所属する組織の IDいいえ
organization_dataobject[]ユーザーが所属する組織のデータはい

urn:logto:scope:organization_roles

クレーム名タイプ説明ユーザー情報が必要か?
organization_rolesstring[]ユーザーが所属する組織のロールで、<organization_id>:<role_name> の形式いいえ

パフォーマンスとデータサイズを考慮して、「ユーザー情報が必要か?」が「はい」の場合、クレームは ID トークンに表示されず、ユーザー情報エンドポイント のレスポンスで返されます。

API リソース

まず 🔐 ロールベースのアクセス制御 (RBAC) を読むことをお勧めします。これにより、Logto の RBAC の基本概念と API リソースを適切に設定する方法を理解できます。

Logto プロバイダーの設定

API リソースを設定したら、アプリで Logto を設定する際にそれらを追加できます:

./auth.ts
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:readshopping:write の権限があり、https://store.your-app.com/api リソースには store:readstore:write の権限があります。

これらの権限を要求するには、アプリで Logto を設定する際にそれらを追加できます:

./auth.ts
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 リソースは、プレフィックスなしで readwrite スコープを持つことができます。その後、Logto の設定では:

./auth.ts
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 リソースは、readwrite の両方のスコープを要求します。

注記:

API リソースで定義されていないスコープを要求しても問題ありません。例えば、API リソースに email スコープが利用できなくても、email スコープを要求できます。利用できないスコープは安全に無視されます。

サインインが成功すると、Logto はユーザーのロールに応じて適切なスコープを API リソースに発行します。

API リソースのためのアクセス トークンの取得

Auth.js はリソースパラメーターなしでアクセス トークンを一度だけ取得します。アクセス トークンの取得を自分で実装する必要があります。

リフレッシュ トークンを取得する

Logto プロバイダーの設定を更新し、「prompt」パラメーターを追加して consent に設定し、offline_access スコープが含まれていることを確認します:

./auth.ts
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 をセッションに保存するコールバックを追加します:

./auth.ts
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 トークンエンドポイントからアクセス トークンを取得できます。

./app/page.tsx
// ...

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 スコープを追加する必要があります:

./auth.ts
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 リソースのアクセス トークンと同様に、リフレッシュ トークンを使用して組織アクセス トークンを取得できます。

./app/page.tsx
// ...

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);
}

// ...
}

さらなる読み物

エンドユーザーフロー:認証 (Authentication) フロー、アカウントフロー、組織フロー コネクターを設定する あなたの API を保護する NextAuth.js v4 から v5 への Logto 統合の移行