はじめに
NRI OpenStandia Advent Calendar 2022 の18日目は、認証の仕組みを提供するオープンソースであるSuperTokensの概要から簡単な実装までを説明します。
本記事を読んでより詳しくSuperTokensの使い方を知りたいと思った人は、公式ドキュメントが充実しているので、そちらを見ることをお勧めします。
1 SuperTokensの概要
1.1 SuperTokensとは
SuperTokensとは「オープンソースの認証プラットフォーム」です。
SuperTokensの特徴は、簡単に認証機能を実現することです。
認証機能の実現にはアプリのUX、開発、セキュリティといった様々な知識や技術を要するため、認証機能を作り上げるためには多くの時間がかかります。
SuperTokensは安全なログインとセッション管理を行う機能をSDKで提供します。
そのため、開発者の負荷が少なくユーザに安全な認証を提供できます。
1.2 SuperTokensの仕組み
SuperTokensのコンポーネント
SuperTokensには3つのコンポーネントがあります。
コンポーネント | 説明 |
---|---|
フロントエンドSDK | ログインUIのレンダリングとセッショントークンの自動管理を担当する。 |
バックエンドSDK | サインアップ、サインイン、サインアウト、セッションの更新などのAPIを提供する。 フロントエンドはこれらのAPIと通信する。 これらの API は、アプリケーションの API と同じドメインで公開される。 |
SuperToken Core | 認証のコアロジックを含むHTTPサービス。 DBとのインターフェイスを担当し、バックエンドSDKによってDBを必要とする操作についてクエリされる。 |
ログインの仕組み
メールパスワードログインを例にログインの仕組みを説明します。
- ログインするために、フロントエンドSDKがユーザの資格情報と共にバックエンドSDKのAPI
/auth/signin
を呼び出す。 - バックエンドSDKはリクエストの検証を行い、資格情報を使用してSuperTokens Coreの
/recipe/signin
を呼び出す。 - SuperTokens Coreはパスワードを検証し、ステータスをバックエンドSDKに返す。
- 検証失敗のステータスがバックエンドSDKに帰ってきた場合、バックエンドSDKはそれをフロントエンドSDKに送信し、フロントエンドSDKはユーザにエラーメッセージを表示する。
- 検証成功のステータスがバックエンドSDKに帰ってきた場合、バックエンドSDKはセッションレシピ(※1)を使用して新しいセッショントークン(アクセストークン + リフレッシュトークン)を作成するようにSuperTokens Coreの
/recipe/session
を呼び出す。 - SuperTokens Coreがセッショントークンを作成してバックエンドSDKに返した後、バックエンドSDKはセッショントークンをCookieにアタッチし、フロントエンドSDKに送信する。
- ユーザログイン完了。
※1 SupenrTokensでは、ログインに使用されるさまざまな処理をそれぞれ「レシピ」と呼びます。概要は「2.1 SuperTokensが提供するログイン方法」で説明しています。
1.3 SuperTokensの長所と短所
- 長所
- オープンソース(ユーザ数に制限なく永久に無料)
- カスタマイズ性が高い
- 短所
- IdPではない
SuperTokensはSaaSとしても提供しており、その場合は有料になります。
2 SuperTokensのサンプルアプリを動かす
2.1 SuperTokensが提供するログイン方法
SuperTokensでは、各ログイン方法を「レシピ」と表現しています。
レシピは公式ドキュメントのUser Guidesにまとめられています。
レシピは以下のものがあります。
- パスワードレス
- ソーシャルログイン
- メールパスワードログイン
- 電話パスワードログイン
- ユーザ管理ダッシュボード
- セッション管理
- ユーザロール
これらのレシピは「メールパスワード+ソーシャルログイン」のように組み合わせて使用することもできます。
2.2 サポートされているフレームワーク
フロントエンド
バックエンド
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
にアクセスするとサインイン画面が表示されます。
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
と表示されているので、コンテナが正常に起動したことを確認できます。
バックエンド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キーを入力します。
次に、フロントエンドアプリに移動し、サインアップフローを介してユーザを作成します。
ダッシュボードに戻り、ページを更新するとユーザが表示されます。
ダッシュボードには標準で、ユーザ情報確認・修正機能、パスワード変更機能、メールアドレス修正機能、ユーザ削除機能が備わっています。
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
にフォームフィールドの情報を追加します。
また、サインアップが成功した時にフォームフィールドを処理するためにsignUpPOST
APIを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」を記入します。
サインアップ後ユーザのメタデータを確認すると、「First name」と「Last name」のidであるfirst_name
とlasta_name
が追加されているのが確認できます。
また、first_name
やlasta_name
のように、ダッシュボードの「USER INFORMATION」に表示するために事前に準備されてある固定IDがあり、それらのIDにメタデータを保存すると「USER INFORMATION」に反映されます。
まとめ・所感
本記事では、オープンソースで認証の仕組みを提供するSuperTokensの概要から簡単な実装までを説明しました。
簡単に認証機能を提供することをSuperTokensの目的としているように、とても簡単に作ることができました。
特にフロントエンドとバックエンドがSDKで提供されているので、様々な種類のログイン機能がコマンド一つで出来上がる点がありがたいです。
SuperTokensではロードマップが策定されており、いろんな機能がこれから開発予定です。
Open ID CoonectのIdProviderとして振る舞う機能拡張が予定されているなど、今後の発展に期待します。
参考資料