ブログ

【AWS】Amazon Cognitoのログアウトで消えるもの、残るもの

この記事をSNSでシェア!

はじめに

お客様との打ち合わせで、Amazon Cognito(以下、Cognito)を使った認証・認可の設計について相談を受けました。やりたいことは、一度ログインした利用者が別のアプリへ移ったときに、ログインし直さずに使えるようにすることでした。

Cognito 自体はこれまで何度も触っています。ユーザープールを作り、アプリケーションクライアントやコールバック URL を設定して、ログインを動かすところまではできます。

ただ、「ログイン後の状態はどこに残るのか」「ログアウトすると何が消えるのか」と聞かれて、うまく答えられませんでした。設定画面のどこにも書かれていない話で、動かせることと説明できることは別だと痛感しました。

公式ドキュメントも確認したものの、読んだだけでは分かったつもりで終わりそうでした。そこで、実際に検証環境を構築し、ブラウザの挙動や通信の状態を直接チェックしてみました。本記事は、その検証によって得られた結果をまとめたものです。

なお、検証は2026年9月時点の Cognito で実施しています。

対象読者

  • Cognito で認証基盤を設計・実装するエンジニア
  • SSO やログアウトの要件を詰めている方
  • 「ログアウト後もアクセスできてしまう」理由を理解したい方

本記事で取り扱わないこと

  • 外部 IdP(Google 等)のセッション終了処理
  • クライアントシークレットやセルフサインアップなど、Cognito 設定項目の詳細
  • MFA の設定

前提:認可コードとトークンは別物

ログアウトで何が消えるかを追うには、Cognito が「何を」発行しているかを先に押さえる必要があります。 ここでは、その理解に必要な範囲だけを扱います。

Cognito のコンソールは AWS 独自の呼び方をしている箇所があり、OIDC(OpenID Connect)の解説記事を読んでも画面と結びつきません。この記事で使うのは次の4つです。

OIDC / OAuth 2.0 の用語Cognito での呼ばれ方
Relying Party(クライアント)アプリケーションクライアント
Authorization Endpoint/oauth2/authorize
Token Endpoint/oauth2/token
Redirect URIコールバック URL

認可コードフローは6ステップで、トークンが手に入るのは最後です。

図1 認可コードフローのシーケンス図。アプリ、ブラウザ、Cognito 間の認可リクエストからトークン交換までの流れ

ログインボタンをクリックすると、ブラウザは Cognito のドメインにリダイレクトされます。その際に遷移する URL は以下の通りです(実際の値は伏せて記載しています)。

GET https://example-idp.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize
  ?response_type=code
  &client_id=(アプリケーションクライアントID)
  &redirect_uri=http://localhost:5174/callback
  &scope=openid+email+profile
  &state=(ランダム文字列)
  &nonce=(ランダム文字列)
  &code_challenge=(code_verifier の SHA-256 を Base64URL したもの)
  &code_challenge_method=S256

ここで押さえたいのは、redirect_uri に返ってくるのは認可コードであってトークンではないという点です。ログインが終わると、コールバック URL に code が付いて戻ってきます。それをトークンと交換します。

POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id=(アプリケーションクライアントID)
&code=(authorize で受け取った認可コード)
&redirect_uri=http://localhost:5174/callback
&code_verifier=(code_challenge の元になった文字列)

ここでアクセストークンとリフレッシュトークンが返ります。openid スコープを要求していれば、ID トークンも一緒に返ります。このコードとトークンの区別が、あとで prompt=none の挙動を理解するうえで重要なポイントとなります。

「ログイン済み」はどこにあるのか

結論として、ログイン状態は「managed login のブラウザセッション」と「各アプリが持つログイン状態」の2層に分かれています。

一度ログインすれば、しばらくは再入力なしで使えます。この「ログイン済み」という状態がどこに保持されているのかは、設定画面のどこにも書かれていません。そこで、実際に動かして確かめました。

検証環境:ホスト名を分けた2つのアプリ

今回の検証にあたり、以下の構成を準備しました。

  • アプリA(http://localhost:5174)とアプリB(http://127.0.0.1:5175)の2つ
  • どちらも同じユーザープールに、別々のアプリケーションクライアントとして登録
  • ユーザープールドメインの branding version は managed login

最後の1行は前提になります。ここで使う prompt パラメータは managed login でのみ利用でき、classic hosted UI では使えません。

片方を localhost、もう片方を 127.0.0.1 にしているのにも理由があります。Cookie の適用範囲はホスト名とパスで決まり、ポート番号は含まれません。localhost:5174 と localhost:5175 では、ポートが違ってもブラウザは同じ Cookie を送ってしまいます。localhost と 127.0.0.1 は同じマシンを指しますが、ホスト名としては別の文字列なので、Cookie が分かれます。

Cookieオリジン(CORS・localStorage)
ホスト名が違う分離される分離される
ポートだけ違う分離されない分離される

オリジンはポートを含むので、混同しやすいところです。実運用でサブドメインに分ける場合も、Cookie の Domain 属性の指定次第で共有されるかどうかが変わります。

画面なしで認可が通るか試す

アプリAにログインし、そのあとアプリBを開いて prompt=none を付けた認可リクエストを投げます。prompt=none は「ログイン画面を出さずに、有効なセッションがあればそのまま認可を続けてほしい」という指定です。

GET https://example-idp.auth.ap-northeast-1.amazoncognito.com/oauth2/authorize
  ?response_type=code
  &client_id=(アプリBのクライアントID)
  &redirect_uri=http://127.0.0.1:5175/callback
  &scope=openid+email+profile
  &prompt=none

紙面の都合で state、nonce、code_challenge を省略しています。実際には3つとも送り、検証しています。

結果は、追加のログイン画面なしで認可コードが返りました。prompt=none が成功しても、返るのはあくまでコードです。図1の5と6を経て、アプリBはアプリAとは別に発行された一組のトークンを、利用者の操作ゼロで手に入れました。

Cookie を共有していない2つのアプリの間で、なぜこれが成立するのか。効いているのはアプリ側の Cookie ではなく、Cognito のドメインが持つブラウザセッションです。ブラウザから見れば authorize へのアクセスはどちらも同じ Cognito ドメイン宛なので、そのドメインの Cookie が送られます。

図2 ログイン状態の2層構造(上層:managed login の Cookie、下層:各アプリのログイン状態)

この managed login のセッション Cookie は1時間有効です。そしてドキュメントに明記されているのですが、Cookie を使って再認証しても、有効期限は延長されません。最後に対話的にログインしてから1時間が過ぎれば、利用者はもう一度ログインすることになります。

ログアウトはどこまで効くのか

消す対象は4つあり、それぞれ別々に管理されています。ひとつの操作ですべてが消えることはありません。

ログイン状態が2層あるなら、ログアウトも1つでは済まないはずです。順に確認しました。

アプリだけログアウトした場合

まずアプリBだけをログアウトしました。ここでやったのは、アプリBが持つローカルなログイン状態を削除しただけです。トークンの失効も Cognito の /logout も実行していません。アプリBは未ログインになりましたが、アプリAはログイン済みのままです。そしてアプリBは、そのあと prompt=none でまた無画面でログインし直せました。下層を消しても、上層が残っているためです。

Cognito からログアウトした場合

次に Cognito 側からログアウトしました。この状態で prompt=none を投げると、Cognito は login_required を返します。上層が消えたので、ログイン画面なしで認可を続ける根拠がなくなったわけです。

図3 prompt=none による認可コード返却と login_required の分岐(managed login のブラウザセッション有無に依存)

このとき、ログアウトの戻り先ではアプリBが依然としてログイン済みでした。Cognito のログアウトは、各アプリのログイン状態までは終わらせません。

操作しなくても、時間で切れる場合

ここまでは利用者が操作してログアウトする場合を見てきました。操作しなくても、時間が経てば状態は変わります。

先に触れたとおり、managed login のセッション Cookie は1時間で期限切れになります。この時点で prompt=none は通らなくなり、次にログインするときは画面が出ます。

ただし切れるのは上の層だけです。時間切れは「Cognito からのログアウト」に近く、「全部のログアウト」ではありません。アプリが持っているログイン状態や発行済みのトークンが、Cookie の期限切れに連動して消えるわけではありません。

「ログアウト」を4つの対象と5つの操作に分解する

ここまでを踏まえて、「ログアウト」と呼ばれる操作を分解します。消す対象は、いまの2層に発行済みトークンと外部 IdP のセッションを足した4つ。それに対して操作は5つあります。

図4 ログアウトと呼ばれる5つの操作が、アプリのログイン状態、発行済みトークン、managed login の Cookie、外部 IdP のセッションのどれを消すのかを示す図

混ざりやすいのは、トークンの失効とブラウザセッションの削除が独立している点です。失効操作の対象は発行済みトークンで、Cookie を消すのは /logout です。逆に /logout を呼んでも、アプリが保持する ID・アクセス・リフレッシュトークンやサーバーセッションは消えません。

トークンの失効には注意点が2つあります。1つは、アプリケーションクライアントで失効機能を有効にしておく必要がある点です(新規作成時は既定で有効)。もう1つは、発行される JWT が自己完結型のため、独自 API で署名と exp しか見ていないと、失効させたはずのトークンが通ってしまう点です。

/logout は外部 IdP(Google など)のセッションまでは終了させません。Cognito からログアウトしても IdP 側にはログイン状態が残るので、再ログイン時にアカウント選択なしで入れてしまう場合があります。共用端末を想定するなら確認が要ります。なお、SAML IdP は SLO を設定すれば /logout から連鎖しますが、OIDC やソーシャル IdP は IdP ごとのサインアウト手順を別に用意することになります。RP-Initiated Logout の対応可否は提供元によって異なります。ここは今回検証していないため、これ以上の断定は避けます。

なお、記事中の結果は検証環境で確認した挙動です。すべての構成で同じになるとは限りません。ブラウザを分けた場合や、セッションが時間で失効する境界も今回は確認していません。

相談には、どう答えたのか

ここまで細かい話を書いてきましたが、お客様に同じ説明をしたわけではありません。

相談の中身は「別のアプリへ移っても、ログインし直さずに使えるようにしたい」というものでした。やりたいことを整理していく中でセッションの話が出てきて、私が伝えたのは「いまならこういう作りにするのがいい」という方向性です。トークンの種類や prompt=none の挙動を1つずつ説明したわけではありません。

ただ、その方向性を自分で納得して言うために、ここまでの検証が必要でした。詳しくない方に向けて要点だけまとめるなら、こうなります。

  • ログアウトボタンを押しても、すべてのログイン状態が消えるわけではない。消える対象は4つあり、操作ごとに効く範囲が違う
  • アプリ側でログアウトしても、Cognito 側にセッションが残っていれば、ログイン画面なしで入り直せる。逆に Cognito からログアウトしても、アプリが持っているトークンは自動では消えない
  • 時間切れとログアウトは別物。Cookie が1時間で切れるとログイン画面なしの認可は通らなくなるが、それは上の層の話で、アプリ側の状態を Cognito が消しに来るわけではない

「ログアウトしたい」という要件は、このうちどれを落としたいのかまで決めないと実装できません。そこを先に確認しておくと、あとで食い違いません。

まとめ

Cognito の「ログイン済み」は、managed login のブラウザセッションと、各アプリが管理するログイン状態に分かれています。片方を消しても、もう片方は残ります。別のアプリを開いたときに画面なしで認可が通るのも、両方が同じ Cognito ドメインのセッションに乗っているからです。

「ログアウトしたい」と言われたときは、どこまで落としたいのかを分けて確認する必要があります。図4の4つの対象は別々に管理されていて、実現方法も難易度も変わります。

設定画面を見ているだけでは、ここは分かりませんでした。手を動かして確かめて、ようやく腹落ちしました。同じところで詰まっている方の参考になれば幸いです。

関連記事

Cognito については弊社ブログに他にも記事があります。あわせてご覧ください。

参考

OpenID Connect Core 1.0

The redirect and authorization endpoint(AWS ドキュメント)

The token issuer endpoint(AWS ドキュメント)

User pool managed login(AWS ドキュメント)

The managed login sign-out endpoint(AWS ドキュメント)

Ending user sessions with token revocation(AWS ドキュメント)

投稿者プロフィール

西村 僚介
西村 僚介
2021年入社、BS事業部所属。
現在はAWS(Amazon Web Services)を活用したクラウドインフラの設計・構築から、バックエンド開発までを一貫して担当しています。日々進化するクラウド技術をキャッチアップし、常に「その時点でのベストプラクティス」をお客様に提案できるよう心掛けています。単に動くものを作るだけでなく、運用開始後の保守性やコストパフォーマンスまで見据えた、長く愛されるシステム作りを大切にしています。
この記事をSNSでシェア!