External User Verification with Forms
アプリケーションによっては、ユーザーがシステムにログインできるようになる前に、外部サービスで本人(または資格)確認を行う必要があります。例えば、SaaS の無料トライアルで招待コードを要求するケース、不動産掲載サイトで住所確認を行うケース、あるいは医療・金融サービスのようにより厳格な検証が必要なケースなどが考えられます。
Auth0 は最近、サインアップ時にユーザーから追加情報を収集できるようにするため Custom Prompts をリリースしました。これらのフィールドは取得でき、さらに Pre Registration Action Flow で追加の処理を行えます。ただし、情報を取りすぎたり(あるいは条件付きで情報を取得したり)すると、エンドユーザー体験に影響が出る可能性があります。そこで Auth0 は最近、ユーザー情報の収集、ワークフロー実行、イベントデータのオーケストレーションを行うための、新しいドラッグ&ドロップのエディタである Forms もリリースしました。これは検証ワークフローにも適用できます。本記事では「招待コード」ワークフロー、つまりユーザーがアプリケーションにログインできるようになる前に招待コードを検証しなければならないケースを扱います。
注:Custom Prompts と Forms は非常に相性が良いです。サインアップを完了したユーザーは Login Flow をトリガーし、そこで Forms を起動できます。
このブログ記事では Auth0 Forms を使って招待コードを収集し、外部でホストされている API へ HTTP リクエストを送って「コード + メールアドレス」の組み合わせが有効かどうかを検証します。これは限定的なユースケースの一例ですが、ここで説明するソリューションは「アクセス許可を与える前に、ユーザー入力を外部で検証する必要がある」あらゆるシナリオに対応できます。
ユースケース
あなたが、まもなくプライベート Beta をローンチする SaaS アプリケーションを持っているとしましょう。ユーザーがあなたのプラットフォームへサインアップすること自体は制限したくありません(MarTech スタックに取り込めるようにしたい)。しかし、チームメンバーまたはアフィリエイトからアクセスを付与されたユーザー以外は、正式にプラットフォームへログインできないようにしたい、とします。
このユースケースにはさまざまな解き方がありますが、本記事では「ユーザーに “Invite Code(招待コード)” を入力してもらい、バックエンドアプリケーションがそのコードでアクセス付与済みであることを検証する」という方法で解決します。プライベートローンチへのアクセスを承認する前に、ユーザーのメールアドレスとコードを一緒に送信して検証します。
Auth0 Custom Prompts を使えばサインアップ中にこのコードを収集できますが、検証に失敗した場合はアカウント自体が作成されません。あなたの目的は「アカウントは作成したうえで、プライベート Beta へのアクセスだけを招待コードで制御する」ことです。
フォームでユーザーに入力を促す
このフローを起動するために Post-Login Action を利用します。ユーザーはサインアップ後に自動的にこのフローへ入り、以後アプリケーションへ戻ってくるたびにこのフローに入ります。この Action はユーザーのプロフィール上のフラグを確認し、以前に検証済みかどうかを判定します。
exports.onExecutePostLogin = async (event, api) => {
if (event.user.app_metadata.verified !== true) {
const FORM_ID = 'ap_eJWCKKPimBy86AyTRsQikQ';
api.prompt.render(FORM_ID);
}
};
Auth0 テナントの Forms には Actions → Forms からアクセスできます。これを開くと新しいタブでエディタが起動します。以下は、本記事の特定フロー用のビジュアルエディタです。
High Level Form Flow
ユーザーはテキスト入力で招待コードを入力するよう促され、その後フローが呼び出されます。次の画像では、コード検証の結果にエラーがあるかどうかをチェックするフローダイアグラムが確認できます。エラーがあればエラーメッセージを表示し、そうでなければフローを継続します。
Verification Flow
ここでは、ユーザーのメールアドレスと収集した招待コードを使って、あなたのエンドポイントを呼び出す HTTP リクエストステップの構成方法が分かります。
HTTP Request Structure
レスポンスに基づき、結果が true ならユーザーはフローを継続し、そうでなければエラーメッセージが表示され、正しい値を入力するまで先へ進めません。
Conditional Configuration
ユーザーが正しい値を入力すると、フローは終了してメインパスへ戻り、アクセスが付与されたこととアプリケーションを利用できることを通知します。フローを起動した Action には、認証トランザクションを再開するための continue セクションが必要です。また、再びこのフローに入らなくて済むよう、ユーザーのプロフィールにフラグを保存します。
exports.onContinuePostLogin = async (event, api) => {
api.user.setAppMetadata("verified", true);
};
Success Prompt
そして、サインアップして「メール + コード」の組み合わせが有効な新規ユーザーが通る、エンドツーエンドのフローがこちらです。
End to End User Journey
以下は、検証フローを今日から構築し始められる完成版 JSON です!Forms の画面で Create Form → Import It! と進むことで、この JSON を直接テナントへインポートできます。
Importing Existing Flow with JSON
{
"version": "3.0.0",
"form": {
"name": "Invite Code",
"description": null,
"messages": {
"custom": {},
"errors": {}
},
"languages": {
"default": null,
"primary": "en"
},
"translations": {},
"start": {
"nextNode": "step_Y3AO",
"coordinates": {
"x": 22,
"y": 167
},
"hiddenFields": []
},
"nodes": [
{
"id": "step_Y3AO",
"type": "STEP",
"alias": "Collect Code",
"config": {
"nextNode": "flow_Cilk",
"components": [
{
"id": "rich_text_sME2",
"type": "RICH_TEXT",
"config": {
"content": "<h3><strong>Please enter your invite code.</strong></h3>"
},
"category": "BLOCK"
},
{
"id": "invite_code",
"hint": null,
"type": "TEXT",
"label": "Invite Code",
"config": {
"maxLength": null,
"minLength": null,
"multiline": false,
"placeholder": "XXX-XXX",
"defaultValue": null
},
"category": "FIELD",
"required": true,
"transient": false
},
{
"id": "next_button_xlCe",
"type": "NEXT_BUTTON",
"config": {
"text": "Continue"
},
"category": "BLOCK"
}
]
},
"coordinates": {
"x": 323,
"y": -36
}
},
{
"id": "flow_Cilk",
"type": "FLOW",
"alias": "Verify Code",
"config": {
"flowId": "#FLOW-1#",
"nextNode": "step_IifF"
},
"coordinates": {
"x": 1051,
"y": 139
}
},
{
"id": "step_IifF",
"type": "STEP",
"alias": "Verification Complete",
"config": {
"nextNode": "$ending",
"components": [
{
"id": "rich_text_pMye",
"type": "RICH_TEXT",
"config": {
"content": "<h3><strong>Welcome to Demo0, </strong></h3><h3><strong>Click Continue to get Started!</strong></h3>"
},
"category": "BLOCK"
},
{
"id": "next_button_Z6Md",
"type": "NEXT_BUTTON",
"config": {
"text": "Continue"
},
"category": "BLOCK"
}
]
},
"coordinates": {
"x": 1510,
"y": -2
}
}
],
"ending": {
"content": null,
"redirection": null,
"callback": null,
"afterSubmit": {
"email": null,
"flowId": null
},
"coordinates": {
"x": 2194,
"y": 110
},
"resumeFlow": true
},
"social": [],
"style": {
"css": null,
"theme": "ROUND",
"version": "MODERN"
},
"tags": []
},
"flows": {
"#FLOW-1#": {
"name": "Verify Invite Code",
"description": null,
"actions": [
{
"id": "http_request_verify_code",
"type": "HTTP",
"alias": "Verify Invite Code",
"notes": null,
"action": "SEND_REQUEST",
"params": {
"url": "https://edge.samyap.dev/api/verify-code",
"body": {
"email": "{{context.user.email}}",
"invite_code": "{{fields.invite_code}}"
},
"type": "JSON",
"basic": null,
"method": "POST",
"params": {},
"headers": {},
"acceptNOK": false,
"connectionId": null
},
"allowFailure": false
},
{
"id": "if_then_condition_q0Ym",
"type": "FLOW",
"alias": "Verify Code Result",
"notes": null,
"action": "BOOLEAN_CONDITION",
"params": {
"then": [],
"evaluate": {
"operands": [
{
"operands": [
"{{ http_request_verify_code.body.result }}",
"true"
],
"operator": "EQ"
}
],
"operator": "AND"
},
"otherwise": [
{
"id": "show_error_message_4rkM",
"type": "FLOW",
"alias": null,
"notes": null,
"action": "ERROR_MESSAGE",
"params": {
"message": "Your code is invalid. Please contact support for help"
},
"allowFailure": false
}
]
},
"allowFailure": false
}
],
"triggers": {
"webhook": {
"secret": null,
"enabled": false
}
},
"synchronous": true,
"security": {
"rateLimits": []
}
}
},
"connections": {}
}
まとめ
今日は、Auth0 Forms を活用してユーザーから入力を収集し、ノーコードの Forms / Flows エディタを使って外部システムでユーザーを検証する方法を学びました。この能力は、Progressive Profiling(段階的プロファイリング)や同意取得(Consent Collection)など、さまざまなアプリケーションへ拡張できます。
Auth0 では、お客様に高品質のサービスと開発者体験を提供することを目指しています。お客様とユーザーの最初の接点はログイン画面であることが多いため、ユーザー体験が最高水準であることの重要性を理解しています。ホスト型の Universal Login により、こうした高セキュリティなワークフローをデプロイ・保守する負担を取り除けます。本記事で示した Forms の機能により、チームはより速く改善を回し、ブランドに合った “粘り強い(リテンションにつながる)” 体験へカスタマイズし、最終的にビジネス成長を後押しできます。