9
4

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とOry Kratosを組み合わせて、ユーザーの新規登録・ログイン・ログアウト機能をハンズオン形式で実装していきます。

Kratosは、ユーザーの新規登録やログイン、パスワードリセットなどの認証機能を提供するOSSです。Ory Kratos / Hydra / Keto等のモジュールから構成される、オープンソースの認証・認可プラットフォーム「Ory」の1つで、ID管理(Identity Management)を担当します。

Oryについて詳しくは「Ory(オリー)とは?ChatGPTの8億ユーザーを支えるOSSの認証基盤」をご覧ください。

Kratosには、Ory Network(SaaS版)を使う方法と、自分でサーバーを立てるセルフホスト構成の2つの利用形態があります。

Kratosを選ぶポイントは、Auth0やFirebase Authentication、Cognitoなどの認証サービスと比べて、UIのカスタマイズに制限がない点です。ヘッドレス設計で認証機能に関するAPIを提供するため、Next.jsと組み合わせることでUI/UXを自由に構築できます。

本記事では、Dockerを用いたセルフホスト構成でKratosを構築し、Next.jsと連携させます。本記事の通りに実装すると、新規登録やログインなどの認証機能と、ログイン後にユーザーのメールアドレスを表示する画面が完成します。

完成形: ログイン後のトップページ

対象読者

  • Ory、Ory Kratosに興味がある方
  • Kratosで認証機能を実装してみたい方
  • Next.js(App Router)の基礎知識がある方

前提知識

  • Dockerの基本操作(docker compose up など)
  • Next.jsの基礎(App Router、ミドルウェア)

この記事のポイント

Oryの公式ドキュメントにはクイックスタートが用意されていますが、Kratosが提供するサンプルUIをそのまま動かす形式になっています。本記事では、自分のNext.jsアプリにOry Kratosを組み込むという視点で、以下のポイントを押さえながら進めます。

  • Ory Elements(Oryが提供する認証UIコンポーネント)を使った認証UI実装
  • Next.jsのミドルウェアによるセッション管理
  • 各ステップで動作確認しながら進められるハンズオン形式

この記事で学べること

  • Kratosをローカル環境で起動する方法
  • Ory Elementsで認証UIを作る方法
  • Next.jsとKratosを連携し、ユーザーの新規登録・ログイン・ログアウトを実装する方法

前提条件

以下のツールがインストールされていることを確認してください。

  • Docker
  • Node.js(18.18以上)
  • Git

1. Kratosをローカルで起動

まず、Kratosをローカル環境で起動します。

1-1. リポジトリのクローン

git clone https://github.com/ory/kratos.git
cd kratos

1-2. Kratos設定の編集

quickstartのデフォルト設定では、認証画面のURLがKratos付属のサンプルUI(4455ポート)を向いています。これをNext.jsアプリ(3000ポート)に変更します。

contrib/quickstart/kratos/email-password/kratos.ymlをエディタで開き、以下の箇所を編集してください。

contrib/quickstart/kratos/email-password/kratos.yml
 selfservice:
-  default_browser_return_url: http://127.0.0.1:4455/welcome
+  default_browser_return_url: http://127.0.0.1:3000/

   flows:
     verification:
       enabled: true
-      ui_url: http://127.0.0.1:4455/verification
+      ui_url: http://127.0.0.1:3000/auth/verification
       after:
-        default_browser_return_url: http://127.0.0.1:4455/welcome
+        default_browser_return_url: http://127.0.0.1:3000/

     logout:
       after:
-        default_browser_return_url: http://127.0.0.1:4455/login
+        default_browser_return_url: http://127.0.0.1:3000/

     login:
-      ui_url: http://127.0.0.1:4455/login
+      ui_url: http://127.0.0.1:3000/auth/login

     registration:
-      ui_url: http://127.0.0.1:4455/registration
+      ui_url: http://127.0.0.1:3000/auth/registration

この設定により、Kratosが認証フローを開始するとNext.jsアプリの認証画面へリダイレクトされるようになります。

1-3. Docker Composeで起動

docker compose -f quickstart.yml -f quickstart-standalone.yml up

初回起動時はイメージのダウンロードに時間がかかります。以下のような成功メッセージを含むログが出力されれば起動完了です。

kratos-kratos-migrate-1  | ------------ SUCCESS ------------
kratos-kratos-migrate-1  | Successfully applied migrations!
# 略
kratos-kratos-migrate-1 exited with code 0
kratos-kratos-1  | ... msg=Starting the admin httpd on: 0.0.0.0:4434
kratos-kratos-1  | ... msg=Starting the public httpd on: 0.0.0.0:4433

1-4. 起動確認

ブラウザで http://127.0.0.1:4433/health/alive にアクセスし、{"status":"ok"} が返ってくれば正常です。

参考: 各サービスの役割

URL サービス 説明
http://127.0.0.1:4433 Kratos Public API ブラウザからの認証リクエストを処理
http://127.0.0.1:4434 Kratos Admin API 管理者向けAPI(ユーザー一覧取得や削除など特権操作に使用)
http://127.0.0.1:4436 MailSlurper 開発用メールサーバー(確認メールの確認に使用)

2. Next.jsプロジェクトの作成

新しいNext.jsプロジェクトを作成します。

2-1. プロジェクト作成

npx create-next-app@latest my-ory-app --typescript --app --tailwind --eslint
cd my-ory-app

コマンド実行時に表示される質問には以下のように回答してください。

質問 回答
Would you like to use React Compiler? No
Would you like your code inside a src/ directory? No
Would you like to customize the import alias (@/* by default)? No

2-2. Oryパッケージのインストール

本記事は以下のバージョンで動作確認しています。

  • @ory/elements-react: 1.1.0
  • @ory/nextjs: 1.0.0-rc.1
npm install @ory/elements-react @ory/nextjs

@ory/nextjsは執筆時点でRC(Release Candidate)版です。正式リリース後にAPI変更の可能性があります。

2-3. Ory Elementsのスタイル追加

app/globals.cssの先頭にOry Elementsのスタイルをインポートします。

Ory Elementsのコンポーネント(ログインフォームなど)が正しく表示されるために必要です。Tailwind CSSの@import@tailwindよりも前に記述してください。

app/globals.css
@import "@ory/elements-react/theme/styles.css";

3. 設定ファイルの作成

3-1. 環境変数ファイルの作成

プロジェクトルートに.env.localを作成します。

ミドルウェアがKratosのURLを知るために必要です。

.env.local
NEXT_PUBLIC_ORY_SDK_URL=http://127.0.0.1:4433

3-2. Ory設定ファイルの作成

プロジェクトルートにory.config.tsを作成します。

ory.config.tsはNext.jsアプリ側のOry Elementsの設定(UIの言語、リンクの表示/非表示、リンク生成)を制御します。一方、kratos.ymlはKratosサーバーの動作設定(認証フロー開始時のリダイレクト先や認証方式など)を制御します。

ory.config.ts
import type { OryClientConfiguration } from "@ory/elements-react"

const config: OryClientConfiguration = {
  intl: {
    locale: "en",
  },
  project: {
    name: "My Ory App",
    default_redirect_url: "/",
    registration_enabled: true,
    verification_enabled: true,
    // 本ハンズオンでは新規登録・ログインなどの基本機能に絞るためrecovery_enabledをfalseに設定
    recovery_enabled: false,
    login_ui_url: "/auth/login",
    registration_ui_url: "/auth/registration",
    recovery_ui_url: "/auth/recovery",
    verification_ui_url: "/auth/verification",
    settings_ui_url: "/settings",
    error_ui_url: "/error",
  },
}

export default config
設定値 説明
intl.locale UIの言語設定。対応言語: en, de, es, fr, nl, pl, pt, sv, no
name アプリケーション名。Ory Elementsの画面タイトルに表示
default_redirect_url 認証完了後のリダイレクト先
registration_enabled falseでログイン画面の「新規登録」リンクが非表示
recovery_enabled falseでログイン画面の「パスワードを忘れた」リンクが非表示
login_ui_url ログイン画面のパス。Ory Elementsがリンク生成時に使用
registration_ui_url 登録画面のパス。Ory Elementsがリンク生成時に使用
recovery_ui_url パスワードリカバリー画面のパス。Ory Elementsがリンク生成時に使用
verification_ui_url メール確認画面のパス。Ory Elementsがリンク生成時に使用
settings_ui_url ユーザー設定画面のパス。Ory Elementsがリンク生成時に使用
error_ui_url エラー画面のパス。認証エラー発生時のリダイレクト先

3-3. ミドルウェアの作成

プロジェクトルートにmiddleware.tsを作成します。ミドルウェアを作成する理由は、ブラウザのCookie制限を回避するためです。

Kratosはセッション管理にCookieを使用しますが、ブラウザはセキュリティ上、異なるドメイン間でのCookie送受信を制限します。例えば、Next.jsアプリがlocalhost:3000、Kratosがlocalhost:4433で動作しているとします。この場合、ブラウザから直接Kratosにリクエストを送るとCookieが正しく機能しません。

ミドルウェアを挟むことで、ブラウザの通信先はlocalhost:3000だけになり、Cookieが正常に動作します。Kratosへのリクエストはミドルウェアがサーバーサイドで転送します。

middleware.ts
import { createOryMiddleware } from "@ory/nextjs/middleware"
import oryConfig from "@/ory.config"

export const middleware = createOryMiddleware(oryConfig)

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

4. 認証画面の実装

ディレクトリ構成

以下のディレクトリ構成で認証画面を作成します。

app/
├── auth/
│   ├── layout.tsx
│   ├── login/
│   │   └── page.tsx
│   ├── logout/
│   │   └── page.tsx
│   ├── registration/
│   │   └── page.tsx
│   └── verification/
│       └── page.tsx
├── layout.tsx
└── page.tsx

4-1. 認証画面のレイアウト作成

app/auth/layout.tsxを作成します。

Next.jsのApp Routerでは、layout.tsxは同じディレクトリ以下のすべてのページに自動的に適用されます。このレイアウトにより、ログイン・登録・メール確認などの認証画面が中央に配置されます。

mkdir -p app/auth
app/auth/layout.tsx
export default function AuthLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="min-h-screen flex items-center justify-center">
      {children}
    </div>
  )
}

4-2. ログイン画面の作成

app/auth/login/page.tsxを作成します。

Kratosはフローベースの認証を採用しており、認証処理を開始するとフローIDが発行されます。このフローIDに紐づいてCSRFトークンやフォームの状態が管理されます。ログイン画面では、このフロー情報を取得してOry Elementsのコンポーネントに渡します。

  1. getLoginFlowでKratosからログインフローの情報を取得
  2. 取得したフロー情報をLoginコンポーネントに渡してUIを表示
mkdir -p app/auth/login
app/auth/login/page.tsx
import { Login } from "@ory/elements-react/theme"
import { getLoginFlow, OryPageParams } from "@ory/nextjs/app"
import config from "@/ory.config"

export default async function LoginPage(props: OryPageParams) {
  const flow = await getLoginFlow(config, props.searchParams)

  if (!flow) {
    return null
  }

  return <Login flow={flow} config={config} />
}

flowが取得できない場合(画面が真っ白になる場合)は、以下を確認してください。

  • Kratosが起動しているか(docker composeが実行中か)
  • .env.localNEXT_PUBLIC_ORY_SDK_URLhttp://127.0.0.1:4433になっているか
  • ブラウザのURLがlocalhostではなく127.0.0.1になっているか

4-3. 登録画面の作成

app/auth/registration/page.tsxを作成します。

ログイン画面と同様の構成です。

  1. getRegistrationFlowでKratosから登録フローの情報を取得
  2. 取得したフロー情報をRegistrationコンポーネントに渡してUIを表示
mkdir -p app/auth/registration
app/auth/registration/page.tsx
import { Registration } from "@ory/elements-react/theme"
import { getRegistrationFlow, OryPageParams } from "@ory/nextjs/app"
import config from "@/ory.config"

export default async function RegistrationPage(props: OryPageParams) {
  const flow = await getRegistrationFlow(config, props.searchParams)

  if (!flow) {
    return null
  }

  return <Registration flow={flow} config={config} />
}

4-4. メール確認画面の作成

app/auth/verification/page.tsxを作成します。

新規登録後、ユーザーの入力したメールアドレスが本人のものか確認するための画面です。確認メールに記載されたコードを入力することで、メールアドレスの所有権を検証します。

  1. getVerificationFlowでKratosから確認フローの情報を取得
  2. 取得したフロー情報をVerificationコンポーネントに渡してUIを表示
mkdir -p app/auth/verification
app/auth/verification/page.tsx
import { Verification } from "@ory/elements-react/theme"
import { getVerificationFlow, OryPageParams } from "@ory/nextjs/app"
import config from "@/ory.config"

export default async function VerificationPage(props: OryPageParams) {
  const flow = await getVerificationFlow(config, props.searchParams)

  if (!flow) {
    return null
  }

  return <Verification flow={flow} config={config} />
}

4-5. ログアウト画面の作成

app/auth/logout/page.tsxを作成します。

ログアウトAPIはブラウザのセッションCookieを使ってログアウト対象を特定するため、クライアントサイドから呼び出します。CSRF対策のため、ログアウトにはトークンが必要です。以下の手順でログアウトを行います。

  1. KratosのログアウトAPI(/self-service/logout/browser)にリクエストすると、Kratosがログアウトトークン付きのURL(Kratosのエンドポイント)を返す
  2. そのURLにリダイレクトすると、Kratosがトークンを検証してセッションを破棄し、トップページにリダイレクトされる
mkdir -p app/auth/logout
app/auth/logout/page.tsx
"use client"
import { useEffect } from "react"

export default function LogoutPage() {
  useEffect(() => {
    fetch("/self-service/logout/browser")
      .then((res) => res.json())
      .then((data) => {
        window.location.href = data.logout_url
      })
  }, [])

  return <p>ログアウト中...</p>
}

4-6. トップページの更新

app/page.tsxを更新します。

getServerSessionでサーバーサイドからセッション情報を取得し、ログイン状態に応じて表示を切り替えます。セッションが存在すればユーザー情報とログアウトボタンを、存在しなければログイン・新規登録へのリンクを表示します。

app/page.tsx
import Link from "next/link"
import { getServerSession } from "@ory/nextjs/app"

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

  return (
    <main className="flex min-h-screen flex-col items-center justify-center p-24">
      <h1 className="text-4xl font-bold mb-8">My Ory App</h1>
      {session ? (
        <div className="text-center">
          <p className="mb-4">ログイン中: {session.identity?.traits?.email}</p>
          <a
            href="/auth/logout"
            className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
          >
            ログアウト
          </a>
        </div>
      ) : (
        <div className="flex gap-4">
          <Link
            href="/auth/login"
            className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
          >
            ログイン
          </Link>
          <Link
            href="/auth/registration"
            className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
          >
            新規登録
          </Link>
        </div>
      )}
    </main>
  )
}

5. 動作確認

5-1. 開発サーバーの起動

npm run dev

5-2. ユーザー新規登録フロー

  1. http://127.0.0.1:3000 にアクセスして「新規登録」をクリック
    トップページ.png
  2. メールアドレス、姓、名を入力して「Sign up」をクリック
    新規登録画面.png
  3. パスワードを入力して「Sign up」をクリック
    パスワード画面.png
  4. http://127.0.0.1:4436 でMailSlurperを開き、確認コードを取得
    コード受信画面.png
  5. 確認コードを入力して「Continue」をクリック
    コード入力画面.png
  6. 確認コード検証後、「Continue」をクリックしてトップページに戻り、ログイン状態が表示される
    ログイン後のトップページ.png

5-3. ログインフロー

  1. すでにログイン中の場合は、トップページの「ログアウト」ボタンをクリックしてログアウト
  2. http://127.0.0.1:3000/auth/login にアクセスして、メールアドレスとパスワードを入力し「Sign in with password」をクリック
    ログイン画面.png
  3. ログインが完了し、トップページに戻り、ログイン状態が表示される

5-4. ログアウト

トップページの「ログアウト」ボタンをクリックするとログアウトできます。

参考: ユーザーの削除

繰り返し動作確認する場合、以下のコマンドで登録したユーザーを削除できます。

# ユーザーIDを確認
curl http://127.0.0.1:4434/admin/identities | jq -r '.[].id'

# ユーザーを削除(IDを指定)
curl -X DELETE http://127.0.0.1:4434/admin/identities/{id}

# 全ユーザーを削除
curl http://127.0.0.1:4434/admin/identities | jq -r '.[].id' | xargs -I {} curl -X DELETE http://127.0.0.1:4434/admin/identities/{}

Admin API(ポート4434)はユーザーの削除など特権操作が可能なため、本番環境では外部に公開しないでください。内部ネットワークからのみアクセス可能にするか、適切な認証・認可を設定してください。

実装してみて

実際に手を動かしてみて分かったことを共有します。

Ory Elementsは便利だが日本語は非対応

@ory/elements-reactを使うと、ログイン・登録画面のUIを自前で作る必要がありません。フォームのバリデーションやエラー表示に加え、認証フローの状態管理も行ってくれます。

ただし、日本語には対応していません。単純にory.config.tsintl.locale: "ja"と設定しても、タイトルやボタンなど各要素で以下のようなエラーが発生します。

Error: [@formatjs/intl Error MISSING_TRANSLATION] Missing message: "registration.title" for locale "ja", using id as fallback.

Ory Network(SaaS版)では82言語に対応していますが、OSSの@ory/elements-reactのnpmパッケージにバンドルされているのは英語やドイツ語など9言語のみです。日本語化するにはカスタム翻訳の追加が必要です。

日本語化の方法は2つあります。

  1. intl.customTranslationsでカスタム翻訳を追加する
  2. Ory Elementsを使わず、Kratos APIを直接呼び出して独自UIを構築する

パスワードポリシーはKratosが管理

パスワードポリシーはKratosが管理しています。デフォルト設定は以下の通りです。
設定を変更したい場合は、kratos.ymlselfservice.methods.password.config配下で設定します。

設定項目 デフォルト値 説明
最小文字数 8文字 min_password_lengthで変更可能(最小6)
漏洩パスワードチェック 有効 Have I Been Pwned APIで検知
識別子との類似性チェック 有効 メールアドレスに似たパスワードを拒否

漏洩パスワードを使おうとすると、以下のエラーが表示されます。

The password has been found in data breaches and must no longer be used.

企業では「英大文字、英小文字、数字、記号を組み合わせる」といったルールがよく求められます。しかしKratosはNISTガイドライン(SP 800-63B)に準拠しており、これらは意図的に実装されていません。上記3つ以外のチェックが必要な場合は、独自で実装する必要があります。

データの永続化について

quickstartではdsn: memoryが設定されており、インメモリで動作します。Dockerを停止するとデータは消えます。

本番環境では永続化が必要です。PostgreSQL、MySQL、CockroachDBに対応しています。詳細は公式ドキュメントを参照してください。

メールアドレスの確認は登録後

メールアドレスの所有権確認(コードによる検証)は、新規登録の後に行われます。Kratosの標準機能では、メールアドレスを確認してからユーザーを登録する流れには対応していません。

登録前にメールアドレスを確認したい場合は、独自でコード送信・検証機能を開発し、検証結果をKratosのWebhookで確認する必要があります。

kratos.ymlとNext.jsアプリのURL不一致に注意

Kratos側の設定(kratos.yml)とNext.jsアプリのリダイレクトURLが一致していないと、認証後、意図しないページへ遷移してしまうことがありました。設定を変更する際は両方のURLを確認するようにしてください。

localhostと127.0.0.1の混在に注意

ブラウザによっては localhost と 127.0.0.1 でCookieの送受信挙動が異なります。Kratosのデフォルト設定では 127.0.0.1 を前提としているため、必ず 127.0.0.1 でアクセスしてください。

まとめ

Ory Kratosを使うことで、ユーザーの新規登録・ログイン・ログアウトといった認証機能を簡単に実装できました。

今回実装したのは基本的な機能です。これをベースにOry Hydraと連携させてOAuth2/OIDCに対応させたり、ソーシャルログイン(Google、GitHubなど)を追加できます。

一方で、企業で求められるUIのカスタマイズ(日本語化を含む)やパスワードポリシーの独自要件には、独自の実装が必要になる場面があります。Kratosはヘッドレス設計のため柔軟にカスタマイズできますが、「実装してみて」セクションで紹介したように、要件に応じた工夫が求められる点は押さえておきましょう。

少し触ってみるだけでも分かることが多いので、まずはOSSとして無料で試してみることをおすすめします。

Oryに興味を持った方へ

スタジオメッシュは、Oryの公式代理店として技術サポート・導入パッケージを提供しています。日本語での技術相談や導入支援をご希望の方は、お気軽にお問い合わせください。

お問い合わせはこちらから

参考リンク

9
4
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
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?