Keycloakでトークンイントロスペクションが失敗することがある現象の原因

プログラミング
この記事は約5分で読めます。

はじめに

Keycloakは、オープンソースのシングルサインオン(SSO)ソフトウェアです。OpenID ConnectとOAuth2.0のプロトコルをサポートし、アプリケーションに対して認証および認可を行うための機能を提供します。

トークンイントロスペクションは、OpenID ConnectおよびOAuth2.0のプロトコルで使用されるトークンを検証する仕様です。この仕様は、トークンが現在アクティブかどうかなどをトークンのメタデータをもとに判別し、そのトークンに関するJSONドキュメントを返します。

RFC ft-ietf-oauth-introspection: OAuth 2.0 Token Introspection
This specification defines a method for a protected resource to query an OAuth 2.0 authorization server to determine the active state of an OAuth 2.0 token and ...

Keycloakにもこのトークンイントロスペクションのエンドポイントが提供されています。わたしの環境(V21.0)では、Realm settings → GeneralにあるOpenID Endpoint Configurationというリンクから、トークンイントロスペクションエンドポイントのURLを確認することができます。

リンク先でエンドポイントのURLが確認できる

Postmanでアクセストークンを検証する

DockerでKeycloakを立て、API開発ツールであるPostmanを使ってアクセストークンの取得と検証を行いました。ポート番号を18080にマッピングしたため、localhost:18080がベースのURLになります。

結果は無事成功、アクセストークンのメタデータを取得することができました。

アプリケーションで検証してみると

続いて、Dockerで立てたNode.jsのWebアプリケーションでアクセストークンの検証を行いました。サンプルコードが以下になります。なお、${authSettings.baseUrl}の部分には、http://keycloak:8080/realms/masterが入ります。

export const tokenIntrospection = (
  req: Request,
  _: Response,
  next: NextFunction
) => {
  (async () => {
    // ヘッダーからトークンを取得
    const authorizationHeader = req.headers.authorization;
    if (
      !authorizationHeader ||
      authorizationHeader.startsWith("Bearer ") === false
    )
      throw new Exception("トークンが見つかりません", 401);

    const token = authorizationHeader.replace("Bearer ", "");

    // トークンを認証サーバーに送信し、有効かどうかを確認
    const authSettings = config.authSettings;
    const response = await axios.post(
      `${authSettings.baseUrl}/protocol/openid-connect/token/introspect`,
      {
        token,
        client_id: authSettings.clientId,
        client_secret: authSettings.clientSecret,
        token_hint: "access_token",
      },
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        },
      }
    );

    const data = response.data;
    if (data.active === false) throw new Exception("トークンが無効です", 401);

    // 有効なトークンであれば、ユーザー情報をリクエストに付与
    req.user = {
      id: data.sub,
    };

    next();
  })().catch(next);
};

アクセストークンを取得するエンドポイントをNode.jsでまだ実装していなかったため、トークンはPostmanで取得し、その検証はNode.js側で行ってみました。

結果は失敗、常にトークンが非アクティブと判定されました。

コンテナからでは検証できない?

わたしはコンテナからだと検証できないのでは、と仮設を立て、Node.jsのアプリケーションをローカルで動かしてみました。${authSettings.baseUrl}のホスト名の部分をlocalhost:18080に変更し、再度Postmanでアクセストークンを取得、ローカルで動いているNode.jsで検証を行いました。

結果は成功、Postmanで取得・検証した時と同じようにトークンのメタデータを取得することができました。

本当にコンテナからでは検証できないのか

本当にコンテナからでは検証できないのか確認するために、コンテナ内でアクセストークンを取得し、Postman側とコンテナのNode.js側それぞれで検証を行いました。

結果は、Postman側では失敗し、コンテナのNode.js側では成功しました。

結論

上記の実験結果から、どうやらアクセストークンを取得した時と同じURLで検証を行う必要があるようです。この仕様に関して、以下の投稿を発見しました。

keycloak token introspection always fails with {"active":false}
I'm kind of desesperate to make this keycloak work. I can authenticate but for some reason, my token introspection always fail. For example if I try to authenti...

これによると、トークンの生成に使用したのと同じ認証サーバーのURLで検証を行う必要があるそうです。

トークンイントロスペクションが常に失敗してしまう場合は、今回のようにURLが異なっていることが原因なのかもしれません。

まとめ

今回は、同じアクセストークンを使用しても検証に失敗してしまう現象の原因についてまとめました。

タイトルとURLをコピーしました