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

Kenji Baheux
Kenji Baheux

ログアウト

ユーザーがウェブサイトからログアウトすると、パーソナライズされたユーザー エクスペリエンスから完全に脱却したいという希望を伝えることになります。そのため、可能な限りユーザーのメンタルモデルに忠実に従うことが重要です。たとえば、適切なログアウト エクスペリエンスでは、ユーザーがログアウトを決定する前に開いた可能性があるタブも考慮される必要があります。

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

考慮すべきポイント

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

  • 明確で一貫性のあるログアウト UX: ウェブサイト全体で、簡単に識別してアクセスできるログアウト ボタンまたはリンクを明確かつ一貫して表示します。メニューやサブページなど、わかりにくい場所で、あいまいなラベルを使用したり、ログアウト機能を非表示にしたりしないでください。
  • 確認プロンプト: ログアウト プロセスを完了する前に確認プロンプトを実装します。これにより、ユーザーが誤ってログアウトするのを防ぐことができます。また、ユーザーは本当にログアウトする必要があるのか(たとえば、安全なパスワードやその他の認証メカニズムを使用してデバイスを徹底的にロックするなど)を再考できるようになります。
  • 複数のタブに対応する: ユーザーが同じウェブサイトの複数のページを別のタブで開いた場合は、1 つのタブからログアウトすると、そのウェブサイトで開いている他のすべてのタブも更新されるようにします。
  • 安全なランディング ページにリダイレクトする: ログアウト後に、ログインしていないことを明確に示す安全なランディング ページにユーザーをリダイレクトします。個人情報を含むページにユーザーをリダイレクトすることは避けてください。同様に、他のタブにもログイン状態が反映されていないことを確認します。また、攻撃者が悪用できるオープン リダイレクトを構築していないことを確認してください。
  • セッションのクリーンアップ: ユーザーがログアウトしたら、ユーザーのセッションに関連する機密性の高いユーザー セッション データ、Cookie、一時ファイルを完全に削除します。これにより、ユーザー情報やアカウント アクティビティへの不正アクセスを防ぎ、ブラウザがさまざまなキャッシュ、特にバックフォワード キャッシュから機密情報を含むページを復元するのを防止できます。
  • エラー処理とフィードバック: ログアウト時に問題が発生した場合に、明確なエラー メッセージまたはフィードバックをユーザーに提供します。ログアウト プロセスが失敗した場合に、潜在的なセキュリティ リスクやデータ漏洩について通知する。
  • ユーザー補助に関する考慮事項: スクリーン リーダーやキーボード操作などの支援技術を使用しているユーザーを含め、障がいのあるユーザーがログアウト メカニズムにアクセスできるようにします。
  • ブラウザ間の互換性: さまざまなブラウザやデバイスでログアウト機能をテストし、一貫して確実に動作することを確認します。
  • 継続的なモニタリングと更新: ログアウト プロセスを定期的にモニタリングして、潜在的な脆弱性やセキュリティの抜け穴がないか確認します。特定された問題に対処するために、アップデートとパッチを適時に実装します。
  • 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 や機密データを含むすべての Cookie を消去します。expires の値を遠い過去の日付に設定し、Cookie の値を空の文字列に設定します。

<ph type="x-smartling-placeholder">
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 を消去し、可能であれば他のクリーンアップ処理を続行できます。Service Worker がある場合は、Background Fetch 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 via 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 ヘッダーで配信される同一オリジン ページを削除します。 <ph type="x-smartling-placeholder">
      </ph>
    • 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.
        // ...
      }
    }
    

まとめ

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