Next.jsとCorbadoを使用したパスキー実装

## 初投稿
はじめてのQiita投稿になります。
Notionにまとめていたのですが、Notionの投稿を検索にヒットさせるには課金が必要とわかり、こちらにも投稿しようと思った次第です。
至らない部分もあるかと思いますが温かい目で見ていただけると幸いです。
間違いなどありましたら、どしどし書いていただけると自分自身の勘違いに気づけるのでとても助かります。


## Corbadoとは

新規サービス・既存サービスにパスキー機能の簡単に実装することが出来るSaaSになります。

Corbado自体で数行のコード（デザインとか変更することは考えず）記載するだけで多くのパスキーに関するサポートをしてくれます。そのおかけで用意にパスキーの導入が可能になります。

https://www.corbado.com/

パスキーについて詳しく知りたい場合はこちら↓の一読をお願いします

https://qiita.com/roll1226/items/dc79b4bf1044167ea6d4

## 実際に実装していく

### 1. Next.jsの環境構築


> ⏩ 環境構築を知っている方はここは読み飛ばして問題ないです。
ただし、今回はNext.jsのバージョンを`13.5.3`で行っているため`13.5.3`以前のバージョンを使用する場合は読み替えての実装をお願いします。

親の顔より見た環境構築するためのコマンドを実行していく

```bash
npx create-next-app@latest
```

参考までに今回の環境は下記のような設定にしています

```bash
✔ What is your project named? … passkey-test
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … Yes
✔ Would you like to use App Router? (recommended) … Yes
✔ Would you like to customize the default import alias? … Yes
✔ What import alias would you like configured? … @/*
```

一先ず正常に構築出来た確認していきます。

```bash
cd passkey-test
```

作成したプロジェクトに移動（プロジェクト名を変更している方は読み替えてコードを実行してください）

```bash
npm run dev
```

上記のコマンドを実行・`localhost:3000`にアクセスをして下記の画面が表示されれば問題なく環境構築完了になります。

![screencapture-localhost-3000-2023-10-01-06_16_30.png](https://file.notion.so/f/f/36ab5533-6b86-4df6-aa9d-1dc141d81287/29805834-a0f3-4406-8ba1-158910015ca3/screencapture-localhost-3000-2023-10-01-06_16_30.png?id=e0ba0238-cd55-4856-9f83-7a0fd2660e38&table=block&spaceId=36ab5533-6b86-4df6-aa9d-1dc141d81287&expirationTimestamp=1696204800000&signature=6dnMFLHGFUmEr3rPh97qC7i_h4QIkx4PIpnLU2mrLNY&downloadName=screencapture-localhost-3000-2023-10-01-06_16_30.png)

## Corbadoのアカウント作成・APIキーの取得・プロジェクトの設定

アカウントを作成してAPIキーを取得していきます。

1. Corbadoのホームページにアクセスします。

https://www.corbado.com/
    
2. アクセスしたら右上にある「Start for free」をクリック
![screencapture-corbado-2023-10-01-06_23_345.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/371bb0c3-1be2-0785-3044-e0686c1f9234.png)

    
3. 「Name」と「Email」を入力後、パスキーを作成するかのポップアップが表示されるので「続行」をクリックしてください。
![スクリーンショット 2023-10-01 6.28.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/24a56d9a-9eff-a882-3fcb-619bc6bc4157.png)

    
4. 登録したアカウントのアクティベート
    
    登録したEmailにCorbadoから「Your login request to Corbado」という件名でメールが届いているのでアクティベートをする
    
5. プロジェクトの作成
アクティベートが完了するとプロジェクトを作成できる画面に遷移しますので、「Create new project」をクリック、プロジェクト名を自分自身が分かりやすい名前を入力していってください。入力ができたら「Create Project」をクリック。
![screencapture-app-corbado-projects-2023-10-01-06_40_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/59c8fb09-650a-6149-784f-9638622a7e9a.png)
その後2回質問されるので答えていきます。
初めにWebアプリかNativeアプリか聞かれるので「Web app」を選択して「Next」をクリック
![screencapture-app-corbado-pro-3544238274629393321-intro-app-type-guide-2023-10-01-06_42_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/b9e684f8-e03e-1c6d-ed24-e1c8ad0546c5.png)
次に使用しているフロントエンド言語・バックエンド言語・使用目的を聞かれます。今回は下記の回答をしていきます。
    * フロントエンド言語：Next.js
    * バックエンド言語：No backend (SPA)
    * 使用目的：Test passkeys

    質問に答え終わるとダッシュボードに遷移します。
    ![screencapture-app-corbado-pro-3544238274629393321-2023-10-01-06_48_425.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/3c96250e-5ff2-bb01-c728-f3e5fe0eb5bb.png)

6. 「Getting started > Integration guide」に遷移、APIシークレットキーを作成していきます。
今回は「Step 1」と「Step 2」を使用していきます。
![screencapture-app-corbado-pro-3544238274629393321-app-getting-started-integration-guide-2023-10-01-07_05_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/2d01648b-a8c4-910a-c1ab-0985a9d0c802.png)
始めに「Step 1」内の「Create API secret」クリックしてキーを作成 

    > **⚠️ シークレットキーなので絶対に外部へ公開しないでください**

    ![スクリーンショット 2023-10-01 7.15.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/32bf86de-de88-5c3a-98b2-17f36fdc6d4e.png)
    
    次にアプリケーションURL・リダイレクトURL・証明書利用者IDの登録
    * Application URL：パスキーのシステムを組み込むURLを入力。今回は「http://localhost:3000」
    * Redirect URL：認証成功後に遷移・セッションCookieを受け取るURLを指定。今回は「http://localhost:3000/profile」
    * Relying Party：passkeyがバインドされるドメイン（プロトコル・ポート・パスなし）。今回は「localhost」

    ![スクリーンショット 2023-10-01 8.45.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/e381d39c-f941-b13c-90ab-f7dabf75861e.png)

以上でCorbado側での設定は完了になります。
次はNext.js側を修正していきます。

## Next.jsにパスキーを実装

1. envファイルを作成
    
    ```bash
    NEXT_PUBLIC_PROJECT_ID=xxxxxxxxxxxxxxx
    API_SECRET=xxxxxxxxxxxxxxx
    ```
    
    これでNext.jsにて`process.env`を使用してenvに指定した値にアクセスできます。
注意としてシークレットキーには`NEXT_PUBLIC_`を付けないでください。
    `NEXT_PUBLIC_`はクライアント・サーバー両方でアクセスを可能にするものになります。
    そのため機密性を確保するためにはシークレットキーに`NEXT_PUBLIC_`を付けません。これによりサーバー側でのみアクセスを可能に出来ます。
    
2. 今回使用するパッケージをインストール
    
    ```bash
    npm i @corbado/webcomponent react-loader-spinner
    ```
    
    上記のコマンドを実行してパッケージをインストールしていきます。
    
    * @corbado/webcomponent：Corbadoのパスキーを使用するためのパッケージ
    * react-loader-spinner：ローディング機能を持つパッケージ
3. `declarations.d.ts`の追加
    
    ESlinterを正常に動作させるために`declarations.d.ts`をsrcディレクター内に作成・下記コードを追加します。
    
    ```tsx
    declare namespace JSX {
        interface IntrinsicElements {
            'corbado-auth': any;
            'corbado-logout-handler': any,
            'corbado-auth-provider': any
        }
    }
    
    declare module '@corbado/webcomponent';
    ```
    
4. `page.tsx`を編集
    
    デフォルトで入っているコードを削除・下記コードを追加していきます。
    
    ```tsx
    "use client";
    
    import "@corbado/webcomponent/pkg/auth_cui.css";
    import { useEffect } from "react";
    
    export default function Home() {
      useEffect(() => {
        // クライアントのみで実行
        import("@corbado/webcomponent")
          .then((resp) => {
            console.log(resp);
          })
          .catch((err) => {
            console.log(err);
          });
      }, []);
    
      return (
        <div>
          <corbado-auth
            project-id={process.env.NEXT_PUBLIC_PROJECT_ID}
            conditional="yes"
          >
            <input
              name="username"
              id="corbado-username"
              data-input="username"
              required
              autoComplete="webauthn"
            />
          </corbado-auth>
        </div>
      );
    }
    ```
    
5. `profile.tsx`を追加
    
    新規に`app/profile/page.tsx`を追加・下記コードを追加します。
    
    ```tsx
    "use client";
    
    import { useRouter } from "next/navigation";
    import { useEffect, useState } from "react";
    import { LineWave } from "react-loader-spinner";
    
    const projectID = process.env.NEXT_PUBLIC_PROJECT_ID;
    
    interface User {
      userID: any;
      email: any;
      ID: string;
      created: string;
      emails: Email[];
      fullName: string;
      name: string;
      phoneNumbers: PhoneNumber[];
      status: string;
      updated: string;
    }
    
    interface Email {
      ID: string;
      created: string;
      email: string;
      status: string;
      updated: string;
    }
    
    interface PhoneNumber {
      ID: string;
      phoneNumber: string;
      status: string;
    }
    
    export default function Profile() {
      const [user, setUser] = useState<User | null>(null);
      const [session, setSession] = useState(null);
      const [loading, setLoading] = useState(true);
      const router = useRouter();
    
      // セッション管理
      useEffect(() => {
        setLoading(true);
        // クライアントのみで実行
        import("@corbado/webcomponent")
          .then((module) => {
            const Corbado = module.default || module;
            const sessionInstance = new Corbado.Session(projectID);
            setSession(sessionInstance);
          })
          .catch((err) => {
            console.log(err);
            setLoading(false);
          });
      }, []);
    
      useEffect(() => {
        if (session) {
          setLoading(true);
          // @ts-ignore
          session.refresh((user: any) => {
            setUser(user);
            setLoading(false);
          });
        }
      }, [session]);
    
      const handleLogout = async () => {
        setLoading(true);
        // @ts-ignore
        await session.logout();
    
        setUser(null);
        setLoading(false);
        router.push("/");
      };
    
      return (
        <div>
          {loading && <LineWave />}
    
          {!loading && user && (
            <div>
              <h1>プロフィールページ</h1>
              <div>
                <p>
                  ユーザーID: {user.userID}
                  <br />
                  Email: {user.email}
                </p>
              </div>
              <button onClick={handleLogout}>ログアウト</button>
            </div>
          )}
    
          {!loading && !user && (
            <div>
              <p>ログインされていません</p>
              <p>
                Please go back to <a href="/">ログインする</a>
              </p>
            </div>
          )}
        </div>
      );
    }
    ```
    
6. 動作チェック
    
    `localhost:3000`とフォームが表示されます。
    
    初めはアカウントがないため、下部にある「Sign up」からアカウントを登録・パスキーを使えるように設定します。
    登録後、登録したEmailにアクティベート用のメールが届いているのでアクティベートをしていきます。
    その後、ログイン画面に戻り登録したEmailを入力して「Continue」をクリックするとパスキーが起動してくれます。
    指紋認証 or 顔認証をするとログインが出来ます。
    ![screencapture-localhost-3000-2023-10-01-08_09_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/bdf1aac1-017b-d0ef-73dd-97384ebc6a2a.png)
    ![screencapture-localhost-3000-profile-2023-10-01-08_53_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/311195/c0351799-8480-4c49-b7c4-38ab30fbfa59.png)

## Github

今回書いたコードは下記リポジトリにて公開しているので是非見ていただけると幸いです。

https://github.com/roll1226/test-passkey

## 参考

https://www.corbado.com/blog/nextjs-passkeys