Wednesday, January 15, 2025

Refresh Token Flow - PingFederate

When using PingFederate with rolling tokens enabled, refresh tokens are managed to enhance security by invalidating previously issued refresh tokens after they are used. This ensures that only one valid refresh token exists at a time. 

Here's how you can use a refresh token with PingFederate in such a scenario:


Step-by-Step Guide

1. Understand Rolling Tokens

  • With rolling tokens enabled, every time a refresh token is used to obtain a new access token, PingFederate issues:
    • A new refresh token (replacing the old one).
    • A new access token.
  • The previously issued refresh token becomes invalid, so the client must always use the latest refresh token received.

2. Enable Rolling Tokens in PingFederate

  • In PingFederate, rolling refresh tokens are configured at the OAuth Client level.
  • To enable rolling tokens:
    • Go to OAuth Settings for the specific client in PingFederate Admin Console.
    • Under Refresh Token Settings, select:
      • Enable rolling tokens: Ensures old refresh tokens are invalidated after use.
      • Revocation policy: Determines if older refresh tokens are immediately revoked or if they expire naturally.

3. Use the Refresh Token to Obtain New Tokens

  • When the access token expires, the client application must send the refresh token to the Token Endpoint to obtain a new set of tokens.

Token Endpoint Request:


POST /as/token.oauth2 HTTP/1.1 Host: <pingfederate-server> Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=<current_refresh_token>& client_id=<client_id>& client_secret=<client_secret>

Parameters:

  • grant_type: Set to refresh_token.
  • refresh_token: The most recent refresh token issued by PingFederate.
  • client_id: The client ID of your application.
  • client_secret: The client secret associated with the client (if using client authentication).

4. Parse the Token Response

  • After a successful refresh request, PingFederate will respond with:

    { "access_token": "<new_access_token>", "refresh_token": "<new_refresh_token>", "expires_in": 3600, "token_type": "Bearer" }
  • Important: Save the new refresh token (<new_refresh_token>). This is now the only valid refresh token for future requests.

5. Handle Errors Gracefully

  • If the refresh token is invalid (e.g., due to being used already or expired), PingFederate will return an error:

    { "error": "invalid_grant", "error_description": "The refresh token is invalid." }
  • Resolution:
    • Prompt the user to reauthenticate to obtain a new set of tokens.
    • Ensure the application always uses the latest refresh token received.

6. Secure Token Storage

  • Since refresh tokens are sensitive, store them securely:
    • Use encrypted storage (e.g., secure server-side database).
    • Never expose refresh tokens in client-side environments (e.g., browser or local storage).

Example Workflow

  1. Initial Login:

    • User authenticates, and PingFederate issues:
      • Access Token (e.g., valid for 1 hour).
      • Refresh Token (e.g., valid for 7 days).
  2. Access Token Expiry:

    • After the access token expires, the client uses the refresh token to obtain a new access token and refresh token.
  3. Refresh Token Usage:

    • The client sends the current refresh token to PingFederate's Token Endpoint.
    • PingFederate responds with a new access token and a new refresh token.
    • The client replaces the old refresh token with the new one.
  4. Token Revocation:

    • If the refresh token is compromised or misused, rolling tokens ensure the impact is limited since only the latest token is valid.

Best Practices for Rolling Tokens

  1. Always Use the Latest Refresh Token:

    • After each token refresh, replace the old refresh token with the new one immediately.
  2. Implement Graceful Error Handling:

    • Handle scenarios where the refresh token is invalid or expired by reauthenticating the user.
  3. Secure Storage:

    • Store refresh tokens securely to prevent unauthorized access.
  4. Short-Lived Access Tokens:

    • Configure short access token lifetimes (e.g., 15–60 minutes) for better security and rely on refresh tokens to maintain session continuity.

Debugging Tips

If refresh token workflows aren't functioning as expected:

  1. Check PingFederate Logs:

    • Review logs in PingFederate Admin Console for error details during token refresh attempts.
  2. Verify Client Configuration:

    • Ensure rolling tokens are enabled in the OAuth client settings.
    • Confirm token lifetimes and scopes are configured correctly.
  3. Monitor Token Usage:

    • Use PingFederate's Token Inspector to validate issued tokens and their status.
  4. Inspect HTTP Requests/Responses:

    • Use tools like Postman or Fiddler to examine the refresh token flow.

Configuring Rolling Refresh Tokens in PingFederate

1. Configure an OAuth Client in PingFederate

  1. Log into the PingFederate Admin Console.
  2. Navigate to Applications → OAuth Clients and select an existing client or create a new one.
  3. In the Client Settings:
    • Enable Refresh Token Grant:
      • Under the Allowed Grant Types, ensure Refresh Token is selected.
    • Configure Rolling Tokens:
      • Navigate to Refresh Token Settings and enable Rolling Refresh Tokens.
      • Set the Token Lifetime (e.g., 7 days or less depending on your security policy).
      • Select Revocation Policy:
        • Immediate Revocation: Revokes the old refresh token as soon as a new one is issued.
        • Non-Revocation: Allows the old refresh token to remain valid until it expires naturally.

2. Configure Token Lifetimes

  1. Go to Authentication Policies → Access Token Manager.
  2. Configure the Access Token Lifetime (e.g., 15–60 minutes for security).
  3. Configure the Refresh Token Lifetime to define how long the refresh token remains valid (e.g., 7 days).

3. Test the Configuration

  • Use a tool like Postman or cURL to simulate the OAuth flow:
    1. Authenticate to get the initial access token and refresh token.
    2. Use the refresh token to request a new access token.
    3. Verify that a new refresh token is issued during the refresh flow.

Testing the Refresh Token Flow

Step 1: Authenticate and Obtain Tokens

Send a request to PingFederate's Authorization Endpoint or Token Endpoint (depending on the grant type).

Example (Authorization Code Grant):



POST /as/token.oauth2 HTTP/1.1 Host: <pingfederate-server> Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& code=<authorization_code>& redirect_uri=<redirect_uri>& client_id=<client_id>& client_secret=<client_secret>

Response:


{ "access_token": "<access_token>", "refresh_token": "<refresh_token>", "expires_in": 3600, "token_type": "Bearer" }

Step 2: Use the Refresh Token

Send the refresh token to the Token Endpoint to get new tokens:


POST /as/token.oauth2 HTTP/1.1 Host: <pingfederate-server> Content-Type: application/x-www-form-urlencoded grant_type=refresh_token& refresh_token=<current_refresh_token>& client_id=<client_id>& client_secret=<client_secret>

Response (with rolling tokens enabled):


{ "access_token": "<new_access_token>", "refresh_token": "<new_refresh_token>", "expires_in": 3600, "token_type": "Bearer" }
  • Ensure that:
    1. A new refresh token is returned in the response.
    2. The previous refresh token is invalidated.

Troubleshooting Rolling Refresh Token Issues

1. Common Problems

  • Invalid Grant Error:

    • Cause: Using an old/expired refresh token or a refresh token that has already been used.
    • Solution: Always store and use the most recently issued refresh token.
  • Refresh Token Not Rolling:

    • Cause: Rolling tokens may not be enabled for the client.
    • Solution: Verify the Refresh Token Settings in the PingFederate Admin Console for the OAuth client.
  • Token Expired Errors:

    • Cause: Using a refresh token beyond its configured lifetime.
    • Solution: Check the refresh token lifetime in the PingFederate client settings and adjust if needed.
  • Token Revocation Issues:

    • Cause: Old refresh tokens are still being accepted.
    • Solution: Ensure that Immediate Revocation is enabled in the client’s Refresh Token Settings.

2. Debugging Tools

  • PingFederate Logs:

    • Check the pingfederate.log file for errors or warnings related to token issuance and validation.
    • Located in the PingFederate installation directory under log/.
  • Token Inspector:

    • Go to Runtime → OAuth Access Tokens in the PingFederate Admin Console.
    • Use the Token Inspector to verify the status, claims, and expiration times of issued tokens.
  • Postman or cURL:

    • Use these tools to manually test the token exchange flow and ensure the expected tokens are being issued.

3. Validate Client Configuration

  • Ensure the Refresh Token Grant is enabled in the client’s Allowed Grant Types.
  • Verify the redirect_uri and scopes match those used during token requests.

4. Confirm Revocation Policy

  • Navigate to the client’s Refresh Token Settings and confirm the selected Revocation Policy matches your expectations.

5. Enable Debug Logging

  • Increase the logging level in PingFederate to capture detailed information about token exchanges:
    • In the Admin Console, go to Server Settings → Runtime → Diagnostics.
    • Set the logging level to DEBUG.

Best Practices

  1. Always Store the Latest Refresh Token:

    • Replace the old refresh token immediately upon receiving a new one.
  2. Secure Token Storage:

    • Store tokens in encrypted databases or secure server-side storage.
  3. Monitor Token Usage:

    • Use PingFederate's logging and monitoring features to detect misuse or anomalies.
  4. Implement Token Rotation Logic in Clients:

    • Ensure the client application is designed to handle rolling tokens by dynamically updating stored tokens.
  5. Set Appropriate Token Lifetimes:

    • Keep access tokens short-lived for security and rely on refresh tokens for session continuity.

Here’s an example of how to implement a rolling refresh token workflow using PingFederate in a Python client application. This sample demonstrates how to handle token exchange and properly manage refresh tokens.


Python Code Example

Dependencies

Install the required libraries:

pip install requests

Token Management with PingFederate

import requests
import json import time # PingFederate configuration PINGFEDERATE_TOKEN_ENDPOINT = "https://<pingfederate-server>/as/token.oauth2" CLIENT_ID = "<your-client-id>" CLIENT_SECRET = "<your-client-secret>" REDIRECT_URI = "<your-redirect-uri>" REFRESH_TOKEN = None # Initialize with None; this will be updated dynamically. # Global token storage (for demonstration purposes) TOKENS = { "access_token": None, "refresh_token": None, "expires_in": None, "token_acquired_at": None } def authenticate_with_authorization_code(authorization_code): """Authenticate using an authorization code and obtain initial tokens.""" payload = { "grant_type": "authorization_code", "code": authorization_code, "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET } response = requests.post(PINGFEDERATE_TOKEN_ENDPOINT, data=payload) if response.status_code == 200: tokens = response.json() store_tokens(tokens) print("Authenticated successfully!") return tokens else: print("Failed to authenticate:", response.text) return None def refresh_access_token(): """Use the refresh token to obtain a new access token.""" global REFRESH_TOKEN if not REFRESH_TOKEN: print("No refresh token available. User must reauthenticate.") return None payload = { "grant_type": "refresh_token", "refresh_token": REFRESH_TOKEN, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET } response = requests.post(PINGFEDERATE_TOKEN_ENDPOINT, data=payload) if response.status_code == 200: tokens = response.json() store_tokens(tokens) print("Access token refreshed successfully!") return tokens else: print("Failed to refresh token:", response.text) return None def store_tokens(tokens): """Store tokens securely and update global token storage.""" global REFRESH_TOKEN, TOKENS TOKENS["access_token"] = tokens.get("access_token") TOKENS["refresh_token"] = tokens.get("refresh_token") TOKENS["expires_in"] = tokens.get("expires_in") TOKENS["token_acquired_at"] = time.time() REFRESH_TOKEN = TOKENS["refresh_token"] def is_access_token_expired(): """Check if the access token is expired.""" if not TOKENS["access_token"] or not TOKENS["expires_in"]: return True elapsed_time = time.time() - TOKENS["token_acquired_at"] return elapsed_time > TOKENS["expires_in"] def make_authenticated_request(url, headers=None): """Make an API request using the access token.""" if is_access_token_expired(): print("Access token expired. Refreshing...") if not refresh_access_token(): print("Failed to refresh token. Reauthentication required.") return None headers = headers or {} headers["Authorization"] = f"Bearer {TOKENS['access_token']}" response = requests.get(url, headers=headers) if response.status_code == 401: # Unauthorized (token may have been revoked) print("Token invalid. Attempting to refresh...") if not refresh_access_token(): print("Reauthentication required.") return None return make_authenticated_request(url, headers) # Retry request return response # Example usage if __name__ == "__main__": # Replace with the actual authorization code obtained from PingFederate authorization_code = "<authorization_code>" # Step 1: Authenticate and obtain initial tokens tokens = authenticate_with_authorization_code(authorization_code) if tokens: print("Initial tokens:", tokens) # Step 2: Make an API call using the access token api_url = "https://<api-endpoint>/resource" response = make_authenticated_request(api_url) if response: print("API Response:", response.text) # Step 3: Simulate token expiration and refresh print("Simulating token expiration...") time.sleep(TOKENS["expires_in"] + 1) # Wait until the token expires refreshed_tokens = refresh_access_token() if refreshed_tokens: print("Refreshed tokens:", refreshed_tokens) # Step 4: Make another API call with the refreshed token response = make_authenticated_request(api_url) if response: print("API Response after refresh:", response.text)

How It Works

  1. Authenticate with Authorization Code:

    • Use the authorization code to obtain an initial access token and refresh token.
  2. Use the Refresh Token:

    • Refresh the access token using the most recent refresh token.
  3. Handle Token Expiry:

    • Automatically refresh the access token when it expires.
  4. Token Rotation:

    • Update the stored refresh token whenever a new one is issued (rolling tokens).
  5. API Requests:

    • Attach the access token to API requests and retry if the token is invalid.

Best Practices

  • Secure Storage: Store tokens securely (e.g., encrypted storage or a secure database).
  • Handle Errors Gracefully: Implement retry logic and user reauthentication when needed.
  • Minimize Lifetime: Use short-lived access tokens for security and rely on refresh tokens for continuity.
  • Monitor Token Usage: Log and monitor token usage to detect anomalies.

No comments:

Followers