シングルサインオン(SSO)
仕組み
- 読者があなたのサイトにログインしている間、あなたのサーバーが、その読者が誰であるかを示す短命のトークンに署名します。
- ページは、そのトークンを
sso-token属性でコメントウィジェットに渡します。 - Crowment が署名を検証し、その身元で読者をコメントにログインさせます。
トークンは、あなたのサーバーと Crowment だけが知るシークレットで署名されるため、誰かが他人になりすまして読者をログインさせることはできません。シングルサインオンはすべてのプランで利用できます。
1. 有効にする
Site Console → ウィジェット → Single Sign-On を開きます。設定はオーナーと管理者が変更できます。
署名用シークレットを作成する
これを行えるのはウィジェットのオーナーだけです。シークレットは 1 回だけ表示されます。たとえば CROWMENT_SSO_SECRET として、サーバーの環境変数に保存してください。ブラウザで動くコード、アプリのバンドル、公開リポジトリには絶対に含めないでください。
モードを選ぶ
- Off:読者は Crowment でのみログインします。
- Optional:あなたのサイトにログインしている読者は自動的にログインします。Crowment でのログインも引き続き利用できます。
- Required:あなたのサイトのアカウントだけがログインできます。既存のコメントとセッションはそのまま残ります。
ログインページとログアウトページを設定する(任意)
どちらもウィジェットのドメイン上にある必要があります。ウィジェットのログインボタンは、読者が元いたページを示す return_to パラメータ付きで、あなたのログインページに読者を送ります。リダイレクトする前に、return_to が自分のサイトを指していることを確認してください。
2. サーバーでトークンに署名する
読者がログインしているページが表示されるたびに、HS256 とシークレットで JWT に署名します。
| Claim | 必須 | 値 |
|---|---|---|
sub | はい | あなたのサイトでの読者のユーザー ID。文字列で、128 文字以内。変わることも再利用されることもない ID を使ってください。メールアドレスは適しません。 |
name | はい | コメントの横に表示される名前。100 文字以内。HTML は除去されます。 |
avatar | いいえ | https:// の画像 URL。それ以外は無視されます。 |
badges | いいえ | 読者が持つバッジのキー(例: ["vip"])。Badges で設定した SSO キーと一致させます。省略するとバッジは変わりません。[] はこの方法で付与されたバッジを外します。ダッシュボードで付与したバッジは残ります。 |
aud | はい | ウィジェットのアプリキー。埋め込みコードと完全に同じ値です。 |
iat、exp | はい | 発行時刻と有効期限(秒)。トークンの有効期間は最大 1 時間です。1 分までの時計のずれは許容されます。 |
email クレームは不要で、使われません。Crowment がメールアドレスでアカウントを照合することはありません。
// npm install jsonwebtoken
import jwt from 'jsonwebtoken'
const ssoToken = jwt.sign(
{ sub: String(user.id), name: user.displayName, avatar: user.avatarUrl },
process.env.CROWMENT_SSO_SECRET,
{ algorithm: 'HS256', audience: CROWMENT_APP_KEY, expiresIn: '1h' },
)
// composer require firebase/php-jwt
use Firebase\JWT\JWT;
$now = time();
$ssoToken = JWT::encode([
'sub' => (string) $user->id,
'name' => $user->display_name,
'avatar' => $user->avatar_url,
'aud' => CROWMENT_APP_KEY,
'iat' => $now,
'exp' => $now + 3600,
], getenv('CROWMENT_SSO_SECRET'), 'HS256');
# pip install pyjwt
import os
from datetime import datetime, timedelta, timezone
import jwt
now = datetime.now(timezone.utc)
sso_token = jwt.encode(
{
"sub": str(user.id),
"name": user.display_name,
"avatar": user.avatar_url,
"aud": CROWMENT_APP_KEY,
"iat": now,
"exp": now + timedelta(hours=1),
},
os.environ["CROWMENT_SSO_SECRET"],
algorithm="HS256",
)
// go get github.com/golang-jwt/jwt/v5
now := time.Now()
ssoToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": strconv.FormatInt(user.ID, 10),
"name": user.DisplayName,
"avatar": user.AvatarURL,
"aud": crowmentAppKey,
"iat": now.Unix(),
"exp": now.Add(time.Hour).Unix(),
}).SignedString([]byte(os.Getenv("CROWMENT_SSO_SECRET")))
3. ウィジェットに渡す
サーバーでレンダリングするページでは、トークンを埋め込みコードに入れます。
<script defer src="https://embeds.crowment.com/comments.js"></script>
<!-- signed in: pass the token your backend signed for this page view -->
<crow-comments app-key="YOUR_APP_KEY" sso-token="SIGNED_TOKEN"></crow-comments>
<!-- signed out: leave sso-token out -->
<crow-comments app-key="YOUR_APP_KEY"></crow-comments>
読者がログインしているすべてのページでトークンを渡してください。トークンのないページは読者がログアウトしたことをウィジェットに伝え、ウィジェットもコメントからログアウトさせます。ページを移動するたびにログインし直すわけではありません。ウィジェットはセッションを保持し、読者が変わったときだけ新しいトークンと交換します。
シングルページアプリでは、ログイン状態の変化に合わせて属性を設定・削除し、ウィジェットのイベントを受け取ります。
const widget = document.querySelector('crow-comments')
// after your site signs the reader in
widget.setAttribute('sso-token', await fetchCrowmentToken())
// after your site signs the reader out
widget.removeAttribute('sso-token')
// the token expired while the page was open: hand over a fresh one
widget.addEventListener('crow:sso-expired', async () => {
widget.setAttribute('sso-token', await fetchCrowmentToken())
})
// Required mode without a sign-in page: show your own login
widget.addEventListener('crow:login-required', () => openLoginDialog())
crow:sso-expired:ウィジェットが読者をログインさせようとしたものの、トークンの有効期限が切れていました。新しいトークンを設定してください。crow:login-required:Required モードでログインページが設定されておらず、読者がログインボタンを押しました。
あなたのサイト経由でログインした読者は、あなたのサイトでログアウトします。ログアウトページがない場合、次のページでトークンを渡さなければいずれにせよログアウトされるため、ウィジェットは自身のログアウトボタンを表示しません。
4. アカウントの連携
すでに Crowment アカウントを持っている読者は、コメント欄でアバターの横にあるリンクアイコンからアカウントを連携できます。ポップアップで Crowment にログインし、Crowment のページで確認します。以降、あなたのサイトはその読者を Crowment アカウントでログインさせ、サイトのアカウントで書いたコメントはそのアカウントに移ります。
連携により、あなたのサイトはあなた自身のページ上でのみ、その読者を Crowment アカウントでログインさせられるようになります。確認ページでもそのことが読者に伝えられます。読者は User Console の Profile → Linked sites からいつでも連携を解除でき、コメントは読者のもとに残ります。SSO 設定の Account linking で連携をオフにすることもできます。
トークンの名前とアバターは、ログインのたびに読者のコメント用プロフィールを更新します。連携した Crowment アカウントは、自分の名前とアバターを保ちます。
セキュリティ
- シークレットはサーバーに保管してください。シークレットを持つ人は、あなたのサイトで誰にでもなりすまして読者をログインさせられます。
- 漏えいした場合は Replace secret を選択します。古いシークレットで署名されたトークンはすぐに使えなくなるため、新しいシークレットを直ちにデプロイしてください。すでにコメントにログインしている読者は、ログインしたままです。
- トークンは現在のリクエストの読者に対してだけ署名し、有効期間は短くしてください。
トラブルシューティング
| 症状 | 考えられる原因 |
|---|---|
invalid_sso_token | シークレットが間違っている、または置き換え済み、aud がアプリキーと異なる、HS256 以外のアルゴリズム、sub を数値で送信、name が空、有効期間が 1 時間を超えている。 |
sso_token_expired | トークンの exp を過ぎている、またはサーバーの時計がずれている。ページ表示ごとに新しいトークンに署名してください。 |
| SSO is not enabled for this app | モードが Off になっている、またはシークレットが作成されていない。 |
| Request origin does not match | ページがウィジェットのドメインまたはそのサブドメイン上にない。 |