Keycloak を用いたリフレッシュトークンローテーション検証方法 メモ


  • リフレッシュトークンローテーション機能をKeycloakを使って試してみたのでメモしておく。

リフレッシュトークンとは

  • アクセストークンの有効期限が切れた際に新しいアクセストークンを要求するために使用するトークン。
  • ネイティブアプリで使用されることが多い。

リフレッシュトークンローテーションとは

  • リフレッシュトークンを用いてアクセストークンを再発行する際に新しくリフレッシュトークンも再発行し、以前のリフレッシュトークンを無効化する機能。

Keycloakでの検証

設定

  • 「Realm Settings」->「Tokens」を選択
  • 「Revoke Refresh Token」を「ON」に設定
  • 「SSO Session Idle」を30に設定※デフォルトの1のままだと「token is not active」になったため。

動作確認

  1. リフレッシュトークン発行※認可コードグラントなどでリフレッシュトークンを発行する。

  2. 1で発行したリフレッシュトークンを使用してトークン再発行

    リクエスト

    POST /auth/realms/master/protocol/openid-connect/token HTTP/1.1
    Host: localhost:8080
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 784
    
    client_id=python-client&client_secret=a07...213d1&refresh_token=eyJhb...DZag&grant_type=refresh_token
    

    レスポンス

    {
        "access_token": "eyJhbGci...8eKzMz_kJ8yyvyQ",
        "expires_in": 60,
        "refresh_expires_in": 225,
        "refresh_token": "eyJhb...lOToY",
        "token_type": "Bearer",
        "id_token": "eyJhbGciOiJSUzI1NiI..._hWf40po2oMf7w",
        "not-before-policy": 0,
        "session_state": "db0815aa-3ac9-429c-9f59-f789bbe1953b",
        "scope": "openid profile email"
    }
    
  3. 1世代前のリフレッシュトークン無効化確認

    リクエスト※2のリクエストで指定したリフレッシュトークンを指定

    POST /auth/realms/master/protocol/openid-connect/token HTTP/1.1
    Host: localhost:8080
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 784
    
    client_id=python-client&client_secret=a07...213d1&refresh_token=eyJhb...DZag&grant_type=refresh_token
    

    レスポンス

    {
        "error": "invalid_grant",
        "error_description": "Token is not active"
    }
    

    ※2のリクエストで指定したリフレッシュトークンが無効化されていることがわかる。

  4. 新規発行リフレッシュトークンの有効性確認

    リクエスト※新規発行されたリフレッシュトークンを使用する。

    POST /auth/realms/master/protocol/openid-connect/token HTTP/1.1
    Host: localhost:8080
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 784
    
    client_id=python-client&client_secret=a07...213d1&refresh_token=eyJhb...lOToY&grant_type=refresh_token
    

    レスポンス

    {
        "access_token": "eyJhbGciOiJS....30BE04DYQ",
        "expires_in": 60,
        "refresh_expires_in": 497,
        "refresh_token": "eyJhbGciO...28ZoQyPY",
        "token_type": "Bearer",
        "id_token": "eyJhbGciOiJSUzI1NiIsIn...hdQ",
        "not-before-policy": 0,
        "session_state": "cd77a6ba-eda2-4ecc-a0bb-7248cd9695fa",
        "scope": "openid profile email"
    }
    

Keycloakリフレッシュトークン構造

  • ヘッダー部

    {
      "alg": "HS256",
      "typ": "JWT",
      "kid": "5361b8f...9ac2eb"
    }
    
  • ペイロード部

    {
      "exp": 1648350302,
      "iat": 1648350077,
      "jti": "09265c39...c6181bb52",
      "iss": "http://localhost:8080/auth/realms/master",
      "aud": "http://localhost:8080/auth/realms/master",
      "sub": "dc6f27d...dd61e64cd",
      "typ": "Refresh",
      "azp": "python-client",
      "session_state": "db0815...be1953b",
      "scope": "openid profile email",
      "sid": "db0815...e1953b"
    }