認証(Authentication) はアプリケーションを構築する上で重要な機能の一つでどのライブラリ/製品を利用して、どのように設定を行えばいいのか悩んでいる人も多いのではないでしょうか。Next.js の Authentication といえば Auth.js(旧 NextAuth.js)が有名ですが本文書で最近耳にする機会も増え、ユーザ認証とユーザ管理を簡単に行うことができるClerkの動作確認を行います。オープンソースの製品ではありませんが設定もシンプルで無料枠もあるので支払い情報の入力なしで利用することができます。利用できるフレームワークは React ベースのフレームワーク(React, Next.js, Remix, RedwoodJS, Gatsby, Expo)と JavaScript です。本文書では Next.js を利用して Clerk の基本的な機能の動作確認を行っています。

Next.js13用に公開しましたがClerkのバージョンアップにより設定方法に変更があったためNext.j14に対応した内容にリライトしました。

Clerk アカウントの作成

Cleak のアカウントの作成を行うためCleark のサイトにアクセスします。

Clerkトップ画面
Clerkトップ画面

画面右上の”Sign up”のリンクをクリックします。アカウントの作成画面が表示されるので Github, Google またはメールアドレスを利用してアカウントを作成することができます。

サインアップ画面
サインアップ画面

本文書では Google アカウントを利用してアカウントの作成を行うので”Continue with Google”ボタンをクリックするとアカウントの選択画面が表示されるので利用する Google アカウントを選択してください。

Googleアカウントの選択
Googleアカウントの選択

アプリケーションの追加画面が表示されるので”Add application”をクリックしてください。

アプリケーション作成画面
アプリケーション作成画面

Application name には任意の名前のアプリケーション名を入力してユーザのサインアップの方法を選択してください。ここではデフォルトに設定されている”Email address”と Google を選択して”CREATE APPLICATION”ボタンをクリックします。

アプリケーションの作成
アプリケーションの作成

プロジェクトの作成が完了してダッシュボード画面が表示されます。利用するフレームワークによってインストールするライブラリ/設定が異なるのでその手順がフレームワーク毎に表示されます。これから作成するプロジェクトで利用する API Keys の情報が表示されます。このキーを利用して設定を行います。

DashBoard画面
DashBoard画面

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

動作確認を行う Next.js プロジェクトの作成を行います。プロジェクト名は任意の名前をつけることができますがここでは next-js-clerk という名前にしています。TypeScript, ESLint など利用するかどうか選択することができますが残りの選択肢はすべてデフォルトを選択しています。srcはデフォルトの”No”を選択しています。


 % npx create-next-app@latest
Need to install the following packages:
create-next-app@14.2.4
Ok to proceed? (y) y
✔ What is your project named? … next-js-clerk
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes

Using npm.

Initializing project with template: app-tw


Installing dependencies:
- react
- react-dom
- next

Installing devDependencies:
- typescript
- @types/node
- @types/react
- @types/react-dom
- postcss
- tailwindcss
- eslint
- eslint-config-next
//略

Clerk の初期設定

プロジェクトの作成が完了したらプロジェクトディレクトリに移動して Clerk’s Next.js SDK のインストールを行います。


 % cd next-js-clerk
 % npm install @clerk/nextjs

インストール後の package.json ファイルを確認して動作確認したパッケージのバージョンを確認しておきます。


{
  "name": "next-js-clerk",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@clerk/nextjs": "^5.2.2",
    "next": "14.2.4",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "eslint": "^8",
    "eslint-config-next": "14.2.4",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

環境変数を保存する.env.localファイルをプロジェクトディレクトリ直下に作成して Clerk の管理画面に表示されている API Keys のコピー&ペーストします。


NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_YWJvdmUtY2xhbS05Ny5jbGVyay5aY2NvdW50cy5kZXYk
CLERK_SECRET_KEY=sk_test_yE7p3tglzokoPEYU86B1NrN031za3w544yE0VwJzUQ

上記の値はそのままコピーをしても利用することができないので各自がClerkのアカウントを作成して取得した API Keys を設定してください。

Clerkが管理する認証情報をアプリケーション上で利用できるように app ディレクトリの layout.tsx ファイルに<ClerkProvider>の設定を行う必要があります。


import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import { ClerkProvider } from '@clerk/nextjs';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={inter.className}>{children}</body>
      </html>
    </ClerkProvider>
  );
}

Protect Route

どの URL にアクセスした場合に認証を行うかを決めるために Next.js の Middleware の機能を利用します。プロジェクトディレクトリ直下にmiddleware.ts ファイルを作成して以下のコードを記述します。下記の設定では/api を含めすべてのルート(URL)に対して設定を行っています。


import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware();

export const config = {
  matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};

設定が完了したら”npm run dev”コマンドを実行して開発サーバを起動します。


 %  npm run dev

> next-js-clerk@0.1.0 dev
> next dev

  ▲ Next.js 14.2.4
  - Local:        http://localhost:3000
  - Environments: .env.local
✓ Starting...
 ✓ Ready in 5.9s
 ○ Compiling /middleware ...
 ✓ Compiled /middleware in 509ms (179 modules)
 ○ Compiling / ...
 ✓ Compiled / in 2.9s (818 modules)
 GET / 200 in 3393ms
 ✓ Compiled in 432ms (323 modules)
 ○ Compiling /favicon.ico ...
 ✓ Compiled /favicon.ico in 766ms (515 modules)

ブラウザから http://localhost:3000 にアクセスを行います。トップページが表示されます。

Next.jsトップページ
Next.jsトップページ

デフォルトで設定したmiddlewareではどのルーとに対してもアクセスの制限を行っていないためどのページにもアクセスすることができます。

localhost:3000にアクセスする場合でもユーザ認証が完了していなければページが表示されないようにmiddleware.tsファイルを更新します。createRouteMatcher関数で引数に配列で”/”を設定しています。


import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher(['/']);

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect();
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

再度localhost:3000にアクセスするとリダイレクトが行われ、下記のサインイン画面が表示されます。URLを見ると外部のサーバ上のページが開いていることがわかります。

サインイン画面の表示
サインイン画面の表示

サインイン、サインアウトページの設定

先ほどアプリケーションにアクセスすると外部のサイトにリダイレクトされサインイン画面が表示されることを確認しました。Clerk では事前作成されたサインインコンポーネントが準備されているのでそのコンポーネントを利用することでローカル上でサインイン画面が表示されます。

app ディレクトリの下に sign-in/[[…sign-in]]ディレクトリを作成しその下に page.tsx を作成して以下のコードを記述します。


import { SignIn } from "@clerk/nextjs";

export default function Page() {
  return <SignIn />;
}

app ディレクトリの下に sign-up/[[…sign-up]]ディレクトリを作成しその下に page.tsx を作成して以下のコードを記述します。


import { SignUp } from "@clerk/nextjs";

export default function Page() {
  return <SignUp />;
}

.env.localファイルに以下の環境変数を追加します。


NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up

設定後に http://localhost:3000 にアクセスすると外部のサイトにリダイレクトされずローカルサイトのサインイン画面が表示されます。

ローカルでのサインイン画面
ローカルでのサインイン画面

ユーザのサインイン

Google アカウントまたはメールアドレスを利用してサインインを行ってみましょう。ここでは Google アカウントを利用するため表示されたサインイン画面で”Continue with Google”ボタンをクリックします。

サインインが完了するとトップページが表示されます。

Next.jsトップページ
Next.jsトップページ

サインインが完了したら Clerk のダッシュボードで Users ページにアクセスします。Dashboardにもユーザが登録されたことを祝うメッセージが表示されます。Total users, Active usersの値が1になっていることがわかります。

ユーザ登録直後のDashboard画面
ユーザ登録直後のDashboard画面

サイドメニューから”Users”をクリックするとサインインしたユーザが登録されていることが確認できます。サインインしたユーザは Clerk のダッシュボードで管理することができます。

Dashboardのユーザ一覧
Dashboardのユーザ一覧
Clerkのダッシュボードでユーザ一覧を表示
Clerkのダッシュボードでユーザ一覧を表示

ユーザのサインアウト

サインインを行いましたが現在の設定ではサインアウトを行う方法がありません。サインアウトを行うためのボタンもコンポーネントとして準備されているので app/page.tsx ファイルで SignOutButton コンポーネントを import します。


import { SignOutButton } from '@clerk/nextjs';

export default function Home() {
  return (
    <div>
      <SignOutButton />
    </div>
  );
}

スタイルが何も設定されていない Sign Out の文字列が表示されます。button 要素なのでクリックすることができます。

SignOutButtonの設定
SignOutButtonの設定

クリックするとサインアウトすることができます。サインアウトするとすぐにサインイン画面が表示されます。

表示されるボタンは SignOutButton コンポーネントの内側に button 要素を挿入しスタイルを適用することでカスタマイズすることができます。


import { SignOutButton } from '@clerk/nextjs';

export default function Home() {
  return (
    <div className="m-4">
      <SignOutButton>
        <button className="px-4 py-2 rounded-lg bg-blue-500 text-white text-sm">
          サインアウト
        </button>
      </SignOutButton>
    </div>
  );
}
button要素のカスタマイズ
button要素のカスタマイズ

UserButton の設定

SignOutButton コンポーネントを利用してサインアウトを行うことができましたが UserButton コンポーネントを利用してサインアウトを行うことができます。UserButton コンポーネントはサインアウトだけではなくアカウントの管理も行うことができます。


import { UserButton } from '@clerk/nextjs';

export default function Home() {
  return (
    <div>
      <UserButton afterSignOutUrl="/" />
    </div>
  );
}

UserButton コンポーネントを追加するとボタンが表示されるのでボタンをクリックするとドロップダウンメニューで”Manage account”と”Sign Out”のメニューが表示されます。

UserButtonコンポーネントの表示
UserButtonコンポーネントの表示

“Sign Out”をクリックするとサインアウトが行われサインイン画面が表示されます。

ローカルでのサインイン画面
ローカルでのサインイン画面

Manage account をクリックするとアカウント名や Email アドレス、接続してきたデバイスの情報などが表示されます。

複数ページ環境での動作確認

サインイン、サインアウトの方法が理解できたので Dashboard ページを追加して複数ページ環境で Clerk の動作確認を行なっていきます。

Dashboard ページの追加

サインイン後に表示されるダッシュボードページの作成を行います。app ディレクトリに dashboard ディレクトリを作成して page.tsx ファイルを作成します。


export default function DashboardPage() {
  return <h1>Dashboard</h1>;
}

Layout の設定

各ページに共有のヘッダーを設定できるようにプロジェクトディレクトリ直下に components コンポーネントを作成して NavBar.tsx ファイルを作成します。


export const NavBar = () => {
  return (
    <nav className="flex justify-between items-center h-12 mx-4">
      <div className="font-bold">Clerk Auth</div>
    </nav>
  );
};

作成した NavBar コンポーネントを layout.tsx ファイルで import します。


import type { Metadata } from 'next';
import { Inter } from 'next/font/google';
import './globals.css';
import { ClerkProvider } from '@clerk/nextjs';
import { NavBar } from '@/components/NavBar';

const inter = Inter({ subsets: ['latin'] });

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <ClerkProvider>
      <html lang="en">
        <body className={inter.className}>
          <NavBar />
          <main className="mx-4">{children}</main>
        </body>
      </html>
    </ClerkProvider>
  );
}

app/page.tsx ファイルの更新も行います。


export default function Home() {
  return <h1>Home</h1>;
}

middleware.ts ファイルではサインインが行われていなくても”/“にアクセスできるように createRouteMatcher関数の引数を更新します。/dashboardにアクセスを行うにはサインインが必要です。


import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher(['/dashboard(.*)']);

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect();
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

デフォルトで設定されている global.css ファイルのスタイルを削除し、Tailwind CSS の設定のみ残します。


@tailwind base;
@tailwind components;
@tailwind utilities;

ここまでの設定で http://localhost:3000 にアクセスすると Home 画面が表示されます。

トップページ
Homeページ

サインインボタンの設定

”/“ページにサインインボタンを追加してサインインが完了したら Dashboard ページにリダイレクトされるように SignInButton コンポーネントを利用します。forceRedirectUrl props を利用することでサインインが成功した場合にリダイレクトされる場所を指定しています。mode で modal を設定することでモーダルウィンドウとしてサインイン画面が表示されます。


import { SignInButton } from '@clerk/nextjs';

export const NavBar = () => {
  return (
    <nav className="flex justify-between items-center h-12 mx-4">
      <div className="font-bold">Clerk Auth</div>
      <SignInButton forceRedirectUrl="/dashboard" mode="modal" />
    </nav>
  );
};

文字列で”Sign in”ボタンが表示されます。

サインインボタンの表示
サインインボタンの表示

“Sign in”ボタンをクリックして表示されたサインイン画面からサインインを行うと/dashboard にリダイレクトされます。リダイレクトされると Dashboard 画面が表示されます。

Dashboard画面へのリダイレクト
Dashboard画面へのリダイレクト

SignedOut コンポーネントによる非表示設定

サインインが完了しているにも関わらず”Sign in”ボタンが表示されている状態なのでサインインが行われていない場合のみ”Sign in”ボタンが表示されるように SignedOut コンポーネントを利用します。SignInButton コンポーネントを SignedOut コンポーネントでラップします。

import { SignInButton, SignedOut } from '@clerk/nextjs';

export const NavBar = () => {
  return (
    <nav className="flex justify-between items-center h-12 mx-4">
      <div className="font-bold">Clerk Auth</div>
      <SignedOut>
        <SignInButton redirectUrl="/dashboard" mode="modal" />
      </SignedOut>
    </nav>
  );
};

“Sign in”ボタンが非表示となります。サインインが行われていない場合のみ”Sign in”ボタンが表示されるようになりました。

SignInボタンを非表示
SignInボタンを非表示

SignedIn コンポーネントによる表示設定

サインインが行われている場合はサインインボタンの代わりにサインアウトできるように UserButton コンポーネントを設定します。サインインした時にのみ表示されるように SignedIn コンポーネントでラップします。サインアウトした後に”/“にリダイレクトされるように afterSignOutUrl props を設定しています。


import { SignInButton, SignedIn, SignedOut, UserButton } from '@clerk/nextjs';

export const NavBar = () => {
  return (
    <nav className="flex justify-between items-center h-12 mx-4">
      <div className="font-bold">Clerk Auth</div>
      <SignedOut>
        <SignInButton redirectUrl="/dashboard" mode="modal" />
      </SignedOut>
      <SignedIn>
        <UserButton afterSignOutUrl="/" />
      </SignedIn>
    </nav>
  );
};

サインインが行われている場合には UserButton が表示されます。

UserButtonの表示設定
UserButtonの表示設定

SignedIn, SignedOut コンポーネントを利用することでサインインした状態、サインしていない状態での要素の表示、非表示の制御を行えることがわかります。

ユーザ情報の表示

App Router を利用している場合はヘルパー関数の auth を利用することで Authentication オブジェクトの情報を取得することができます。


import { auth } from '@clerk/nextjs/server';
export default function DashboardPage() {
  console.log(auth());

  return (
    <div>
      <h1>Dashboard</h1>
    </div>
  );
}

Next.js 14 ではデフォルトですべてのコンポーネントは Server Component としてサーバ側で処理が行われるので開発サーバを実行したターミナルには Authentication オブジェクトの情報が表示されます。ユーザがサインインしている状態か userId の値を利用して分岐処理などを行うことができます。


{
  actor: undefined,
  sessionClaims: {
    azp: 'http://localhost:3000',
    exp: 1666622607,
    iat: 1666622547,
    iss: 'https://clerk.quiet.muskox-85.lcl.dev',
    nbf: 1666622537,
    sid: 'sess_2GaMqUCB3Sc1WNAkWuNzsnYVVEy',
    sub: 'user_2F2u1wtUyUlxKgFkKqtJNtpJJWj'
  },
  sessionId: 'sess_2GaMqUCB3Sc1WNAkWuNzsnYVVEy',
  session: undefined,
  userId: 'user_2F2u1wtUyUlxKgFkKqtJNtpJJWj',
  user: undefined,
  orgId: undefined,
  orgRole: undefined,
  orgSlug: undefined,
  organization: undefined,
  getToken: [AsyncFunction (anonymous)],
  debug: [Function (anonymous)]
}

User オブジェクトの内容を表示したい場合にはヘルパー関数の currentUser を利用することができます。currentUser 関数を利用する場合は非同期関数の async, await を利用します。Google アカウントを利用している場合は Google で設定しているユーザ名やメールアドレスの情報も含まれます。User オブジェクトを利用することでブラウザ上にユーザ名を表示させることができます。


import { currentUser } from '@clerk/nextjs/server';
export default async function DashboardPage() {
  const user = await currentUser();
  console.log(user);

  return (
    <div>
      <h1 className="font-bold">Dashboard</h1>
      <div>Hello {`${user?.firstName} ${user!.lastName}`}!</div>
    </div>
  );
}
currentUser関数によるユーザ情報の取得
currentUser関数によるユーザ情報の取得

クライアントコンポーネントでのユーザ情報の取得

ヘルパー関数の auth, currentUser は Server Component で利用することができますが Client Component で利用したい場合には useUser Hook を利用することができます。

Client Component を作成するために components ディレクトリに UserInfo.tsx ファイルを作成して以下のコードを記述します。useUser Hook から戻される値には isSignedIn, isLoaded などがあります。isLoaded を利用しない場合にはページにアクセス直後には isSignedIn の値が false のため”Not signed in”が表示されその後 isSignedIn が true になると”Hello John”が表示されます。


'use client';
import { useUser } from '@clerk/nextjs';

export const UserInfo = () => {
  const { isSignedIn, user, isLoaded } = useUser();

  if (!isLoaded) {
    return null;
  }

  if (isSignedIn) {
    return <div>Hello {user.firstName}!</div>;
  }

  return <div>Not signed in</div>;
};
useUser Hookの利用
useUser Hookの利用

‘use client’を設定せず Server Component として useUser Hook を利用した場合にはエラーメッセージが表示されます。


Unhandled Runtime Error
Error: Attempted to call useUser() from the server but useUser is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.

Clerk の基本的な機能しか利用していませんが Clerk を利用することで簡単に Next.js に認証機能が追加できることが理解できたかと思います。