Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.grantex.dev/llms.txt

Use this file to discover all available pages before exploring further.

Overview

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks by binding the authorization request to the token exchange. The Grantex SDK provides a generate_pkce() helper that creates a cryptographically random code verifier and its S256 challenge.

Usage

from grantex import generate_pkce

pkce = generate_pkce()

print(pkce.code_verifier)         # Random 43-character URL-safe string
print(pkce.code_challenge)        # SHA-256 hash of the verifier, base64url-encoded
print(pkce.code_challenge_method) # Always "S256"

Import

from grantex import generate_pkce, PkceChallenge

PkceChallenge

generate_pkce() returns a PkceChallenge frozen dataclass:
FieldTypeDescription
code_verifierstrA cryptographically random URL-safe string (32 bytes, base64url-encoded).
code_challengestrThe SHA-256 hash of the verifier, base64url-encoded.
code_challenge_methodstrAlways "S256".

Complete PKCE Flow

The PKCE flow has three steps: generate the challenge, authorize with the challenge, and exchange with the verifier.

Step 1: Generate the PKCE Pair

from grantex import generate_pkce

pkce = generate_pkce()
# Store pkce.code_verifier securely -- you will need it in Step 3

Step 2: Authorize with the Code Challenge

Pass the code_challenge and code_challenge_method in the authorization request:
from grantex import Grantex, AuthorizeParams

with Grantex(api_key="gx_live_...") as client:
    auth = client.authorize(AuthorizeParams(
        agent_id="agt_abc123",
        user_id="user_xyz",
        scopes=["files:read", "files:write"],
        redirect_uri="https://myapp.com/callback",
        code_challenge=pkce.code_challenge,
        code_challenge_method=pkce.code_challenge_method,
    ))

    # Redirect user to auth.consent_url

Step 3: Exchange with the Code Verifier

After the user approves and you receive the authorization code at your redirect URI, include the code_verifier in the token exchange:
from grantex import Grantex, ExchangeTokenParams

with Grantex(api_key="gx_live_...") as client:
    token_response = client.tokens.exchange(ExchangeTokenParams(
        code="received_auth_code",
        agent_id="agt_abc123",
        code_verifier=pkce.code_verifier,
    ))

    print(f"Grant token: {token_response.grant_token}")
The server verifies that SHA256(code_verifier) == code_challenge before issuing the token. If the verifier does not match, the exchange is rejected.

Full Example

from grantex import (
    Grantex,
    AuthorizeParams,
    ExchangeTokenParams,
    generate_pkce,
)

# 1. Generate PKCE pair
pkce = generate_pkce()

with Grantex(api_key="gx_live_...") as client:
    # 2. Start authorization with PKCE
    auth = client.authorize(AuthorizeParams(
        agent_id="agt_abc123",
        user_id="user_xyz",
        scopes=["calendar:read"],
        redirect_uri="https://myapp.com/callback",
        code_challenge=pkce.code_challenge,
        code_challenge_method=pkce.code_challenge_method,
    ))

    print(f"Send user to: {auth.consent_url}")

    # 3. User approves, you receive the code at your callback
    code = "code_from_redirect"

    # 4. Exchange with PKCE verifier
    result = client.tokens.exchange(ExchangeTokenParams(
        code=code,
        agent_id="agt_abc123",
        code_verifier=pkce.code_verifier,
    ))

    print(f"Grant token: {result.grant_token}")
    print(f"Grant ID: {result.grant_id}")
    print(f"Scopes: {result.scopes}")

How It Works

  1. generate_pkce() creates 32 random bytes and base64url-encodes them to produce the code_verifier.
  2. The code_challenge is the SHA-256 digest of the verifier, base64url-encoded (without padding).
  3. The challenge method is always S256 (plain is not supported).
  4. During token exchange, the server independently computes SHA256(code_verifier) and compares it to the stored code_challenge. A mismatch causes the exchange to fail.