Authelia と Next.js (Auth.js) による OIDC 認証連携ガイド
本記事では、Authelia に OIDC クライアントを追加し、Next.js + Auth.js と連携する手順を記録します。
達成目標:
- Authelia に OIDC クライアントを追加する
- Next.js と Auth.js を利用して OIDC 認証を実装する
https://github.com/kntks/blog-code/tree/main/2025/08/authelia-nextjs-authjs-oidc-integration
- Docker および Docker Compose が利用可能な環境
- ローカルでの開発・検証を目的とする
バージョン情報
Section titled “バージョン情報”ソフトウェア | バージョン |
---|---|
macOS | Sequoia 15.6 |
Docker | 28.3.3 |
Docker Compose | 2.32.4 |
Authelia | 4.39.5 |
最終的なディレクトリ構成
Section titled “最終的なディレクトリ構成”本記事で作成するプロジェクトの最終的なディレクトリ構成:
.├── authelia│ ├── config│ ├── keys│ └── secrets├── compose.yaml├── next.config.ts├── package.json├── pnpm-lock.yaml├── postcss.config.mjs├── README.md├── src│ ├── app│ ├── auth.ts│ └── middleware.ts└── tsconfig.json
Next.js プロジェクトの設定
Section titled “Next.js プロジェクトの設定”1. プロジェクトの作成
Section titled “1. プロジェクトの作成”まず、create-next-app
を使用して Next.js プロジェクトを新規作成します。
pnpm dlx create-next-app authelia-nextjs-authjs-oidc-integration --ts --app --turbopack --import-alias "@/*" --tailwind --src-dir --no-eslint --use-pnpm
2. Auth.js のインストールと設定
Section titled “2. Auth.js のインストールと設定”次に、Auth.js (next-auth
) をインストールし、シークレットキーを生成します。
pnpm add next-auth@betapnpm dlx auth secret
AUTH_SECRET
として生成された値は .env.local
ファイルに設定されます。
3. API ルートの作成
Section titled “3. API ルートの作成”公式ドキュメント に従い、Auth.js の API ルートを作成します。
import { handlers } from "@/auth"export const { GET, POST } = handlers
4. Auth.js の設定
Section titled “4. Auth.js の設定”2025 年 8 月現在、Auth.js には Authelia の公式プロバイダーが存在しないため、カスタムプロバイダーとして設定します。
provider
の authorization
パラメータで groups
を指定することで、アクセストークンを用いて UserInfo エンドポイントにリクエストした際に、ユーザーのグループ情報を取得できるようになります。
import NextAuth, { type DefaultSession } from "next-auth"
declare module "next-auth" { /** * Returned by `auth`, `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context */ interface Session { user: { /** The user's access token. */ accessToken: string /** * By default, TypeScript merges new interface properties and overwrites existing ones. * In this case, the default session user properties will be overwritten, * with the new ones defined above. To keep the default session user properties, * you need to add them back into the newly declared interface. */ } & DefaultSession["user"] }}
import { JWT } from "next-auth/jwt"
declare module "next-auth/jwt" { /** Returned by the `jwt` callback and `auth`, when using JWT sessions */ interface JWT { accessToken?: string }}
export const { handlers, signIn, signOut, auth } = NextAuth({ providers: [ { id: "authelia", name: "Authelia", type: "oidc", issuer: "http://127.0.0.1:9091", checks: ["state"], clientId: process.env.AUTHELIA_CLIENT_ID, clientSecret: process.env.AUTHELIA_CLIENT_SECRET, authorization: { params: { scope: "openid email profile groups" } }, }, ], callbacks: { /** * ユーザーが認証されているか確認する * @see https://authjs.dev/getting-started/session-management/protecting?framework=express#nextjs-middleware */ async authorized({ auth }) { // ユーザーが認証されているか確認するロジックを実装 return !!auth }, /** * @see https://authjs.dev/guides/restricting-user-access */ async signIn({ user, account }) { if(!account) return false
/** * userからidを取得できる */ // user: { id: '4d39df76-9c47-45f2-9d5f-ed47a4957821', email: undefined }
/** * accountには "access_token" があるため、ここで Token Introspection を実行する */
// account: { // access_token: 'authelia_at_eiyrW6Vp6IG9oQK0OCLvVbhCMqFebEg44bJDXW1PIaU.3sw35DUOA3dHDlaroOvoKiM0YEsrlEP1-RzMoSpCJ7w', // expires_in: 3599, // id_token: 'eyJh<base64url>...<base64url>', // scope: 'openid email profile groups', // token_type: 'bearer', // expires_at: 1755705138, // provider: 'authelia', // type: 'oidc', // providerAccountId: 'fb3feb7b-273e-4673-9672-d40a5e106fc1' // },
// falseやErrorをthrowする場合、Access Deniedになる return true }, async jwt({ token, user, profile, account, trigger }) { if(trigger !== "signIn") return token if(!account) return token
token.accessToken = account.access_token return token }, async session({ session, token }) { if(token.accessToken) session.user.accessToken = token.accessToken return session } }})
参考:
5. Middleware の設定
Section titled “5. Middleware の設定”認証が必要なページを保護するため、middleware.ts
を作成します。
export { auth as middleware } from "@/auth"
export const config = { matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],}
参考:
6. フロントエンドの実装
Section titled “6. フロントエンドの実装”認証状態に応じて UI を切り替えるためのコンポーネントを実装します。
import { signOut } from "@/auth"
export default function Home() { return ( <div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20"> <main className="gap-[32px] row-start-2 items-center sm:items-start text-5xl"> <h1 className="mb-8">Authelia Example</h1> <form action={async () => { "use server" await signOut() }} > <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" > Signout </button> </form> </main> </div> );}
Authelia の設定
Section titled “Authelia の設定”Authelia の設定ファイルと関連ファイルを準備します。 以下のディレクトリ構成になるようにファイルを作成します。
authelia├── config│ ├── configuration.yml│ └── users_database.yml├── keys│ ├── private.pem│ └── public.crt└── secrets ├── JWT_SECRET ├── MYAPP_CLIENT_SECRET ├── SESSION_SECRET ├── STORAGE_ENCRYPTION_KEY └── STORAGE_PASSWORD
1. configuration.yml
の作成
Section titled “1. configuration.yml の作成”Authelia では、シークレットの管理方法として、環境変数(_FILE
サフィックスを使用)または設定ファイル内のテンプレート機能が提供されています。
本記事では、設定の一元管理と可読性の観点からテンプレート機能を利用します。
参考:
authelia/config/configuration.yml
# config.template.yaml# https://github.com/authelia/authelia/blob/master/config.template.yml
log: level: debug format: json
session: secret: {{ secret "/secrets/SESSION_SECRET" }} name: 'authelia_session' same_site: 'lax' inactivity: '5m' expiration: '1h' remember_me: '1M' cookies: - domain: '127.0.0.1' authelia_url: 'https://127.0.0.1:9091' # default_redirection_url: 'https://myapp.local' name: 'authelia_session' same_site: 'lax' inactivity: '5m' expiration: '1h' remember_me: '1d'
authentication_backend: file: path: /config/users_database.yml
storage: encryption_key: {{ secret "/secrets/STORAGE_ENCRYPTION_KEY" }}
local: path: /data/db.sqlite3
notifier: filesystem: filename: /data/notification.txt
access_control: default_policy: one_factor
identity_validation: reset_password: jwt_secret: {{ secret "/secrets/JWT_SECRET" }}
identity_providers: oidc: jwks: - key_id: 'example' algorithm: 'RS256' use: 'sig' key: {{ secret "/keys/private.pem" | mindent 10 "|" | msquote }}
clients: - client_id: "myapp" client_name: "My App" client_secret: {{ secret "/secrets/MYAPP_CLIENT_SECRET" }} authorization_policy: one_factor ## access tokenをjwtにしたい場合は設定を追加 # access_token_signed_response_alg: RS256 redirect_uris: - "http://localhost:3000/api/auth/callback/authelia" # Next.js + Auth.js のコールバックURL - "http://localhost:9876/callback" # oauth2cのコールバックURL audience: - 'https://app.localhost' scopes: - 'openid' - 'groups' - 'email' - 'profile' grant_types: - 'authorization_code' response_types: - 'code' response_modes: - 'form_post' - 'query' - 'fragment'
2. users_database.yml
の準備
Section titled “2. users_database.yml の準備”このファイルは、テスト用のユーザー情報を定義します。docker compose up
の実行時に、authentication_backend.file.path
で指定されたパスに自動生成されますが、事前に作成することも可能です。
3. 鍵ペアの生成
Section titled “3. 鍵ペアの生成”OIDC プロバイダーが使用する署名用の鍵ペア(秘密鍵と公開鍵)を生成します。
docker run --rm -v "./authelia/keys:/keys" authelia/authelia:latest authelia crypto certificate rsa generate --directory /keys
このコマンドにより、authelia/keys
ディレクトリに private.pem
と public.crt
が生成されます。
4. シークレット情報の生成
Section titled “4. シークレット情報の生成”セッションやストレージの暗号化に使用するシークレット文字列を生成します。 以下のファイルを作成します。
secrets/├── JWT_SECRET├── MYAPP_CLIENT_SECRET├── SESSION_SECRET├── STORAGE_ENCRYPTION_KEY└── STORAGE_PASSWORD
authelia crypto rand
コマンドを実行して、各ファイルにランダムな文字列を生成・保存します。
生成コマンドの例:
# 各シークレットファイルに対して実行docker run --rm authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric > authelia/secrets/JWT_SECRETdocker run --rm authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric > authelia/secrets/MYAPP_CLIENT_SECRET# ... 他のシークレットも同様に生成
5. 自己署名証明書の生成
Section titled “5. 自己署名証明書の生成”ローカル開発環境で HTTPS を有効にするため、自己署名証明書を生成します。
RSA証明書:
docker run --rm -v "./authelia/keys:/keys" authelia/authelia:latest authelia crypto certificate rsa generate --directory /keys
ECDSA証明書:
docker run --rm -v "./authelia/keys:/keys" authelia/authelia:latest authelia crypto certificate ecdsa generate --directory /keys
参考:
- Generating an RSA Self-Signed Certificate - Authelia
- authelia crypto certificate ecdsa generate - Authelia
- Authelia - OpenZiti
シークレットファイルの参照設定
Section titled “シークレットファイルの参照設定”File Filter を使用してシークレットファイルを参照できます。
Template には secret 関数があるため、シークレットファイルを参照できます。
identity_providers: oidc: jwks: - key: {{ secret "/config/secrets/absolute/path/to/jwks/rsa.2048.pem" | mindent 10 "|" | msquote }}
参考:
Authelia を起動するターミナル
docker compose up
Next.js を起動するターミナル
pnpm dev
アクセストークンの仕様
Section titled “アクセストークンの仕様”トークンの形式
Section titled “トークンの形式”デフォルトでは、Authelia が発行するアクセストークンは 不透明トークン (Opaque Token) であり、JWT ではありません。これは、トークン自体に情報を含まないランダムな文字列です。
不透明トークンの例:
access_token: 'authelia_at_lnlaoMHCAAAAAAAAAAAAAAw8O7EG8QJ_4w.0eDxxxxxxxxxis'
トークンの検証方法
Section titled “トークンの検証方法”不透明トークンを検証するには、トークンイントロスペクションエンドポイント (/api/oidc/introspection
) を利用する必要があります。
JWT形式のアクセストークンを使用する場合
Section titled “JWT形式のアクセストークンを使用する場合”アクセストークンを JWT 形式で発行したい場合は、configuration.yml
の OIDC クライアント設定に access_token_signed_response_alg
を追加します。
identity_providers: oidc: clients: - id: my_client access_token_signed_response_alg: RS256 # 他の設定項目...
参考:
- Why isn’t the Access Token a JSON Web Token?
- 不透明トークンと JWT の比較 - Logto
- Opaque and JWT access tokens - Ory
トラブルシューティング
Section titled “トラブルシューティング”ID トークンに groups
クレームが含まれない
Section titled “ID トークンに groups クレームが含まれない”認証リクエストの scope
に groups
を含めても、ID トークンに groups
クレームが含まれない場合があります。
この場合、Auth.js のプロバイダー設定で authorization
パラメータを明示的に指定し、UserInfo エンドポイントからグループ情報を取得する必要があります。
解決策の例:
export const { handlers, signIn, signOut, auth } = NextAuth({ providers: [ { id: "authelia", name: "Authelia", type: "oidc", issuer: "http://127.0.0.1:9091", checks: ["state"], clientId: process.env.AUTHELIA_CLIENT_ID, clientSecret: process.env.AUTHELIA_CLIENT_SECRET, authorization: { params: { scope: "openid email profile groups" } }, }, ], // ...})
参考:Configuring an OAuth provider - Auth.js
oauth2c
を用いた OIDC フローのテスト
Section titled “oauth2c を用いた OIDC フローのテスト”oauth2c
は、OAuth 2.0/OIDC フローをテストするための便利な CLI ツールです。
1. インストール
Section titled “1. インストール”brew install cloudentity/tap/oauth2c
2. 認証・トークン取得の実行
Section titled “2. 認証・トークン取得の実行”以下のコマンドで、認可コードフローを実行し、トークンを取得します。
oauth2c http://127.0.0.1:9091 \ --client-id myapp \ --client-secret <client-secret> \ --response-types code \ --response-mode query \ --grant-type authorization_code \ --auth-method client_secret_basic \ --scope openid,groups
3. レスポンスの確認
Section titled “3. レスポンスの確認”不透明トークン (デフォルト) の場合:
access_token
はランダムな文字列ですが、id_token
はデコード可能なJWTです。
Response:{ "access_token": "authelia_at_KspW0WeBHTpiClOUmoxcCA5cRads8iATsOX5qmGwyMs.Q8ddYDQ6qOyHViv7ziA1dk05YSlOfbMUUgq2ogn8xxg", "expires_in": 3599, "id_token": "eyJ...-og", "scope": "openid", "token_type": "bearer"}ERROR go-jose/go-jose: compact JWS format must have three partsID token:{ "amr": ["pwd", "kba"], "at_hash": "vtyV4wA9XqUpYgdb3jeYaw", "aud": ["myapp"], "auth_time": 1755435997, "azp": "myapp", "exp": 1755439741, "iat": 1755436141, "iss": "http://127.0.0.1:9091", "jti": "c208c125-38c1-4672-b501-19f54edf27bb", "nonce": "5oxm3rQVW826e5u8gjuAxf", "sub": "72b04f5a-46de-4425-ab60-2a1a193dc7f5"}
JWT アクセストークンの場合:
access_token
もデコード可能な JWT になります。
{ "access_token": "eyJ...Zg", "expires_in": 3599, "id_token": "eyJ...cHA", "scope": "openid", "token_type": "bearer"}Access token:{ "aud": [], "client_id": "myapp", "exp": 1755441782, "iat": 1755438182, "iss": "http://127.0.0.1:9091", "jti": "a233795f-24ad-4832-bdb4-26cecfcae94e", "nbf": 1755438182, "scp": ["openid"], "sub": "72b04f5a-46de-4425-ab60-2a1a193dc7f5"}ID token:{ "amr": ["pwd", "kba"], "at_hash": "tUIsuMrV0Xo5maQVhIQZwg", "aud": ["myapp"], "auth_time": 1755438176, "azp": "myapp", "exp": 1755441782, "iat": 1755438182, "iss": "http://127.0.0.1:9091", "jti": "01cb8499-5ffd-4eca-9095-348cfd102a5a", "nonce": "QLQgxigXtKxjN8e7z4VmZ7", "sub": "72b04f5a-46de-4425-ab60-2a1a193dc7f5"}
参考:https://github.com/SecureAuthCorp/oauth2c
UserInfo エンドポイントのテスト
Section titled “UserInfo エンドポイントのテスト”取得したアクセストークンを使用して、UserInfo エンドポイントからユーザー情報を取得します。
oauth2c http://127.0.0.1:9091 \ --client-id myapp \ --client-secret 1ZtLBKio7tYy4YEMdsfBWmarsihymzmndbedxIoHCA1deFVPAOzIxAkFdwA1QpNGjNeB5eRg \ --response-types code \ --response-mode query \ --grant-type authorization_code \ --auth-method client_secret_basic \ --scopes openid,groups
UserInfo エンドポイントの実行
Section titled “UserInfo エンドポイントの実行”# アクセストークンを変数として定義するaccess_token="authelia_at_jtkIooukcOIW6zgw8OPP3TcSh9UgYtSY15XMhMKnTEY.okvQemHNw6uAugHVuFef6HqpdkVNNddVnj7AfTlFtPk"
# curl で UserInfo エンドポイントを実行するcurl -H "Authorization: Bearer $access_token" http://127.0.0.1:9091/api/oidc/userinfo
レスポンス例:
scope
に groups
を含めた場合、ユーザーが所属するグループ情報が返されます。
{"groups":["admins","dev"],"rat":1755441540,"sub":"72b04f5a-46de-4425-ab60-2a1a193dc7f5"}
Token Introspection エンドポイントのテスト
Section titled “Token Introspection エンドポイントのテスト”アクセストークンの有効性を検証します。
client_id=myappclient_secret=1ZtLBKio7tYy4YEMdsfBWmarsihymzmndbedxIoHCA1deFVPAOzIxAkFdwA1QpNGjNeB5eRg
アクセストークンの取得
Section titled “アクセストークンの取得”oauth2c http://127.0.0.1:9091 \ --client-id $client_id \ --client-secret $client_secret \ --response-types code \ --response-mode query \ --grant-type authorization_code \ --auth-method client_secret_basic \ --scopes openid
Introspection エンドポイントの実行
Section titled “Introspection エンドポイントの実行”# アクセストークンを変数として定義するaccess_token="authelia_at_jtkIooukcOIW6zgw8OPP3TcSh9UgYtSY15XMhMKnTEY.okvQemHNw6uAugHVuFef6HqpdkVNNddVnj7AfTlFtPk"
# curl で Introspection エンドポイントを実行するcurl -X POST \ "http://127.0.0.1:9091/api/oidc/introspection" \ -H "Content-Type: application/x-www-form-urlencoded" \ -H "Authorization: Basic $(echo -n "$client_id:$client_secret" | base64)" \ -d "token=$access_token"
レスポンス例:
トークンが有効な場合、"active":true
と共にトークンのメタデータが返されます。
{"active":true,"client_id":"myapp","exp":1755448886,"iat":1755445286,"scope":"openid","sub":"72b04f5a-46de-4425-ab60-2a1a193dc7f5","username":"authelia"}
Authelia の主要な OIDC エンドポイント
Section titled “Authelia の主要な OIDC エンドポイント”エンドポイントの確認方法
Section titled “エンドポイントの確認方法”Discoverable Endpoints から確認できます。
Well-Known Discovery Endpoint の実行
Section titled “Well-Known Discovery Endpoint の実行”curl -s http://127.0.0.1:9091/.well-known/openid-configuration | jq 'with_entries(select(.key | endswith("_endpoint") or test("jwks_uri")))'
レスポンス例:
{ "jwks_uri": "http://127.0.0.1:9091/jwks.json", "authorization_endpoint": "http://127.0.0.1:9091/api/oidc/authorization", "token_endpoint": "http://127.0.0.1:9091/api/oidc/token", "introspection_endpoint": "http://127.0.0.1:9091/api/oidc/introspection", "revocation_endpoint": "http://127.0.0.1:9091/api/oidc/revocation", "device_authorization_endpoint": "http://127.0.0.1:9091/api/oidc/device-authorization", "pushed_authorization_request_endpoint": "http://127.0.0.1:9091/api/oidc/pushed-authorization-request", "userinfo_endpoint": "http://127.0.0.1:9091/api/oidc/userinfo"}