Skip to content

Authelia と Next.js (Auth.js) による OIDC 認証連携ガイド

本記事では、Authelia に OIDC クライアントを追加し、Next.js + Auth.js と連携する手順を記録します。

達成目標:

  • Authelia に OIDC クライアントを追加する
  • Next.js と Auth.js を利用して OIDC 認証を実装する

authelia-nextjs

https://github.com/kntks/blog-code/tree/main/2025/08/authelia-nextjs-authjs-oidc-integration

  • Docker および Docker Compose が利用可能な環境
  • ローカルでの開発・検証を目的とする
ソフトウェアバージョン
macOSSequoia 15.6
Docker28.3.3
Docker Compose2.32.4
Authelia4.39.5

本記事で作成するプロジェクトの最終的なディレクトリ構成:

Terminal window
.
├── 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

まず、create-next-app を使用して Next.js プロジェクトを新規作成します。

Terminal window
pnpm dlx create-next-app authelia-nextjs-authjs-oidc-integration --ts --app --turbopack --import-alias "@/*" --tailwind --src-dir --no-eslint --use-pnpm

次に、Auth.js (next-auth) をインストールし、シークレットキーを生成します。

Terminal window
pnpm add next-auth@beta
pnpm dlx auth secret

AUTH_SECRET として生成された値は .env.local ファイルに設定されます。

公式ドキュメント に従い、Auth.js の API ルートを作成します。

src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const { GET, POST } = handlers

2025 年 8 月現在、Auth.js には Authelia の公式プロバイダーが存在しないため、カスタムプロバイダーとして設定します。

providerauthorization パラメータで groups を指定することで、アクセストークンを用いて UserInfo エンドポイントにリクエストした際に、ユーザーのグループ情報を取得できるようになります。

src/auth.ts
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
}
}
})

参考:

認証が必要なページを保護するため、middleware.ts を作成します。

src/middleware.ts
export { auth as middleware } from "@/auth"
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

参考:

認証状態に応じて UI を切り替えるためのコンポーネントを実装します。

src/app/page.tsx
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 の設定ファイルと関連ファイルを準備します。 以下のディレクトリ構成になるようにファイルを作成します。

Terminal window
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

Authelia では、シークレットの管理方法として、環境変数(_FILE サフィックスを使用)または設定ファイル内のテンプレート機能が提供されています。 本記事では、設定の一元管理と可読性の観点からテンプレート機能を利用します。

参考:

authelia/config/configuration.yml
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'

このファイルは、テスト用のユーザー情報を定義します。docker compose up の実行時に、authentication_backend.file.path で指定されたパスに自動生成されますが、事前に作成することも可能です。

OIDC プロバイダーが使用する署名用の鍵ペア(秘密鍵と公開鍵)を生成します。

Terminal window
docker run --rm -v "./authelia/keys:/keys" authelia/authelia:latest authelia crypto certificate rsa generate --directory /keys

このコマンドにより、authelia/keys ディレクトリに private.pempublic.crt が生成されます。

セッションやストレージの暗号化に使用するシークレット文字列を生成します。 以下のファイルを作成します。

Terminal window
secrets/
├── JWT_SECRET
├── MYAPP_CLIENT_SECRET
├── SESSION_SECRET
├── STORAGE_ENCRYPTION_KEY
└── STORAGE_PASSWORD

authelia crypto rand コマンドを実行して、各ファイルにランダムな文字列を生成・保存します。

生成コマンドの例:

Terminal window
# 各シークレットファイルに対して実行
docker run --rm authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric > authelia/secrets/JWT_SECRET
docker run --rm authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric > authelia/secrets/MYAPP_CLIENT_SECRET
# ... 他のシークレットも同様に生成

ローカル開発環境で HTTPS を有効にするため、自己署名証明書を生成します。

RSA証明書:

Terminal window
docker run --rm -v "./authelia/keys:/keys" authelia/authelia:latest authelia crypto certificate rsa generate --directory /keys

ECDSA証明書:

Terminal window
docker run --rm -v "./authelia/keys:/keys" authelia/authelia:latest authelia crypto certificate ecdsa generate --directory /keys

参考:

シークレットファイルの参照設定

Section titled “シークレットファイルの参照設定”

File Filter を使用してシークレットファイルを参照できます。

Template には secret 関数があるため、シークレットファイルを参照できます。

authelia/config/configuration.yml
identity_providers:
oidc:
jwks:
- key: {{ secret "/config/secrets/absolute/path/to/jwks/rsa.2048.pem" | mindent 10 "|" | msquote }}

参考:

Authelia を起動するターミナル

Terminal window
docker compose up

Next.js を起動するターミナル

Terminal window
pnpm dev

デフォルトでは、Authelia が発行するアクセストークンは 不透明トークン (Opaque Token) であり、JWT ではありません。これは、トークン自体に情報を含まないランダムな文字列です。

不透明トークンの例:

access_token: 'authelia_at_lnlaoMHCAAAAAAAAAAAAAAw8O7EG8QJ_4w.0eDxxxxxxxxxis'

不透明トークンを検証するには、トークンイントロスペクションエンドポイント (/api/oidc/introspection) を利用する必要があります。

JWT形式のアクセストークンを使用する場合

Section titled “JWT形式のアクセストークンを使用する場合”

アクセストークンを JWT 形式で発行したい場合は、configuration.yml の OIDC クライアント設定に access_token_signed_response_alg を追加します。

authelia/config/configuration.yml
identity_providers:
oidc:
clients:
- id: my_client
access_token_signed_response_alg: RS256
# 他の設定項目...

参考:

ID トークンに groups クレームが含まれない

Section titled “ID トークンに groups クレームが含まれない”

認証リクエストの scopegroups を含めても、ID トークンに groups クレームが含まれない場合があります。 この場合、Auth.js のプロバイダー設定で authorization パラメータを明示的に指定し、UserInfo エンドポイントからグループ情報を取得する必要があります。

解決策の例:

src/auth.ts
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 ツールです。

Terminal window
brew install cloudentity/tap/oauth2c

以下のコマンドで、認可コードフローを実行し、トークンを取得します。

Terminal window
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

不透明トークン (デフォルト) の場合: access_token はランダムな文字列ですが、id_token はデコード可能なJWTです。

Terminal window
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 parts
ID 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 になります。

Terminal window
{
"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 エンドポイントからユーザー情報を取得します。

Terminal window
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
Terminal window
# アクセストークンを変数として定義する
access_token="authelia_at_jtkIooukcOIW6zgw8OPP3TcSh9UgYtSY15XMhMKnTEY.okvQemHNw6uAugHVuFef6HqpdkVNNddVnj7AfTlFtPk"
# curl で UserInfo エンドポイントを実行する
curl -H "Authorization: Bearer $access_token" http://127.0.0.1:9091/api/oidc/userinfo

レスポンス例: scopegroups を含めた場合、ユーザーが所属するグループ情報が返されます。

{"groups":["admins","dev"],"rat":1755441540,"sub":"72b04f5a-46de-4425-ab60-2a1a193dc7f5"}

Token Introspection エンドポイントのテスト

Section titled “Token Introspection エンドポイントのテスト”

アクセストークンの有効性を検証します。

Terminal window
client_id=myapp
client_secret=1ZtLBKio7tYy4YEMdsfBWmarsihymzmndbedxIoHCA1deFVPAOzIxAkFdwA1QpNGjNeB5eRg
Terminal window
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 エンドポイントの実行”
Terminal window
# アクセストークンを変数として定義する
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 エンドポイント”

Discoverable Endpoints から確認できます。

Terminal window
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"
}

参考:Discoverable Endpoints - Authelia