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.jsonNext.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-pnpm2. Auth.js のインストールと設定
Section titled “2. Auth.js のインストールと設定”次に、Auth.js (next-auth) をインストールし、シークレットキーを生成します。
pnpm add next-auth@betapnpm dlx auth secretAUTH_SECRET として生成された値は .env.local ファイルに設定されます。
3. API ルートの作成
Section titled “3. API ルートの作成”公式ドキュメント に従い、Auth.js の API ルートを作成します。
import { handlers } from "@/auth"export const { GET, POST } = handlers4. 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_PASSWORD1. 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_PASSWORDauthelia 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 /keysECDSA証明書:
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 upNext.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/oauth2c2. 認証・トークン取得の実行
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,groups3. レスポンスの確認
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,groupsUserInfo エンドポイントの実行
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 openidIntrospection エンドポイントの実行
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"}