Skip to content

Latest commit

 

History

History
371 lines (287 loc) · 9.76 KB

File metadata and controls

371 lines (287 loc) · 9.76 KB

JWT Bearer Authentication Support

This document describes the implementation of JWT Bearer Authentication support for the Google API Python Client library, enabling RS256 JWT token generation with Key ID (kid) header support.

Overview

The implementation adds support for JWT Bearer token authentication, allowing clients to authenticate with Google APIs using RS256-signed JWT tokens that include a Key ID (kid) in the header. This is particularly useful for Android Publisher API operations that require JWT Bearer authentication.

Architecture

graph TB
    A[Client Application] -->|1. Creates| B[JwtBearerCredentials]
    B -->|2. Generates| C[JWT Token with RS256]
    C -->|3. Includes| D[Key ID in Header]
    B -->|4. Implements| E[google.auth.credentials.Credentials]
    E -->|5. Used by| F[googleapiclient.discovery.build]
    F -->|6. Creates| G[Service Instance]
    G -->|7. Makes| H[API Requests with Bearer Token]
    
    style B fill:#e1f5ff
    style C fill:#fff4e1
    style E fill:#e8f5e9
Loading

Components

1. JwtBearerCredentials Class

The JwtBearerCredentials class extends google.auth.credentials.Credentials and provides JWT token generation capabilities.

classDiagram
    class Credentials {
        <<abstract>>
        +token: str
        +expiry: datetime
        +valid: bool
        +expired: bool
        +refresh(request)
        +apply(headers, token)
        +before_request(request, method, url, headers)
    }
    
    class JwtBearerCredentials {
        -_kid: str
        -_private_key_pem: str
        -_default_claims: dict
        -_signer: RSASigner
        +kid: str
        +generate_token(claims): str
        +get_kid(): str
    }
    
    Credentials <|-- JwtBearerCredentials
    
    note for JwtBearerCredentials "Generates RS256 JWT tokens\nwith kid header"
Loading

Location: googleapiclient/jwt_bearer_credentials.py

Key Features:

  • Generates RS256-signed JWT tokens
  • Includes Key ID (kid) in JWT header
  • Supports custom JWT claims
  • Automatic token expiration handling
  • Token caching and refresh logic

2. AndroidPublisher Batch Update Example

A complete example demonstrating how to use JWT Bearer authentication with the Android Publisher API.

Location: samples/androidpublisher/example_custom_endpoint.py

Flow Diagram

sequenceDiagram
    participant App as Application
    participant Cred as JwtBearerCredentials
    participant JWT as JWT Library
    participant API as Google API Service
    participant Server as Google API Server
    
    App->>Cred: Create with kid & private_key_pem
    App->>API: build('androidpublisher', 'v3', credentials=Cred)
    
    API->>Cred: before_request() called
    Cred->>Cred: Check if token valid
    alt Token expired or missing
        Cred->>JWT: encode(signer, claims, key_id=kid)
        JWT-->>Cred: JWT token string
        Cred->>Cred: Store token & expiry
    end
    Cred->>API: Apply 'Authorization: Bearer {token}' header
    API->>Server: HTTP Request with Bearer token
    Server-->>API: Response
    API-->>App: Result
Loading

Token Generation Process

flowchart TD
    A[Start: generate_token called] --> B{Merge claims}
    B --> C[Default claims]
    B --> D[Provided claims]
    C --> E[Combined claims]
    D --> E
    E --> F{exp claim exists?}
    F -->|No| G[Set exp = now + 3600]
    F -->|Yes| H[Use provided exp]
    G --> I{iat claim exists?}
    H --> I
    I -->|No| J[Set iat = now]
    I -->|Yes| K[Use provided iat]
    J --> L[Encode JWT with RSASigner]
    K --> L
    L --> M[Add kid to header via key_id]
    M --> N{Token is bytes?}
    N -->|Yes| O[Decode to UTF-8 string]
    N -->|No| P[Use as string]
    O --> Q[Store token & expiry]
    P --> Q
    Q --> R[Return token]
    
    style A fill:#e1f5ff
    style L fill:#fff4e1
    style Q fill:#e8f5e9
    style R fill:#f3e5f5
Loading

Usage Example

Basic Usage

from googleapiclient.discovery import build
from googleapiclient.jwt_bearer_credentials import JwtBearerCredentials

# Create JWT Bearer credentials
credentials = JwtBearerCredentials(
    kid="your-key-id",
    private_key_pem="-----BEGIN RSA PRIVATE KEY-----\n..."
)

# Build service with credentials
service = build('androidpublisher', 'v3', credentials=credentials)

# Use the service
response = service.monetization().onetimeproducts().batchUpdate(
    packageName="com.example.app",
    body=batch_request_body
).execute()

With Custom Claims

credentials = JwtBearerCredentials(
    kid="your-key-id",
    private_key_pem=private_key,
    claims={
        'iss': 'your-issuer',
        'sub': 'your-subject',
        'aud': 'https://www.googleapis.com/auth/androidpublisher'
    }
)

# Generate token with additional claims
token = credentials.generate_token({
    'custom_claim': 'value'
})

Loading from JSON File

import json

with open('serviceAccount.json', 'r') as f:
    data = json.load(f)

credentials = JwtBearerCredentials(
    kid=data['kid'],
    private_key_pem=data['privateKeyPem']
)

File Structure

graph LR
    A[google-api-python-client] --> B[googleapiclient/]
    A --> C[samples/]
    A --> D[docs/]
    
    B --> E[jwt_bearer_credentials.py]
    C --> F[androidpublisher/]
    D --> G[jwt-bearer-authentication.md]
    
    F --> H[example_custom_endpoint.py]
    F --> I[README.md]
    
    style E fill:#e1f5ff
    style H fill:#fff4e1
    style G fill:#e8f5e9
Loading

API Reference

JwtBearerCredentials

Constructor

JwtBearerCredentials(kid: str, private_key_pem: str, claims: Optional[Dict[str, Any]] = None)

Parameters:

  • kid (str): The Key ID to include in the JWT header
  • private_key_pem (str): RSA private key in PEM format
  • claims (dict, optional): Default JWT claims (iss, sub, exp, iat, etc.)

Methods

generate_token(claims: Optional[Dict[str, Any]] = None) -> str

Generates a JWT Bearer token with custom claims.

Parameters:

  • claims (dict, optional): Additional JWT claims to merge with defaults

Returns:

  • str: The signed JWT token
get_kid() -> str

Returns the Key ID (kid).

Returns:

  • str: The Key ID

Properties

  • kid (str): The Key ID (read-only)
  • token (str): Current access token (from base class)
  • expiry (datetime): Token expiration time (from base class)
  • valid (bool): Whether credentials are valid (from base class)
  • expired (bool): Whether credentials are expired (from base class)

Comparison with PHP Implementation

graph TB
    subgraph PHP["PHP Implementation"]
        P1[JwtBearerCredentials.php]
        P2[AndroidPublisher.php]
        P3[MonetizationOneTimeProducts.php]
        P4[Client.php modifications]
    end
    
    subgraph Python["Python Implementation"]
        PY1[jwt_bearer_credentials.py]
        PY2[discovery.build - auto-generated]
        PY3[example_custom_endpoint.py]
    end
    
    P1 -.Equivalent.-> PY1
    P2 -.Auto-handled.-> PY2
    P3 -.Auto-handled.-> PY2
    P4 -.Not needed.-> PY2
    
    style PY2 fill:#e8f5e9
    note1[Python uses discovery documents<br/>No manual service classes needed]
    PY2 -.-> note1
Loading

Key Differences:

  1. Service Classes: PHP requires manual service class creation, while Python uses auto-generated services via discovery documents
  2. Client Modifications: PHP needed Client.php modifications, Python doesn't require client changes
  3. Token Generation: Both use RS256 with kid header, but implementation details differ
  4. Error Handling: Python uses HttpError exception, PHP uses Google_Service_Exception

Error Handling

flowchart TD
    A[API Request] --> B{Request succeeds?}
    B -->|Yes| C[Return response]
    B -->|No| D{Error type?}
    D -->|HttpError| E[HTTP Status Code]
    D -->|Other Exception| F[Generic Error]
    E --> G[Parse error content]
    G --> H[Display error details]
    F --> I[Display error message]
    H --> J[Exit with code 1]
    I --> J
    
    style C fill:#e8f5e9
    style E fill:#ffebee
    style F fill:#ffebee
Loading

Security Considerations

graph LR
    A[Private Key] -->|Stored securely| B[PEM Format]
    B -->|Used for signing| C[JWT Token]
    C -->|Includes| D[Key ID]
    D -->|Validates| E[Server]
    
    F[Token Expiration] -->|Prevents| G[Replay Attacks]
    H[RS256 Algorithm] -->|Ensures| I[Signature Integrity]
    
    style A fill:#ffebee
    style C fill:#e8f5e9
    style F fill:#fff4e1
    style H fill:#e1f5ff
Loading

Best Practices:

  • Store private keys securely (environment variables, secret managers)
  • Use appropriate token expiration times
  • Rotate keys periodically
  • Never commit private keys to version control
  • Use HTTPS for all API communications

Testing

To test the implementation:

  1. Create a serviceAccount.json file in the project root:
{
  "kid": "your-key-id",
  "privateKeyPem": "-----BEGIN RSA PRIVATE KEY-----\n..."
}
  1. Run the example:
cd samples/androidpublisher
python example_custom_endpoint.py

Dependencies

  • google-auth >= 1.32.0, < 3.0.0 - For credentials interface and JWT support
  • google-auth-httplib2 >= 0.2.0, < 1.0.0 - For HTTP transport

Related Documentation

Changelog

Added

  • JwtBearerCredentials class for RS256 JWT token generation with kid header
  • Android Publisher batch update example using JWT Bearer authentication
  • Documentation with architecture diagrams

Files Changed

  • googleapiclient/jwt_bearer_credentials.py (new)
  • samples/androidpublisher/example_custom_endpoint.py (new)
  • samples/androidpublisher/README.md (new)
  • docs/jwt-bearer-authentication.md (new)