はじめに
サイト間シングルサインオン(SSO)で、送り出す側が署名付きのハンドオフトークンを発行するところまでは前の記事で解説しました。この記事はその続きで、トークンを受け取った側のサイトが何をしているのか、という話です。
受入側の窓口にトークンが届いてから、ユーザーの画面にログイン済みのページが表示されるまで。この間には「検証する・ログイン状態を作る・元の目的地へ戻す」という3段階があります。ユーザーからは一瞬に見えますが、安全性も体験の良し悪しも、ほとんどこの受入側の作り込みで決まります。
この記事では、その3段階で何を確認し、失敗したときにどう振る舞わせたのかを順に説明します。読み終わる頃には、SSOが「ログインをやり直す仕組み」ではないことが分かるはずです。
コールバックが受け取るもの
トークンを受け取る専用の窓口
どのサイトにも、ハンドオフトークンを受け取るための専用URL(コールバックURL)を用意してあります。送出側は、このURLにトークンを付けてユーザーをリダイレクトさせます。
ログイン済みユーザーの移動に合わせてトークンを発行
トークンを受け取り、検証してからログイン状態を作る
図が示しているのは、受け渡しはブラウザのリダイレクト(別のURLへ自動的に移動させること)を使って行われる、ということです。サイト同士が裏で直接やり取りするのではなく、ユーザーのブラウザが運び役になります。
この窓口はインターネットに公開されているので、誰でもトークンらしき文字列を付けてアクセスできます。届いたものを無条件に信じない、という前提から設計を始めました。
戻り先の情報も一緒に受け取る
コールバックには、トークンに加えて「ログイン状態を作ったあと、どのページへ送るか」という情報も渡されます。ユーザーは移動先サイトの特定の商品ページを見たくてリンクを踏んだかもしれません。その意図をそのまま引き継ぎたいからです。
これが無いと、どのリンクを踏んでもトップページに着いてしまい、探し直す手間が発生します。SSOで再ログインの手間を消しても、そこで別の手間が生まれたら意味がない。戻り先を持ち回るのは、体験をつなげるための仕掛けです。
戻り先も検証の対象にする
戻り先の情報は、悪意のある外部URLに書き換えられていないかを確認します。書き換えられたまま素直にリダイレクトすると、自サイトを経由して見知らぬサイトへユーザーを飛ばしてしまうことになります。
対策は単純で、戻り先は自サイト内のパスに限る、という制限をかけるだけです。外部のURLが指定されていたら無視して、あらかじめ決めた既定のページへ送ります。細かい話ですが、こういう確認の積み重ねが効いてきます。
届いたトークンを検証する
3つのチェックを順番に通す
受け取ったトークンは、ログイン状態を作る前に必ず検証します。トークン側に仕込んだ「署名・有効期限・使い捨て」を、ここで順番に確かめていく形です。
共有している鍵で署名を計算し直し、中身が書き換えられていないかを確かめる
発行時刻から数十秒の期限を過ぎていないかを確かめる。切れていれば拒否
ワンタイムIDが未使用かを確かめる。使用済みなら拒否し、通ったらその場で使用済みに記録
自サイト宛に発行されたトークンかを確かめる。別サイト宛なら拒否
図が示しているのは、4つのうち1つでも通らなければ、その先には進まないということです。
順番にも意味があります。まず署名で中身が信用できるかを判定し、信用できると分かってから中身に書かれた期限や宛先を読む。署名を確かめる前に中身を信じて処理を進めると、書き換えられた値をそのまま使ってしまいます。「疑わしきは拒否」を通したうえで、確認の順番まで決めておく、ということですね。
検証に失敗したときの振る舞い
失敗したらSSOを打ち切り、通常のログインへ
トークンが期限切れだったり不正だったりした場合、無理にSSOを成立させようとしません。素直に引き継ぎをあきらめ、通常のログイン画面へ案内します。ユーザーへの表示は「もう一度お試しください」程度にとどめ、内部の失敗理由は見せないのが安全です。
失敗理由を画面に細かく出すと、攻撃を試している相手に「どこまで通ったか」を教えることになります。署名で落ちたのか期限で落ちたのかが分かれば、次の試行の精度が上がってしまう。
一方で、運用する側は理由が分からないと調査できません。だから詳細はサーバー側のログにだけ残し、画面には出さない、と切り分けました。ここは体験の親切さとセキュリティが少しぶつかる部分で、迷ったら安全側に寄せています。
ログイン状態を作って元の場所へ戻す
受入側は自分のセッションを新しく作る
検証を通過したら、受入側は自分自身のセッション(ログイン状態を保持する情報)を新しく作ります。送出側のセッションをそのまま借りるのではなく、トークンで受け取ったユーザー識別子をもとに、受入側で改めてログイン状態を組み立てる形です。
なぜ作り直すのか
Cookie(ブラウザ側に保存される小さな覚え書き)は、発行したドメインでしか読めません。だから受入側は、自分のドメインで有効なCookieを自前で発行する必要があります。トークンはあくまで引き継ぎのきっかけであって、セッションそのものではない、というのがポイントです。
こうして作られたセッションは、以降そのサイト内では通常のログインとまったく同じように働きます。裏側の商品・会員データは全サイト共通なので、ポイント残高も会員価格もそのまま反映されます。ユーザーから見れば、最初からそのサイトにログインしていたのと区別がつきません。
トークンはここで役目を終える
セッションが作られた時点で、トークンの役目は完全に終わります。使用済みとして記録され、二度と通らない状態になります。
この「使い捨てて終わり」という性質があるので、トークンが後から履歴やログに残っていても、それ自体でログインし直されることはありません。長く生きる情報を作らない、というのが受入側の処理でも一貫している考え方です。
元の目的地へリダイレクトする
セッションができたら、最初に受け取っておいた戻り先へユーザーを送り届けます。ここでようやく、「リンクを踏んだら、ログイン済みの状態で見たかったページが開いた」という体験が完成します。
裏側では検証・セッション発行・リダイレクトと何段階も走っているのに、体感としては一瞬です。気づかれないことがそのまま成功の証、という珍しいタイプの機能だと思います。
受入側の動きを1枚に
表が示しているのは、受入側の仕事が「受け取る・疑う・作る・戻す」の4段階に整理できる、ということです。この4段階はどのサイトでも同じなので、サイトを増やしても処理の形は変わりません。
まとめ
受入側の役割は、届いたトークンを疑い、正しければ自分のサイトのログイン状態を作り、元の場所へ戻すことでした。要点を整理します。
- 無条件に信じない:公開された窓口である以上、検証から始める
- 確認は4つ、順番も決める:署名を確かめてから、期限・使い捨て・宛先を読む
- セッションは作り直す:Cookieがドメインをまたげないため、自分のドメインで発行する
- 戻り先も検証する:外部URLへの飛ばし先にされないよう自サイト内に限定する
- 失敗は穏やかに:SSOを打ち切って通常ログインへ誘導し、理由は画面に出さない
失敗時の分岐は数え上げると意外に多く、期限切れ・署名不一致・使用済み・宛先違い・戻り先不正のそれぞれで挙動を決める必要がありました。この洗い出しは、AIエージェントに「この条件で来たときどうなるか」を列挙してもらいながら潰していった部分です。
トークン自体の設計は「署名付きハンドオフトークンの仕組み」に、SSO全体の位置づけは「複数ECサイト間のシングルサインオン設計」にまとめています。窓口のURLや許可サイトの管理方法は「サイト追加に耐えるSSO設定管理」をご覧ください。