Skip to main content
OAuth 2.0 with PKCE (Proof Key for Code Exchange) is the recommended authentication method for XDK Python. It provides enhanced security and supports automatic token refresh.

Overview

The OAuth2PKCEAuth class handles the complete OAuth 2.0 PKCE flow:
  • Code verifier and challenge generation
  • Authorization URL creation
  • Code exchange for tokens
  • Automatic token refresh
  • Token expiration checking

Class Signature

from xdk.oauth2_auth import OAuth2PKCEAuth

auth = OAuth2PKCEAuth(
    base_url: str = "https://api.x.com",
    authorization_base_url: str = "https://x.com/i",
    client_id: str = None,
    client_secret: str = None,
    redirect_uri: str = None,
    token: Dict[str, Any] = None,
    scope: Union[str, List[str]] = None
)

Parameters

  • base_url - API base URL (default: "https://api.x.com")
  • authorization_base_url - OAuth authorization URL (default: "https://x.com/i")
  • client_id - Your OAuth 2.0 client ID from the X Developer Portal
  • client_secret - Your client secret (optional for public clients)
  • redirect_uri - Callback URL for the OAuth flow
  • token - Existing token dictionary if you have one
  • scope - Space-separated scopes or list of scopes (e.g., "tweet.read tweet.write users.read")

OAuth 2.0 PKCE Flow

1

Initialize Client with OAuth2

Create a Client instance with OAuth 2.0 credentials:
from xdk import Client

client = Client(
    client_id="your_client_id",
    client_secret="your_client_secret",  # Optional for public clients
    redirect_uri="https://yourapp.com/callback",
    scope="tweet.read tweet.write users.read"
)
The Client automatically creates an OAuth2PKCEAuth instance when you provide client_id.
2

Get Authorization URL

Generate the authorization URL and redirect the user:
# Get authorization URL (PKCE parameters generated automatically)
auth_url = client.get_authorization_url(state="random_state_string")

print(f"Visit this URL to authorize: {auth_url}")
# Redirect user to auth_url in your web app
The code verifier and challenge are automatically generated and stored at oauth2_auth.py:78.
3

Handle Callback

After the user authorizes, X redirects to your redirect_uri with a code parameter:
https://yourapp.com/callback?code=AUTH_CODE&state=random_state_string
Extract the authorization code from the callback URL.
4

Exchange Code for Token

Exchange the authorization code for access and refresh tokens:
# Using the code from callback
token = client.exchange_code(code="AUTH_CODE")

print(f"Access Token: {token['access_token']}")
print(f"Refresh Token: {token['refresh_token']}")
print(f"Expires In: {token['expires_in']} seconds")
Store the refresh token securely. It’s used to obtain new access tokens without user interaction.
5

Make Authenticated Requests

The client is now authenticated and ready to use:
# Access token is automatically used for requests
user = client.users.get_me()
print(f"Authenticated as: @{user.data.username}")

# Create a tweet
tweet = client.posts.create_tweet(text="Hello from XDK Python!")
print(f"Tweet ID: {tweet.data.id}")

Token Management

Check Token Expiration

if client.is_token_expired():
    print("Token is expired or will expire soon")
    client.refresh_token()
else:
    print("Token is still valid")
The is_token_expired() method at oauth2_auth.py:270 includes a 10-second buffer to prevent edge cases.

Manual Token Refresh

try:
    new_token = client.refresh_token()
    print(f"New access token: {new_token['access_token']}")
except ValueError as e:
    print(f"Refresh failed: {e}")
    # Re-authenticate user
Token refresh requires a client_secret. Public clients cannot refresh tokens and must re-authenticate users.

Access Current Token

# Get the full token dictionary
token = client.token
print(token)
# {
#     'access_token': '...',
#     'token_type': 'bearer',
#     'expires_in': 7200,
#     'refresh_token': '...',
#     'scope': 'tweet.read tweet.write users.read',
#     'expires_at': 1234567890.123
# }

# Get just the access token
access_token = client.access_token
print(access_token)

Using Existing Tokens

If you’ve already obtained tokens, initialize the client with them:
from xdk import Client

# Option 1: Use access_token directly
client = Client(
    access_token="your_existing_access_token"
)

# Option 2: Use full token dictionary (enables refresh)
client = Client(
    client_id="your_client_id",
    client_secret="your_client_secret",
    token={
        'access_token': 'your_access_token',
        'refresh_token': 'your_refresh_token',
        'token_type': 'bearer',
        'expires_in': 7200,
        'scope': 'tweet.read tweet.write users.read'
    }
)

# Refresh when needed
if client.is_token_expired():
    client.refresh_token()

Scopes

Request appropriate scopes for your application:
# As a space-separated string
client = Client(
    client_id="...",
    scope="tweet.read tweet.write users.read follows.read follows.write"
)

# Or as a list
client = Client(
    client_id="...",
    scope=["tweet.read", "tweet.write", "users.read", "follows.read", "follows.write"]
)

Common Scopes

  • tweet.read - Read tweets
  • tweet.write - Create and delete tweets
  • users.read - Read user information
  • follows.read - Read follow relationships
  • follows.write - Follow and unfollow users
  • offline.access - Get refresh tokens (required for token refresh)
  • dm.read - Read direct messages
  • dm.write - Send direct messages
Always request only the scopes your application needs. Users can see which permissions you’re requesting.

Advanced: Manual PKCE Parameters

For advanced use cases, you can manually set PKCE parameters:
from xdk.oauth2_auth import OAuth2PKCEAuth

auth = OAuth2PKCEAuth(
    client_id="your_client_id",
    redirect_uri="https://yourapp.com/callback",
    scope="tweet.read users.read"
)

# Set custom code verifier
auth.set_pkce_parameters(
    code_verifier="your_code_verifier",
    code_challenge="your_code_challenge"  # Optional, will be generated if not provided
)

# Get the parameters
verifier = auth.get_code_verifier()
challenge = auth.get_code_challenge()
The code challenge is generated at oauth2_auth.py:89 using SHA-256 hashing.

Error Handling

try:
    token = client.exchange_code(code="invalid_code")
except ValueError as e:
    print(f"Code exchange failed: {e}")
    # Show error to user and restart OAuth flow

try:
    token = client.refresh_token()
except ValueError as e:
    if "client_secret is required" in str(e):
        print("Cannot refresh - public client")
        # Re-authenticate user
    elif "No token to refresh" in str(e):
        print("No existing token")
        # Start new OAuth flow
    else:
        print(f"Refresh failed: {e}")

Legacy Method: fetch_token

You can also use the full callback URL:
# Using the complete callback URL
callback_url = "https://yourapp.com/callback?code=AUTH_CODE&state=random_state"
token = client.fetch_token(authorization_response=callback_url)
The fetch_token() method at oauth2_auth.py:202 internally uses exchange_code() for compatibility.

Complete Example

import os
from xdk import Client
from flask import Flask, request, redirect

app = Flask(__name__)

# Initialize client
client = Client(
    client_id=os.getenv("X_CLIENT_ID"),
    client_secret=os.getenv("X_CLIENT_SECRET"),
    redirect_uri="http://localhost:5000/callback",
    scope="tweet.read tweet.write users.read offline.access"
)

@app.route("/login")
def login():
    # Generate authorization URL
    auth_url = client.get_authorization_url(state="random_state")
    return redirect(auth_url)

@app.route("/callback")
def callback():
    # Get authorization code from callback
    code = request.args.get("code")
    state = request.args.get("state")
    
    # Verify state parameter
    if state != "random_state":
        return "Invalid state", 400
    
    try:
        # Exchange code for token
        token = client.exchange_code(code)
        
        # Store token securely (e.g., in session, database)
        # session['token'] = token
        
        # Get authenticated user
        user = client.users.get_me()
        
        return f"Successfully authenticated as @{user.data.username}!"
    except ValueError as e:
        return f"Authentication failed: {e}", 400

@app.route("/tweet")
def tweet():
    # Check if token is expired
    if client.is_token_expired():
        try:
            client.refresh_token()
        except ValueError:
            return redirect("/login")
    
    # Create a tweet
    tweet = client.posts.create_tweet(text="Hello from XDK Python with OAuth 2.0!")
    return f"Tweet posted: {tweet.data.id}"

if __name__ == "__main__":
    app.run(port=5000)

Security Best Practices

Always use HTTPS for your redirect_uri in production to prevent token interception.
  • Use the state parameter to prevent CSRF attacks
  • Store tokens encrypted in your database
  • Never expose client_secret in client-side code
  • Implement token rotation by refreshing before expiration
  • Use appropriate scopes - request only what you need
  • Monitor token usage and revoke compromised tokens

Next Steps

Client Reference

Learn how to use the Client with OAuth 2.0

OAuth 1.0a

Explore the traditional OAuth 1.0a authentication method

Build docs developers (and LLMs) love