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

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 を削除します。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(安全な HTTPS 専用 Cookie と JavaScript でアクセス可能な通常の Cookie)を必要とすることを検討してください。ユーザーがオフラインでログアウトしようとしている場合は、JavaScript Cookie を削除し、可能であれば他のクリーンアップ オペレーションを続行できます。サービス ワーカーを使用している場合は、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 など)を削除する推奨事項に従っている場合は、機密データが前後キャッシュに保持されることを心配する必要はありません。実際、バックフォワード キャッシュ機能は、次のいずれかのシグナルを検出すると、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.
        // ...
      }
    }
    

まとめ

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