OIDCバックチャネルログアウト

Auth0は、エンタープライズプランサブスクリプションのすべてのテナントにおいて、OpenID Connectバックチャネルログアウト1.0仕様をサポートしています。

この仕様では、IDトークンとログアウトトークンに含まれるセッションID (sid)を活用して、バックチャネル通信を介してセッションの終了を調整します。異なるセッションIDは、テナント内のユーザーエージェントまたはデバイスの個々のセッションを表します。ログアウトトークンは、ログアウトするエンドユーザーとセッションを識別します。

バックチャネル通信

バックチャネルログアウトを使用するには、アプリケーションは、テナントサーバーからアクセス可能なバックチャネルログアウトURIを公開する必要があり、アプリケーションはログアウトトークンを含む要求を受信する必要があります。アプリケーションがこの要求を受信すると、トークン内のクレームと一致するローカルセッション状態をクリアする必要があります。

OIDCバックチャネルログアウト通信のトークン

アプリケーションは、バックチャネル経由で通信が実行されるときに、どのセッションを終了するかを決定するためにセッションクッキーに依存することはできません。むしろ、サービスはIDとログアウトトークンの共有セッション識別子(sid)に依存します。

エンドユーザーがログイン時にAuth0で正常に認証されると、認可サーバーはアクセストークンとIDトークンを発行します。ログアウトトークンは、ログアウトアクションやセッションの取り消しなどによってセッションが破棄されたときに生成されます。IDトークンとログアウトトークンの両方に、バックチャネルログアウトワークフローを容易にするためにアプリケーションが必要とするクレームが含まれています。クレームの詳細については、「JSON Webトークンクレーム」をお読みください。

Workflow for back-channel logout
  1. ログイン - ユーザー認証中に、Auth0テナントはIDトークンにsidを追加します。

  2. ログイン - アプリケーションは、受信したセッション識別子を独自のセッションストアに保存し、それをアプリケーション固有のセッションに関連付けます。

  3. ログアウト - IdPは、事前に登録されたログアウトコールバックURLを呼び出し、ログアウトトークンをこのエンドポイントに投稿します。トークンには、user_id (sub)とsidおよびその他のパラメーターが含まれます。

  4. ログアウト - アプリケーションのバックエンドは、OIDC仕様に従ってログアウトトークンを検証し、sidを抽出する必要があります。その後、バックエンドはこのトークンを使用して、識別子に関連付けられたセッションを見つけ、必要に応じてセッションを終了できます。

仕組み

サンプルユースケースでは、バックチャネルログアウトが複数のアプリケーションでどのように機能するかを示します。

Back-channel logout multiple app use case
  1. アプリケーションの構成中に、アプリケーションAはAuth0にバックチャネルログアウトURIを登録します。

  2. アプリケーションの構成中に、アプリケーションBはAuth0にバックチャネルログアウトURIを登録します。

  3. エンドユーザーのログイン時に、ユーザーはAuth0で認証してアプリケーションAにアクセスします。

  4. Auth0は、sidを含むIDトークンをアプリケーションAに送信します。詳細については、「IDトークンの構造」をお読みください。

  5. ユーザーはAuth0で認証してアプリケーションBにアクセスします。

  6. Auth0は同じsidを含むIDトークンをアプリケーションBに送信します。アプリケーションはセッション情報を保存しなければなりません。

  7. ログアウト中に、アプリケーションAまたは他のエンティティがフロントチャネルでログアウトを開始します。

  8. Auth0はセッションCookieを介してAuth0セッションレイヤーを終了します。

  9. Auth0はアプリケーションAのバックチャネルログアウトURIを呼び出し、ログアウトトークンを投稿します。

  10. アプリケーションAはログアウトトークンを検証し、セッションを終了します。

  11. Auth0はアプリケーションBのバックチャネルログアウトURIを呼び出し、ログアウトトークンを投稿します。

  12. アプリケーションBはログアウトトークンを検証し、セッションを終了します。

サンプルトークン

Auth0でログアウトトークンとして使用するには、アプリケーションでJWTを解析および検証できる必要があります。詳細については、「JSON Webトークンを検証する」をお読みください。

アプリケーションがトークンを検証してデコードすると、コンテンツは以下の例のようになります。

{
  "iss": "https://artex-dev.eu.auth0.com/",
  "sub": "auth0|602e93db83fa6f00749a23e6",
  "aud": "TuhNLv7ulXD3RfyLlSMbOvszzwJJFPpO",
  "iat": 1698160928,
  "exp": 1698161048,
  "jti": "44a91215-dfb4-4dfe-a1eb-fcafa911deba",
  "events": {
    "http://schemas.openid.net/event/backchannel-logout": {}
  },
  "trace_id": "81b336a94a4a5707",
  "sid": "375UIp_ID5mCTClIeBEHpXfGwq51tF_L"
}

Was this helpful?

/

Auth0 SDK

完全な例と製品コードは、express-openid-connect SDKバックチャネルログアウト例のセクションにすでに含まれています。

実装例

セッションストレージ

セッションストレージの例はNode (Express)で構築されており、Express OpenID Connectウェブアプリサンプルに基づいています。

アプリケーションセッションタブで、ログアウトトークンを受信するように構成したルートを公開します。トークンを検証し、ユーザーセッションを終了します。

routes/index.js

const express = require('express');
const router = express.Router();
const { requiresAuth } = require('express-openid-connect');

// middleware to validate the logout token
const requiresValidLogoutToken = require('../middlewares/validateLogoutToken');

// helper function to delete user sessions
const deleteUserSessions = require('../utils/sessions');

// new route to receive backchannel logout tokens
// must be configured in the Application -> Sessions tab 
// in the Auth0 Management Dashboard
router.post(
  '/backchannel-logout',
  requiresValidLogoutToken,
  function (req, res, next) {
    // at this point the logout token is valid, checked by requiresValidLogoutToken middleware
    // you can access it from the request object: req.logoutToken

    // delete user session so the user gets logged out
    deleteUserSessions(
      req.app.locals.sessionStore,
      req.logoutToken.sub,
      req.logoutToken.sid
    );

    res.sendStatus(200);
  }
);

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'Auth0 Webapp sample Nodejs',
    isAuthenticated: req.oidc.isAuthenticated(),
    headline: process.env.APP_NAME,
    backgroundColor: process.env.BACKGROUND_COLOR,
    baseURL: process.env.BASE_URL,
  });
});

router.get('/profile', requiresAuth(), function (req, res, next) {
  res.render('profile', {
    userProfile: JSON.stringify(req.oidc.user, null, 2),
    title: 'Profile page',
    headline: process.env.APP_NAME,
    backgroundColor: process.env.BACKGROUND_COLOR,
    baseURL: process.env.BASE_URL,
  });
});

module.exports = router;

Was this helpful?

/

middlewares/validateLogoutToken.js

// This middleware validates the logout token as defined here:
// https://openid.net/specs/openid-connect-backchannel-1_0.html#Validation

const jose = require('jose');

async function requiresValidLogoutToken(req, res, next) {

  // get remote key set for token verification
  const JWKS = jose.createRemoteJWKSet(
    new URL(process.env.ISSUER_BASE_URL + '/.well-known/jwks.json')
  );

  const logoutToken = req.body.logout_token;

  if (!logoutToken) {
    res.status(400).send('Need logout token');
  }

  try {
    const { payload, protectedHeader } = await jose.jwtVerify(
      logoutToken,
      JWKS,
      {
        issuer: process.env.ISSUER_BASE_URL + '/',
        audience: process.env.CLIENT_ID,
        typ: 'JWT',
        maxTokenAge: '2 minutes',
      }
    );

    // Verify that the Logout token contains a sub claim, a sid claim, or both
    if (!payload.sub && !payload.sid) {
      res
        .status(400)
        .send(
          'Error: Logout token must contain either sub claim or sid claim, or both'
        );
    }

    // Verify that the logout token contains an events claim
    // whose value is a JSON object containing the member name http://schemas.openid.net/event/backchannel-logout
    if (!payload.events['http://schemas.openid.net/event/backchannel-logout']) {
      res
        .status(400)
        .send(
          'Error: Logout token must contain events claim with correct schema'
        );
    }

    // Verify that the Logout token does not contain a nonce claim.
    if (payload.nonce) {
      res
        .status(400)
        .send('Error: Logout token must not contain a nonce claim');
    }

    // attach valid logout token to request object
    req.logoutToken = payload;

    // token is valid, call next middleware
    next();
  } catch (error) {
    res.status(400).send(`Error:  ${error.message}`);
  }
}

module.exports = requiresValidLogoutToken;

Was this helpful?

/

ログアウトトークンストア

トークンストレージの一般的なアプローチは、セッションストアモデルの代替としてログアウトストアを定義することです。アプリケーションは、ログアウトトークンのコレクションを永続レベルで保持します。

アプリケーションが認証ステータスを確認する必要があるときは、ログアウトトークンストアを照会して、セッションがまだアクティブかどうかを確認します。ログアウトストアは、必要な情報のみを保持するために、古い情報を定期的にフラッシュします。

Logout Token Store

セキュリティに関する考慮事項

バックチャネルログアウトトークンはインターネット経由で配信されるため、それを受信するコールバックエンドポイントは、信頼性が高く安全な操作を確保するためにベストプラクティスに従う必要があります。以下の推奨事項リストは網羅的なものではなく、常に特定の展開および運用状況を考慮して、それに応じて適応する必要があります。以下のリストでは、バックチャネルログアウトトークンを処理するアプリはすべて「アプリ」と呼ばれています。

  • アプリは、ユーザーログイン時に受信したセッションID (sid クレーム)を保存し、後でバックチャネルログアウトトークンを受け取ったときに取得できるようにする必要があります。

  • アプリは、JWT検証のベストプラクティスに従って、受信したトークンを検証する必要があります。

  • アプリは、信頼できるテナントによって発行されたトークンのみを受け入れる必要があります。悪意のある人物が他のAuth0テナントによって発行されたトークンを送信しようとする可能性があります。このような試みは拒否しなければなりません。

  • アプリは、アプリが認識するsid値(セッションID)が含まれている場合にのみトークンを受け入れる必要があります。無効なセッションID(期限切れまたは認識されない)を含むトークンは拒否する必要があります。

  • アプリはコールバックエンドポイントをTLS経由でのみ公開する必要があります。暗号化されていない通信チャネルは許可されません。

  • アプリは、公開されている送信IPアドレスのリストからの要求のみを受け入れることをお勧めします。

  • アプリは、監視、ログ記録、レート制限に関する一般的なベスト プラクティスに従うことが推奨されますが、これらの詳細はこのドキュメントの範囲外です。

  • アプリでは、古くなったセッションや期限切れのセッションを定期的にクリーンアップすることをお勧めします。

  • ログアウトトークンが常に正しいバックチャネルログアウトコールバックURLに配信されるようにするには、エンドポイントアドレスの変更をテナント構成と同期する必要があります。

もっと詳しく