0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Next.js】Auth.js + Keycloakで認証を実装してみる

0
Last updated at Posted at 2026-02-01

Next.jsで認証を実装したことがなかったのでいちばん有名そうな Auth.js を使って実装してみる。

※ 実装してから気づいたのですが、Better Authが今後のスタンダードになるそう、、、orz

実装したソースコード

1. パッケージのインストール

pnpm add next-auth@beta

2. 環境変数の設定

issureを調べる

REALM_NAME=AppCommon
curl -sSkL "https://keycloak.prd.baseport.net/realms/$REALM_NAME/.well-known/openid-configuration" | jq -r ".issuer"
# https://keycloak.prd.baseport.net/realms/AppCommon

Auth.jsに必要な環境変数を設定します。

.env
AUTH_SECRET=your-generated-secret-key # openssl rand -base64 32 で生成
AUTH_KEYCLOAK_ID=your-client-id
AUTH_KEYCLOAK_SECRET=your-client-secret
AUTH_KEYCLOAK_ISSUER=https://keycloak.prd.baseport.net/realms/AppCommon
AUTH_TRUST_HOST=true
  • AUTH_TRUST_HOST
    リバースプロキシ経由でアプリケーションを公開する場合、AUTH_TRUST_HOST=true に設定するとAuth.js はリバースプロキシから送信される X-Forwarded-Host ヘッダーを信頼するようになります。

3. Auth.js設定ファイルの作成

プロジェクトのルートにauth.tsを作成してAuth.jsの設定を作成します。

KeycloakをIDプロバイダとする場合の設定はこちらのドキュメントにのっていました。

src/auth.ts
import NextAuth from "next-auth"
import type { NextAuthConfig } from "next-auth"
import Keycloak from "next-auth/providers/keycloak"
 
// NextAuthConfig: https://authjs.dev/reference/nextjs#nextauthconfig
const config = {
  // https://authjs.dev/reference/nextjs#pages
  pages: {
    signIn: "/login",
  },
  providers: [
    // Keycloakプロバイダーの設定
    // https://authjs.dev/getting-started/providers/keycloak#configuration
    Keycloak({
      clientId: process.env.AUTH_KEYCLOAK_ID!,
      clientSecret: process.env.AUTH_KEYCLOAK_SECRET!,
      issuer: process.env.AUTH_KEYCLOAK_ISSUER!,
    }),
  ]
} satisfies NextAuthConfig

// 認証関数(`auth`, `signIn`, `signOut`)とAPIハンドラー(`handlers`)をエクスポート
// NextAuthResult: https://authjs.dev/reference/nextjs#nextauthresult
export const { handlers, signIn, signOut, auth } = NextAuth(config)

4. APIルートハンドラーの作成

Auth.jsが必要とする認証APIエンドポイントを提供するためのルートハンドラを作成します。

以下のエンドポイントが自動的に提供されます

  • /api/auth/signin/keycloak - ログイン開始
  • /api/auth/callback/keycloak - Keycloakからのコールバック(必須
  • /api/auth/signout - ログアウト
  • /api/auth/session - セッション情報取得
  • /api/auth/csrf - CSRFトークン取得

※ Keycloakとの通信やセッション管理はすべてこのエンドポイントを経由するため、このファイルがないと認証が動作しません

src/app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"

export const { GET, POST } = handlers

5. プロキシ(ミドルウェア)の作成

ページごとに認証チェックを実装するのでは非効率ですし、抜け漏れも発生しやすいのでミドルウェアを作成して認証の状態を一括チェックします。

役割:

  • すべてのページアクセス前に認証状態をチェック
  • 未認証ユーザーを/loginにリダイレクト
  • 認証済みユーザーがログインページにアクセスした場合、/dashboardにリダイレクト
src/proxy.ts
import { auth } from "@/auth"
import { NextResponse } from "next/server"

// ミドルウェアで認証を強制する
export default auth((req) => {
  // ユーザーがログインしているかどうか
  const isLoggedIn = !!req.auth

  const { pathname } = req.nextUrl

  // 公開ページリスト
  const publicPages = ["/", "/login"]
  const isPublicPage = publicPages.includes(pathname)

  // 未ログインで、公開ページ以外にアクセスしようとした場合
  if (!isLoggedIn && !isPublicPage) {
    return NextResponse.redirect(new URL("/login", req.url))
  }

  // ログイン済みで、ログインページにアクセスしようとした場合
  if (isLoggedIn && pathname === "/login") {
    return NextResponse.redirect(new URL("/dashboard", req.url))
  }

  // それ以外はそのまま通す
  return NextResponse.next()
})

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

コード解説:

  • req.auth: ミドルウェア内で使える、既に取得済みのセッション情報
  • new URL("/login", req.url): 現在のドメイン情報を使って絶対URLを生成(環境に依存しない)
  • matcher: ミドルウェアを実行するパスを指定(API、静的ファイル、画像は除外)

6. / ページの作成

/ ページではアクセス時にログイン状態に応じてリダイレクトを行います

  • ログイン済み: /dashboard にリダイレクト
  • 未ログイン: /login にリダイレクト
src/app/page.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"

export default async function Home() {
  const session = await auth()

  if (session) {
    redirect("/dashboard")
  } else {
    redirect("/login")
  }
}

7. 認証が不要なページの作成 (/login)

/login ページはミドルウェアで保護されていないページなので、未ログイン状態でアクセス可能です。
このページではログインボタンを表示します。

src/app/login/page.tsx
import { signIn } from "@/auth"

export default function LoginPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="w-full max-w-md space-y-4 p-8">
        <h1 className="text-2xl font-bold text-center">ログイン</h1>
        
        <form
          action={async () => {
            "use server"
            await signIn("keycloak", { redirectTo: "/dashboard" })
          }}
        >
          <button
            type="submit"
            className="w-full bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600"
          >
            Keycloakでログイン
          </button>
        </form>
      </div>
    </div>
  )
}

ポイント:

  • "use server": Server Actionとして実行
  • signIn("keycloak", { redirectTo: "/dashboard" }): Keycloakログイン後に/dashboardへリダイレクト

8. 認証が必要なページの作成 (/dashboard)

/dashboard はプロキシ(ミドルウェア)によって保護されたページで、ログイン済みの状態でしかアクセスできません。
認証オブジェクト( auth() )からユーザー名やメールアドレスを取得して表示してみます。

src/app/dashboard/page.tsx
import { auth } from "@/auth"
import { SignOutButton } from "@/components/ui/SignOutButton"

export default async function DashboardPage() {
  const session = await auth()  // サーバーコンポーネント

  return (
    <div className="flex min-h-screen items-center justify-center">
      <div className="w-full max-w-md space-y-4 p-8">
        <h1 className="text-2xl font-bold text-center">ダッシュボード</h1>
        <p>ようこそ、{session?.user?.name}さん</p>
        <p>メール: {session?.user?.email}</p>
        <SignOutButton />
        </div>
    </div>

  )
}

ログアウトボタン

src/components/ui/SignOutButton.tsx
import { signOut } from "@/auth"

export function SignOutButton() {
  return (
    <form
      action={async () => {
        "use server"
        await signOut({ redirectTo: "/login" })
      }}
    >
      <button 
        type="submit"
        className="bg-red-500 text-white py-2 px-4 rounded hover:bg-red-600"
      >
        ログアウト
      </button>
    </form>
  )
}

9. 動作確認

pnpm dev

http://localhost:3000 にアクセス

http://localhost:3000/login

スクリーンショット 2026-02-01 16.20.31.png (13.7 kB)

http://localhost:3000/dashboard

スクリーンショット 2026-02-01 16.20.45.png (38.9 kB)

動作フロー

1. ユーザーが /dashboard にアクセス
   ↓
2. ミドルウェアが認証状態をチェック(req.auth)
   ↓
3. 未認証の場合 → /login にリダイレクト
   ↓
4. ログインボタンをクリック
   ↓
5. signIn("keycloak") が /api/auth/signin/keycloak を呼び出し
   ↓
6. Keycloakログインページへリダイレクト
   ↓
7. ユーザーがKeycloakでログイン
   ↓
8. /api/auth/callback/keycloak にコールバック
   ↓
9. セッション作成
   ↓
10. /dashboard にリダイレクト
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?