36
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

image.png

はじめに

NRI OpenStandia Advent Calendar 2022 の18日目は、認証の仕組みを提供するオープンソースであるSuperTokensの概要から簡単な実装までを説明します。

本記事を読んでより詳しくSuperTokensの使い方を知りたいと思った人は、公式ドキュメントが充実しているので、そちらを見ることをお勧めします。

1 SuperTokensの概要

1.1 SuperTokensとは

SuperTokensとは「オープンソースの認証プラットフォーム」です。

SuperTokensの特徴は、簡単に認証機能を実現することです。
認証機能の実現にはアプリのUX、開発、セキュリティといった様々な知識や技術を要するため、認証機能を作り上げるためには多くの時間がかかります。
SuperTokensは安全なログインとセッション管理を行う機能をSDKで提供します。
そのため、開発者の負荷が少なくユーザに安全な認証を提供できます。

1.2 SuperTokensの仕組み

SuperTokensのコンポーネント

画像2.png

SuperTokensには3つのコンポーネントがあります。

コンポーネント  説明
フロントエンドSDK ログインUIのレンダリングとセッショントークンの自動管理を担当する。
バックエンドSDK サインアップ、サインイン、サインアウト、セッションの更新などのAPIを提供する。
フロントエンドはこれらのAPIと通信する。
これらの API は、アプリケーションの API と同じドメインで公開される。
SuperToken Core 認証のコアロジックを含むHTTPサービス。
DBとのインターフェイスを担当し、バックエンドSDKによってDBを必要とする操作についてクエリされる。

ログインの仕組み

メールパスワードログインを例にログインの仕組みを説明します。
画像1.png

  1. ログインするために、フロントエンドSDKがユーザの資格情報と共にバックエンドSDKのAPI/auth/signinを呼び出す。
  2. バックエンドSDKはリクエストの検証を行い、資格情報を使用してSuperTokens Coreの/recipe/signinを呼び出す。
  3. SuperTokens Coreはパスワードを検証し、ステータスをバックエンドSDKに返す。
  4. 検証失敗のステータスがバックエンドSDKに帰ってきた場合、バックエンドSDKはそれをフロントエンドSDKに送信し、フロントエンドSDKはユーザにエラーメッセージを表示する。
  5. 検証成功のステータスがバックエンドSDKに帰ってきた場合、バックエンドSDKはセッションレシピ(※1)を使用して新しいセッショントークン(アクセストークン + リフレッシュトークン)を作成するようにSuperTokens Coreの/recipe/sessionを呼び出す。
  6. SuperTokens Coreがセッショントークンを作成してバックエンドSDKに返した後、バックエンドSDKはセッショントークンをCookieにアタッチし、フロントエンドSDKに送信する。
  7. ユーザログイン完了。

※1 SupenrTokensでは、ログインに使用されるさまざまな処理をそれぞれ「レシピ」と呼びます。概要は「2.1 SuperTokensが提供するログイン方法」で説明しています。

1.3 SuperTokensの長所と短所

  • 長所
    • オープンソース(ユーザ数に制限なく永久に無料)
    • カスタマイズ性が高い
  • 短所
    • IdPではない

SuperTokensはSaaSとしても提供しており、その場合は有料になります。

2 SuperTokensのサンプルアプリを動かす

2.1 SuperTokensが提供するログイン方法

SuperTokensでは、各ログイン方法を「レシピ」と表現しています。
レシピは公式ドキュメントのUser Guidesにまとめられています。
レシピは以下のものがあります。

  • パスワードレス
  • ソーシャルログイン
  • メールパスワードログイン
  • 電話パスワードログイン
  • ユーザ管理ダッシュボード
  • セッション管理
  • ユーザロール

これらのレシピは「メールパスワード+ソーシャルログイン」のように組み合わせて使用することもできます。

2.2 サポートされているフレームワーク

フロントエンド

React、Next.js、Angular、Vue.js
image.png

バックエンド

Node.js、Nest.js、Python、GoLang
image.png

2.3 メールパスワードログイン

レシピの中から、一般的なログイン方法であるメールパスワードログインを実装してみます。

実装環境

  • WSL2
  • Windows10
  • npm:9.1.3
  • node:16.17.1

フロントエンドとバックエンドの構築

ターミナルで次のコマンドを実行します。

$ npx create-supertokens-app@latest --recipe=emailpassword

今回はアプリ名をmy-app、フロントエンドにReact、バックエンドにNode.jsを指定します。

$ npx create-supertokens-app@latest --recipe=emailpassword


 _____                     _____     _
/  ___|                   |_   _|   | |
\ `--. _   _ _ __   ___ _ __| | ___ | | _____ _ __  ___
 `--. \ | | | '_ \ / _ \ '__| |/ _ \| |/ / _ \ '_ \/ __|
/\__/ / |_| | |_) |  __/ |  | | (_) |   <  __/ | | \__ \
\____/ \__,_| .__/ \___|_|  \_/\___/|_|\_\___|_| |_|___/
            | |
            |_|


create-supertokens-app (v0.0.20) lets you quickly get started with using SuperTokens!

Choose your tech stack and the authentication method, we will create a working project that uses SuperTokens for you.


? What is your app called? my-app
? Choose a frontend framework (Visit our documentation for integration with other frameworks): React
? Choose a backend framework (Visit our documentation for integration with other frameworks): Node.js

✅ Finished setting up folder structure!

✅ Setup complete!


To start the application run the following command:

cd my-app
npm run start

これで、フロントエンドとバックエンドの構築は終了です。
今回は簡単に構築するためにSDKを使用しましたが、手動でセットアップすることも可能です。

実際に起動してみましょう。

$ cd my-app
$ npm start

ブラウザを開いてhttp://localhost:3000/authにアクセスするとサインイン画面が表示されます。
image.png

SuperTokens Coreの構築

今回はdockerイメージを使用して構築します。

ターミナルで次のコマンドを実行します。
今回は外部DBを使用せず、SuperTokens内部のDBを使用します。
DBの種類はmySQLを指定します。

docker run -p 3567:3567 -d registry.supertokens.io/supertokens/supertokens-mysql:4.2

ブラウザを開いてhttp://localhost:3567/helloにアクセスするとHelloと表示されているので、コンテナが正常に起動したことを確認できます。
image.png

バックエンドSDKとSuperTokens Coreの接続

バックエンドのbackend/config.tsにSuperTokens Coreの情報を追加します。

export const SuperTokensConfig: TypeInput = {
    supertokens: {
        // this is the location of the SuperTokens core.
-        connectionURI: "https://try.supertokens.com",
+        connectionURI: "http://localhost:3567",
+        apiKey: "someKey" // OR can be undefined
    },
    appInfo: {
        appName: "SuperTokens Demo App",
        apiDomain: "http://localhost:3001",
        websiteDomain: "http://localhost:3000",
    },
    // recipeList contains all the modules that you want to
    // use from SuperTokens. See the full list here: https://supertokens.com/docs/guides
    recipeList: [
        EmailPassword.init(),
        Session.init(),
        Dashboard.init({
            apiKey: "supertokens_is_awesome",
        }),
    ],
};

supertokens: {...}の中にあるconnectionURIはSuperTokens CoreのURIです。
apiKeyはAPIキーを表しますが、デフォルトではAPIキーはないためとりあえず適当な文字列を置きます。
APIキーの追加方法は公式ドキュメント「Adding API Keys」を参考にしてください。

appInfo: {...}はアプリの基本情報を表します。
アプリの情報をまとめると次のようになります。

名前
アプリの名前 my-app
APIドメイン http://localhost:3001
APIベースパス /auth(デフォルト)
ウェブサイトのドメイン http://localhost:3000
ウェブサイトのベースパス /auth(デフォルト)

ユーザ管理ダッシュボードの設定と表示

ユーザ管理ダッシュボードは、SuperTokens上のユーザリストを表示し、セッション、メタデータ、ロール、アカウント情報を簡単に表示、変更、削除することができます。
バックエンドのbackend/config.tsに一意のAPIキーを指定します。

export const SuperTokensConfig: TypeInput = {
    supertokens: {
        // this is the location of the SuperTokens core.
        connectionURI: "http://localhost:3567",
        apiKey: "someKey" // OR can be undefined
    },
    appInfo: {
        appName: "SuperTokens Demo App",
        apiDomain: "http://localhost:3001",
        websiteDomain: "http://localhost:3000",
    },
    // recipeList contains all the modules that you want to
    // use from SuperTokens. See the full list here: https://supertokens.com/docs/guides
    recipeList: [
        EmailPassword.init(),
        Session.init(),
        Dashboard.init({
-            apiKey: "supertokens_is_awesome",
+            apiKey: "my_api_key",
        }),
    ],
};

recipeList: {...}はSuperTokensのレシピの追加や設定を行います。

ブラウザでhttp://localhost:3001/auth/dashboardにアクセスするとAPIキーの入力画面が表示されます。
先ほど指定したAPIキーを入力します。
image.png

認証に成功すると、ダッシュボードが表示されます。
image.png

次に、フロントエンドアプリに移動し、サインアップフローを介してユーザを作成します。
image.png

ダッシュボードに戻り、ページを更新するとユーザが表示されます。
ダッシュボードには標準で、ユーザ情報確認・修正機能、パスワード変更機能、メールアドレス修正機能、ユーザ削除機能が備わっています。
6198d-ityar.gif

3 SuperTokensのサンプルアプリをカスタマイズする

SuperTokensの公式ドキュメントでは、セッション管理や認証フローに関する様々なカスタマイズ方法が記載されています。
今回はそれらを参考に、「2 SuperTokensのサンプルアプリを動かす」で作成したアプリをカスタマイズしてみます。

3.1 サインアップフォームフィールドの追加

公式ドキュメント「Adding Extra Fields」と「Storing user metadata」を参考に、サインアップフォームフィールドの追加をします。
今回は「First name」と「Last name」をフォームに追加します。

フロントエンド

フロントエンドのfrontend/config.tsxにフォームフィールドの情報を追加します。

import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";

export const SuperTokensConfig = {
    enableDebugLogs: true,
    appInfo: {
        appName: "SuperTokens Demo App",
        apiDomain: "http://localhost:3001",
        websiteDomain: "http://localhost:3000",
    },
    recipeList: [
+        EmailPassword.init({
+            signInAndUpFeature: {
+                signUpForm: {
+                    formFields: [{
+                        id: "first_name",
+                        label: "First name",
+                        placeholder: "First name"
+                    }, {
+                        id: "last_name",
+                        label: "Last name",
+                        placeholder: "Last name"
+                    }]
+                }
+            }
+        }), 
        Session.init()],
};

バックエンド

バックエンドのbackend/config.tsxにフォームフィールドの情報を追加します。
また、サインアップが成功した時にフォームフィールドを処理するためにsignUpPOSTAPIをovverrideします。

export const SuperTokensConfig: TypeInput = {
    supertokens: {...},
    appInfo: {...},
    recipeList: [
+        EmailPassword.init({
+            signUpFeature: {
+                formFields: [{
+                    id: "first_name"
+                  }, {
+                    id: "last_name"
+                  }]
+            },
            
+            override: {
+                apis: (originalImplementation) => {
+                    return {
+                        ...originalImplementation,
+                        signUpPOST: async function (input) {
+
+                            if (originalImplementation.signUpPOST === undefined) {
+                                throw Error("Should never come here");
+                            }
+
+                            // First we call the original implementation of signUpPOST.
+                            let response = await originalImplementation.signUpPOST(input);
+
+                            // Post sign up response, we check if it was successful
+                            if (response.status === "OK") {
+
+                                // These are the input form fields values that the user +used while signing up
+                                let formFields = input.formFields;
+
+                            }
+                            return response;
+                        }
+                    }
+                }
+            }
+          }),
        Session.init(),
        Dashboard.init({
            apiKey: "my_api_key",
        }),
    ],
};

ただし、このままではSuperTokensは追加したフォームフィールドを保存しません。
ユーザのサインアップ後に、各ユーザに関するデータを保存するために、ユーザメタデータの保存を行います。

+ import UserMetadata from "supertokens-node/recipe/usermetadata";

export const SuperTokensConfig: TypeInput = {
    supertokens: {...},
    appInfo: {...},
    recipeList: [
+        UserMetadata.init(),
        EmailPassword.init({
            signUpFeature: {
                formFields: [{
                    id: "first_name"
                  }, {
                    id: "last_name"
                  }]
            },
            override: {
                apis: (originalImplementation) => {
                    return {
                        ...originalImplementation,
                        signUpPOST: async function (input) {

                            if (originalImplementation.signUpPOST === undefined) {
                                throw Error("Should never come here");
                            }

                            // First we call the original implementation of signUpPOST.
                            let response = await originalImplementation.signUpPOST(input);

                            // Post sign up response, we check if it was successful
                            if (response.status === "OK") {

                                // These are the input form fields values that the user used while signing up
                                let formFields = input.formFields;

+                                for (const formField of formFields) {
+                                    if (formField.id === "first_name") await UserMetadata.updateUserMetadata(response.user.id, { first_name: formField.value });
+                                    if (formField.id === "last_name") await UserMetadata.updateUserMetadata(response.user.id, { last_name: formField.value });
+                                }

                            }
                            return response;
                        }
                    }
                }
            }
          }),
        Session.init(),
        Dashboard.init({
            apiKey: "my_api_key",
        }),
    ],
};

実行結果

サインアップ時に、「First name」と「Last name」を記入します。
image.png

サインアップ後ユーザのメタデータを確認すると、「First name」と「Last name」のidであるfirst_namelasta_nameが追加されているのが確認できます。
また、first_namelasta_nameのように、ダッシュボードの「USER INFORMATION」に表示するために事前に準備されてある固定IDがあり、それらのIDにメタデータを保存すると「USER INFORMATION」に反映されます。
image.png

まとめ・所感

本記事では、オープンソースで認証の仕組みを提供するSuperTokensの概要から簡単な実装までを説明しました。
簡単に認証機能を提供することをSuperTokensの目的としているように、とても簡単に作ることができました。
特にフロントエンドとバックエンドがSDKで提供されているので、様々な種類のログイン機能がコマンド一つで出来上がる点がありがたいです。

SuperTokensではロードマップが策定されており、いろんな機能がこれから開発予定です。
Open ID CoonectのIdProviderとして振る舞う機能拡張が予定されているなど、今後の発展に期待します。

参考資料

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?