🦁

SuperTokens公式サンプルアプリをカスタマイズしてみた

に公開

はじめに

前回の記事「SuperTokensとは?マルチテナント認証を初心者向けに解説してみた」では、SuperTokensの基本的な概念やマルチテナント認証の考え方、SuperTokensの簡単な実装例(サンプルアプリ)について説明しました。

今回はその続編として、実際にSuperTokensの公式サンプルアプリを動かして、コードの構成を簡単に解説しつつ、カスタマイズも行ってみます。

プロジェクト構成の詳細

今回使用したSuperTokens公式サンプルアプリの、 backendfrontend ディレクトリの構成は以下のようになっています。

example-app/
├── backend/
│   ├── node_modules/
│   ├── .gitignore
│   ├── config.ts         ← SuperTokensの設定(アプリ名、APIドメイン、レシピなどを定義)
│   ├── eslint.config.mjs
│   ├── index.ts          ← バックエンドのエントリーポイント(ミドルウェアなど)
│   └── ...
├── frontend/
│   ├── node_modules/
│   ├── public/
│   ├── src/
│   │   ├── assets/
│   │   ├── Dashboard/
│   │   ├── Home/
│   │   ├── App.css
│   │   ├── App.tsx        ← ルートコンポーネント(ルーティングやUI全体の構成)
│   │   ├── config.tsx   ← SuperTokensのフロント設定(リダイレクト先やUIの有効化設定など)
│   │   ├── main.tsx       ← フロントエンドのエントリーポイント
│   │   └── vite-env.d.ts
│   ├── .gitignore
│   ├── eslint.config.js
│   └── ...

サインイン画面をカスタマイズしてみよう

SuperTokensのフォームは、デフォルトでも使えるように整っていますが、実際のアプリでは「フィールド名を日本語にしたい」「プレースホルダー(placeholder)を分かりやすくしたい」といった場面があると思います。
例えば、ログイン画面で「Email」と表示されているところを「メールアドレス」にしたい場合などですね。今回は、サインイン画面のラベル、プレースホルダーやエラーメッセージなどを変更してみます。

frontend/src ディレクトリのconfig.tsxrecipeListに以下のフォームフィールドを定義します。以下のコードは、サインイン画面で表示される「Email」というラベルを「メールアドレス」に変更し、プレースホルダーを「例: example@example.com」に変えて、必須フィールドのエラーメッセージを「メールアドレスは必須です」に変更する例です。

export const SuperTokensConfig = {
    appInfo: {
        appName: "SuperTokens Demo App",
        apiDomain: getApiDomain(),
        websiteDomain: getWebsiteDomain(),
    },

    recipeList: [
        EmailPassword.init({
            signInAndUpFeature: {
                signInForm: {
                    formFields: [{
                        id: "email",
                        label: "メールアドレス",
                        placeholder: "例: example@example.com",
                        nonOptionalErrorMsg: "メールアドレスは必須です"
                    }]
                }
            }
    }), Session.init()],
    getRedirectionURL: async (context) => {
        if (context.action === "SUCCESS" && context.newSessionCreated) {
            return "/dashboard";
        }
    },
};

ここで、recipeListは、SuperTokensが使う機能(レシピ)を登録する場所であり、EmailPassword.init()を入れることで、「ログイン/サインアップにメールアドレスとパスワードを使います」とSuperTokensに教えています。EmailPassword.init()の中で、signInAndUpFeatureを使ってログインとサインアップ画面の表示や挙動をカスタマイズでき、
今回はこの中のsignInFormを使ってログイン画面をカスタマイズしています。そして、formFields は、各入力フィールド(メールやパスワードなど)に対して、見た目や動作をカスタマイズする場所です。

プロパティ名 説明
id 入力欄の種類("email""password"など)
label 入力欄のタイトル。画面上に表示されるラベル
placeholder 入力欄の中に表示されるヒントテキスト
nonOptionalErrorMsg 入力が必須なのに空欄のまま送信したときに表示されるエラーメッセージ


同様にパスワードフィールドなどもカスタマイズできます。

パスワードリセット機能のカスタマイズ

ログイン機能を提供する上で、ユーザーがパスワードを忘れた場合にパスワードをリセットできる機能が重要です。
SuperTokensでは、あらかじめこの機能が用意されており、デフォルトのUIも提供されています。

サンプルアプリのデフォルトでは、サインイン画面の「Forgot password?」ボタンを押すか、あるいは /auth/reset-password にアクセスすることで、パスワードリセットの画面が表示されます。このセクションでは、パスワードリセット機能を少しカスタマイズしてみましょう。

プロントエンド側のカスタマイズ

1. デフォルトのUIを無効にする

前のセクションで触った frontend/src ディレクトリのconfig.tsxrecipeListの場所に次のような設定をすることで、デフォルトのUIを無効にします。

recipeList: [
        EmailPassword.init({
            resetPasswordUsingTokenFeature: {
                disableDefaultUI: true
            },
        }),
    ]

これで、/auth/reset-password にアクセスしても、またサインイン画面の「Forgot password?」ボタンを押してもパスワードリセットの画面が表示されなくなります。

2. 自分でリセット用のページコンポーネントを作る

次に、デフォルトのUIの代わりに、アプリに ResetPasswordUsingTokenを追加することで、コンポーネントを自分でレンダリングします。

ここでは、frontend/src ディレクトリにResetPasswordPage.tsx というページを作成して、次のように書きます。

import React from "react";
import { ResetPasswordUsingToken } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";

export default function ResetPasswordPage() {
  return (
    <div style={{ maxWidth: "400px", margin: "auto", paddingTop: "50px" }}>
      <h2>パスワードをリセットする画面です</h2>
      <ResetPasswordUsingToken />
    </div>
  );
}

3. ルーティング設定にページを追加する

次に、作成したページをルーティングに追加する必要があります。frontend/src ディレクトリのApp.tsxBrowserRouterに次のようにルートを追加します。BrowserRouterはURLに応じてどのコンポーネントを表示するかを管理しています。

import ResetPasswordPage from "./ResetPasswordPage";

function App() {
    return (
        <SuperTokensWrapper>
            {/* 省略:headerなど */}
            <ComponentWrapper>
                <div className="App app-container">
                    <BrowserRouter>
                        <div className="fill">
                            <Routes>
                                {/* 省略:他のルート設定 */}
                                {/* 追加したパスワードリセット画面のルート設定 */}
                                <Route
                                    path="/custom-reset-password-path"
                                    element={<ResetPasswordPage/>}
                                />
                            </Routes>
                        </div>
                    </BrowserRouter>
                </div>
            </ComponentWrapper>
        </SuperTokensWrapper>
    );
}

これで、/custom-reset-password-path にアクセスすると、先ほど作ったページが表示されます:

次に、サインイン画面の「Forgot password?」ボタンをクリックした時に、/custom-reset-password-path に移動できるようにします。*frontend/src** ディレクトリのconfig.tsxrecipeListに以下のようにgetRedirectionURL を追加することで、リセットパスワードのリンク遷移先をカスタマイズします。

recipeList: [
    EmailPassword.init({
      getRedirectionURL: async (context) => {
        if (context.action === "RESET_PASSWORD") {
          return "/custom-reset-password-path”; 
        }
      },
      resetPasswordUsingTokenFeature: {
        disableDefaultUI: true, 
      },
    }),
    Session.init(),
  ],

バックエンド側のカスタマイズ

SuperTokensはリセットメールにデフォルトで /auth/reset-password を含んだURLを送ります。

しかし、ここで /custom-reset-password-path に変更したので、リンクが動かなくなります。そこで、バックエンドでリンクURLを上書きする必要があります。backend ディレクトリのconfig.tsrecipeListに以下のような設定を追加します。

export const SuperTokensConfig: TypeInput = {
    supertokens: {
        connectionURI: "https://try.supertokens.com",
    },
    appInfo: {
        appName: "SuperTokens Demo App",
        apiDomain: getApiDomain(),
        websiteDomain: getWebsiteDomain(),
    },

    recipeList: [EmailPassword.init({
        emailDelivery: {
                override: (originalImplementation) => {
                    return {
                        ...originalImplementation,
                        sendEmail: async function (input) {
                            if (input.type === "PASSWORD_RESET") {
                                return originalImplementation.sendEmail({
                                    ...input,
                                    passwordResetLink: input.passwordResetLink.replace(
                                        "http://localhost:3000/auth/reset-password",
                                        "http://localhost:3000/custom-reset-password-path" 
                                        )
                                })
                            }
                            return originalImplementation.sendEmail(input);
                        }
                    }
                }
            }
    }), Session.init(), Dashboard.init(), UserRoles.init()],
};

このコードでは、EmailPassword レシピの emailDelivery にある sendEmail という関数を「オーバーライド(override)」しています。オーバーライドすることで、もともと用意されている処理を自分の好きなように書き換えることができます。

具体的には、パスワードリセット用のメールを送るとき、デフォルトで送られるリンクに含まれる /auth/reset-password という部分を、自分で設定した /custom-reset-password-path に置き換えています。

このように signInsignUp などの処理を自由にオーバーライドできるので、独自のバリデーションを追加したり、ログを残したり、特別な処理を挟むこともできます。つまり、標準機能に自分のアプリならではのロジックを柔軟に組み込めるのが魅力ですね。

まとめ

今回はSuperTokens公式サンプルアプリを実際に動かしながら、サインイン画面のラベルやプレースホルダーのカスタマイズ、さらにパスワードリセット機能のフロント・バックエンド両面を簡単にカスタマイズしてみました。

SuperTokensはカスタマイズ性が高く、標準機能にプラスして自分のアプリ固有の要件も実装できるので、今後の認証機能開発にぜひ役立ててみてください。

参考資料

https://supertokens.com/docs/authentication/email-password/customize-the-sign-in-form
https://supertokens.com/docs/authentication/email-password/password-reset

Emoba Tech Blog

Discussion