本人確認
あなたのバックエンドが制御する署名を使い、サインイン済みの人物を認識し、その会話履歴を複数デバイスにまたいで復元します。
なぜ検証するのか#
デフォルトでは SDK は匿名です。安定した visitor_id を生成してプラットフォームのキーストアに保持します。それは一つのデバイス上の一つの会話には十分です。特定のユーザーを認識し、再ログインや別デバイスで履歴を復元し、会話を連絡先プロフィールに確実に紐づけるには、クライアントが名乗るとおりの相手であるという証明がバックエンドに必要です。SDK が単に「私はユーザー 42 です」と送るだけなら、誰でも別の ID になりすまして他人のチャットを読めてしまいます——そのため Respondo は、あなたのバックエンドだけが生成できる暗号署名 userHash を要求します。
identity secret の取得#
identity_secret は、あなたのエージェント(ウィジェットチャネルが接続されている AI ワーカー)に紐づくシークレット文字列です。ダッシュボードの チャネル → ウィジェット → 本人確認 で生成してください。シークレットはエージェント上に置かれるため、そのエージェントに紐づくすべてのチャネル——ウェブウィジェットとモバイル SDK の両方——が同じシークレットを共有します。これは身元に署名する権限を与えるため、あなたのバックエンドにのみ置き、アプリに同梱してはいけません。
userHash の数式#
userHash は、署名対象の単一の身元文字列に対する HMAC-SHA256 を小文字の hex でエンコードしたものです:
userHash = HMAC_SHA256( identity_secret, payload )
payload = userId // userId が設定されている場合
= email // それ以外で email が設定されている場合
= (無効) // 両方が空なら、署名する対象がない- シークレットが HMAC の鍵、身元文字列がメッセージです——逆ではありません。
- 署名する文字列はちょうど一つ——生の
userId(またはメールアドレス)で、ソルトや JSON ラッパーは付けません。 -
userIdとメールアドレスの両方を渡す場合は、userIdに署名します(こちらが優先されます)。 - 出力は hex(SHA-256 で 64 文字)であって、base64 ではありません。
バックエンドの例#
署名はバックエンドで計算し、完成した userHash をアプリに渡します。
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
// HMAC_SHA256(identity_secret, userId) を小文字の hex で。
func ComputeUserHash(identitySecret, userID string) string {
mac := hmac.New(sha256.New, []byte(identitySecret))
mac.Write([]byte(userID))
return hex.EncodeToString(mac.Sum(nil))
}const crypto = require("crypto");
// モバイルクライアントで Respondo.identify に渡す userHash を返す。
function computeUserHash(identitySecret, userId) {
return crypto
.createHmac("sha256", identitySecret)
.update(userId, "utf8")
.digest("hex");
}import hmac
import hashlib
# モバイルクライアントで Respondo.identify に渡す userHash を返す。
def compute_user_hash(identity_secret: str, user_id: str) -> str:
return hmac.new(
identity_secret.encode(),
user_id.encode(),
hashlib.sha256,
).hexdigest()<?php
// モバイルクライアントで Respondo.identify に渡す userHash を返す。
function computeUserHash(string $identitySecret, string $userId): string {
return hash_hmac('sha256', $userId, $identitySecret);
}ハッシュが Respondo に届くまで#
/identity のルートは存在せず、登録するものもありません。 userHash は、SDK がすでに行っているリクエストに相乗りするフィールドです。ホップは 2 つ、あなたが作るのは最初の 1 つだけです:
- あなたのバックエンド → あなたのアプリ。 ハッシュの届け方は自由です。もっとも安上がりなのは、すでに返しているログイン/ブートストラップのレスポンスにフィールドを一つ足すやり方で、追加のラウンドトリップは発生しません。あなた自身のバックエンドに専用エンドポイントを置く方法(たとえば
POST /myapp/identityが{ userId, userHash }を返す)でも同じように機能します。Respondo はそのエンドポイントをホストしません——実装するのはあなたです。 - あなたのアプリ → Respondo。 これは SDK が処理します。いったん
identifyを呼べば、SDK が該当するすべてのリクエストにハッシュを添付し、バックエンドは毎回それを再検証します。
POST /api/v1/chat body identity.userHash
GET /api/v1/chat/resume query user_hash
GET /api/v1/chat/history query user_hash
GET /api/v1/chat/ws frames user_hash(接続後の subscribe/identify JSON フレーム — ハンドシェイク URL には含まれない)
GET /api/v1/widget/tours query user_hash
GET /api/v1/widget/checklists query user_hash
POST /api/v1/widget/push/register body user_hashハッシュはセッションと一度交換して終わりではなく、リクエストごとに再検証されます——だからこそ、盗まれた userId だけでは何の役にも立ちません。
identify は呼ばれず、チャットは匿名のまま動きます。これは想定どおりの挙動であって Respondo のエラーではありません——あなた自身のパスで出た 404 は、あなたの側の未対応タスクであり、壊れた連携ではありません。SDK への身元の受け渡し#
署名済みの身元をバックエンドから取得し、その userHash を、どのプラットフォームでも identify に渡します:
Respondo.identify(
RespondoIdentity(userId = "42", email = "user@example.com", userHash = hash),
)Respondo.identify(
RespondoIdentity(userId: "42", email: "user@example.com", userHash: hash)
)Respondo.identify(RespondoIdentity(
userId: '42', email: 'user@example.com', userHash: hash,
));無効な userHash の挙動#
エージェントで検証が有効になっていて署名が欠けているか誤っている場合、バックエンドは匿名への静かなダウングレードを行います。チャットは引き続き動作し、 visitor_id は保持され、会話は匿名の連絡先に紐づけられます——ただしプロフィールへのリンクはなく、デバイス間の履歴もありません。ユーザーが「認識されない」場合、ほとんどの場合は署名が原因です。 userId に署名したか(メールアドレスや JSON ではなく)、正しい identity_secret を使ったか、小文字の hex を出力したかを確認してください。エージェントの identity_secret が空の場合、検証はオフになり userId / メールアドレスはそのまま受け入れられます。
identity_secret のローテーションは、事実上ハードな切り替えです。古いシークレットで生成されたハッシュはすべて即座に検証されなくなるため、すでにサインイン済みのユーザーは、あなたのバックエンドが新しいシークレットで userHash を再計算して再供給するまで、静かに匿名へフォールバックします。署名側を同じタイミングで更新できるときにのみ、シークレットをローテーションしてください。