優れたログアウト エクスペリエンスの条件

Kenji Baheux
Kenji Baheux

ユーザーがウェブサイトからログアウトすると、パーソナライズされたユーザー エクスペリエンスから完全に抜け出す必要があることが伝わります。そのため、ユーザーのメンタルモデルにできるだけ忠実に準拠することが重要です。たとえば、適切なログアウト エクスペリエンスでは、ユーザーがログアウトする前に開いていたタブも考慮する必要があります。

優れたログアウト エクスペリエンスを実現する鍵は、ユーザー エクスペリエンスの視覚的側面と状態的側面で一貫性を保つことです。このガイドでは、注意すべき点や、ログアウト時のユーザー エクスペリエンスを向上させる方法について具体的なアドバイスを提供します。

考慮すべきポイント

ウェブサイトにログアウト機能を実装する際は、スムーズで安全かつ直感的なログアウト プロセスを実現するために、次の点に注意してください。

  • 明確で一貫性のあるログアウト UX: ウェブサイト全体で簡単に識別してアクセスできる、明確で一貫性のあるログアウト ボタンまたはリンクを提供します。あいまいなラベルを使用したり、ログアウト機能をわかりにくいメニューやサブページなどの直感的にわかりにくい場所に隠したりしないでください。
  • 確認プロンプト: ログアウト プロセスを完了する前に確認プロンプトを実装します。これにより、ユーザーが誤ってログアウトするのを防ぎ、ログアウトする必要があるかどうかを再検討できます。たとえば、デバイスを強力なパスワードやその他の認証メカニズムでロックしている場合などです。
  • 複数のタブを処理する: ユーザーが同じウェブサイトの複数のページを異なるタブで開いている場合、1 つのタブからログアウトすると、そのウェブサイトの他の開いているタブもすべて更新されるようにします。
  • 安全なランディング ページにリダイレクトする: ログアウトが成功したら、ユーザーがログインしていないことを明確に示す安全なランディング ページにリダイレクトします。パーソナライズされた情報を含むページにユーザーをリダイレクトすることは避けてください。同様に、他のタブにもログイン状態が反映されなくなったことを確認します。また、攻撃者が悪用できるオープン リダイレクトを構築していないことを確認してください。
  • セッションのクリーンアップ: ユーザーがログアウトしたら、ユーザーのセッションに関連付けられている機密性の高いユーザー セッション データ、Cookie、一時ファイルを完全に削除します。これにより、ユーザー情報やアカウント アクティビティへの不正アクセスを防ぎ、ブラウザがさまざまなキャッシュ(特に back/forward cache)から機密情報を含むページを復元することも防ぎます。
  • エラー処理とフィードバック: ユーザーがログアウトする際に問題が発生した場合は、明確なエラー メッセージまたはフィードバックをユーザーに提供します。ログアウト プロセスが失敗した場合に発生する可能性のあるセキュリティ リスクやデータ漏洩についてお客様に伝えます。
  • アクセシビリティに関する考慮事項: ログアウト メカニズムが、スクリーン リーダーやキーボード ナビゲーションなどの支援技術を使用しているユーザーを含む、障がいのあるユーザーにとってアクセス可能であることを確認します。
  • クロスブラウザの互換性: さまざまなブラウザやデバイスでログアウト機能をテストし、一貫して確実に動作することを確認します。
  • 継続的なモニタリングと更新: ログアウト プロセスを定期的にモニタリングして、潜在的な脆弱性やセキュリティの抜け穴がないか確認します。特定された問題に対処するために、タイムリーな更新とパッチを実装します。
  • ID 連携: ユーザーがフェデレーション ID を使用してログインしている場合は、ID プロバイダからのログアウトもサポートされているか、必要かどうかを確認します。また、ID プロバイダが自動ログインをサポートしている場合は、自動ログインを無効にすることを忘れないでください。

すること

  • ログアウト フロー(またはその他のアクセス取り消しフロー)の一部としてサーバーで Cookie を無効にする場合は、ユーザーのデバイスの Cookie も削除してください。
  • ユーザーのデバイスに保存されている可能性のある機密データ(Cookie、localStoragesessionStorageindexedDBCacheStorage、その他のローカル データストア)をクリーンアップします。
  • 機密データを含むリソース(特に HTML ドキュメント)が Cache-control: no-store HTTP ヘッダーとともに返されるようにします。これにより、ブラウザはこれらのリソースを永続ストレージ(ディスクなど)に保存しません。同様に、機密データを返す XHR/fetch 呼び出しでも、キャッシュ保存を防ぐために Cache-Control: no-store HTTP ヘッダーを設定する必要があります。
  • ユーザーのデバイスで開いているタブが、サーバーサイドでのアクセス権の取り消しによって最新の状態になっていることを確認します。

ログアウト時の機密データのクリーンアップ

ログアウトする際は、エフェメラル データとローカルに保存された機密データをクリアすることを検討してください。機密データに重点を置くのは、このユーザーが戻ってくる可能性が高く、すべてを消去するとユーザー エクスペリエンスが大幅に低下するためです。たとえば、ローカルに保存されたすべてのデータを削除すると、ユーザーは Cookie の同意を求めるメッセージに再度同意し、ウェブサイトに初めてアクセスしたときと同じように他のプロセスを完了する必要があります。

Cookie をクリーンアップする方法

ログアウト ステータスを確認するページのレスポンスで、Set-Cookie HTTP ヘッダーを付加して、機密データに関連する、または機密データを含むすべての Cookie をクリアします。expires の値を遠い過去の日付に設定し、念のため Cookie の値を空の文字列に設定します。

Set-Cookie: sensitivecookie1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure
Set-Cookie: sensitivecookie2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; secure
...

オフラインのシナリオ

上記の方法は一般的なユースケースには十分ですが、ユーザーがオフラインで作業している場合は機能しません。ログイン状態を追跡するために、2 つの Cookie(1 つは安全な HTTPS 専用 Cookie、もう 1 つは JavaScript でアクセス可能な通常の Cookie)を必須にすることを検討してください。ユーザーがオフライン中にログアウトしようとしている場合は、JavaScript Cookie を削除し、可能であれば他のクリーンアップ オペレーションに進むことができます。サービス ワーカーがある場合は、バックグラウンド取得 API を利用して、後でユーザーがオンラインになったときにサーバー上の状態をクリアするリクエストを再試行することもできます。

ストレージをクリーンアップする方法

ログアウト状態を確認するページのレスポンスで、さまざまなデータストアから機密データをクリーンアップするように注意してください。

  • sessionStorage: ユーザーがウェブサイトとのセッションを終了するとクリアされますが、ユーザーがウェブサイトで開いたすべてのタブを閉じ忘れた場合に備えて、ユーザーがログアウトしたときに機密データを事前にクリーンアップすることを検討してください。

    // Remove sensitive data from sessionStorage
    sessionStorage.removeItem('sensitiveSessionData1');
    // ...
    
    // Or if everything in sessionStorage is sensitive, clear it all
    sessionStorage.clear();
    
  • localStorageindexedDBCache/Service Worker API: ユーザーがログアウトしたときに、これらの API を使用して保存した可能性のある機密データをクリーンアップします。このようなデータはセッション間で保持されるためです。

    // Remove sensitive data from localStorage:
    localStorage.removeItem('sensitiveData1');
    // ...
    
    // Or if everything in localStorage is sensitive, clear it all:
    localStorage.clear();
    
    // Delete sensitive object stores in indexedDB:
    const name = 'exampleDB';
    const version = 1;
    const request = indexedDB.open(name, version);
    
    request.onsuccess = (event) => {
      const db = request.result;
      db.deleteObjectStore('sensitiveStore1');
      db.deleteObjectStore('sensitiveStore2');
    
      // ...
    
      db.close();
    }
    
    // Delete sensitive resources stored with the Cache API:
    caches.open('cacheV1').then((cache) => {
      await cache.delete("/personal/profile.png");
    
      // ...
    }
    
    // Or better yet, clear a cache bucket that contains sensitive resources:
    caches.delete('personalizedV1');
    

キャッシュをクリーンアップする方法

  • HTTP キャッシュ: 機密性の高いデータを含むリソースに Cache-control: no-store を設定している限り、HTTP キャッシュに機密性の高いデータが保持されることはありません。
  • 戻る/進むキャッシュ: 同様に、Cache-control: no-store に関する推奨事項と、ユーザーがログアウトしたときに機密性の高い Cookie(認証関連の HTTPS 専用の安全な Cookie など)をクリアすることに関する推奨事項に従っていれば、戻る/進むキャッシュに機密データが保持されることを心配する必要はありません。実際、バックフォワード キャッシュ機能は、次のシグナルを 1 つ以上検出した場合、Cache-control: no-store HTTP ヘッダーで配信された同一オリジン ページをキャッシュから削除します。
    • 1 つ以上のセキュアな HTTPS 専用 Cookie が変更または削除されました。
    • ページによって発行された XHR/fetch 呼び出しの 1 つ以上のレスポンスに、Cache-control: no-store HTTP ヘッダーが含まれていました。

タブ間の一貫したユーザー エクスペリエンス

ユーザーは、ログアウトする前にウェブサイトのタブを複数開いている可能性があります。その頃には、他のタブや他のブラウザ ウィンドウのことは忘れているかもしれません。関連するタブとウィンドウをすべて閉じることをユーザーに頼るのは避けることをおすすめします。代わりに、ユーザーのログイン状態がタブ間で一貫していることを確認することで、事前対応を行います。

方法

タブ間でログイン状態を維持するには、pageshow/pagehide イベントと Broadcast Channel API を組み合わせて使用することを検討してください。

  • pageshow イベント: 永続化された pageshow で、ユーザーのログイン ステータスを確認し、ユーザーがログインしていない場合は、機密データ(またはページ全体)をクリアします。pageshow イベントは、バック/フォワード ナビゲーションから復元されたときにページが最初にレンダリングされる前にトリガーされるため、ログイン状態のチェックでページを機密情報を含まない状態にリセットできます。

    window.addEventListener('pageshow', (event) => {
      if (event.persisted && !document.cookie.match(/my-cookie)) {
        // The user has logged out.
        // Force a reload, or otherwise clear sensitive information right away.
        body.innerHTML = '';
        location.reload();
      }
    });
    
  • Broadcast Channel API: この API を使用して、タブやウィンドウ間でログイン状態の変更を伝達します。ユーザーがログアウトしている場合は、すべての機密データをクリアするか、機密データを含むすべてのタブとウィンドウでログアウト ページにリダイレクトします。

    // Upon logout, broadcast new login state so that other tabs can clean up too:
    const bc = new BroadcastChannel('login-state');
    bc.postMessage('logged out');
    
    // [...]
    const bc = new BroadcastChannel('login-state');
    bc.onMessage = (msgevt) => {
      if (msgevt.data === 'logged out') {
        // Clean up, reload or navigate to the sign-out page.
        // ...
      }
    }
    

まとめ

このドキュメントのガイダンスに沿って、意図しないログアウトを防ぎ、ユーザーの個人情報を保護する優れたログアウト ユーザー エクスペリエンスを設計できます。