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.
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.
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
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"
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
A complete example demonstrating how to use JWT Bearer authentication with the Android Publisher API.
Location: samples/androidpublisher/example_custom_endpoint.py
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
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
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()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'
})import json
with open('serviceAccount.json', 'r') as f:
data = json.load(f)
credentials = JwtBearerCredentials(
kid=data['kid'],
private_key_pem=data['privateKeyPem']
)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
JwtBearerCredentials(kid: str, private_key_pem: str, claims: Optional[Dict[str, Any]] = None)Parameters:
kid(str): The Key ID to include in the JWT headerprivate_key_pem(str): RSA private key in PEM formatclaims(dict, optional): Default JWT claims (iss, sub, exp, iat, etc.)
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
Returns the Key ID (kid).
Returns:
str: The Key ID
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)
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
Key Differences:
- Service Classes: PHP requires manual service class creation, while Python uses auto-generated services via discovery documents
- Client Modifications: PHP needed
Client.phpmodifications, Python doesn't require client changes - Token Generation: Both use RS256 with kid header, but implementation details differ
- Error Handling: Python uses
HttpErrorexception, PHP usesGoogle_Service_Exception
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
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
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
To test the implementation:
- Create a
serviceAccount.jsonfile in the project root:
{
"kid": "your-key-id",
"privateKeyPem": "-----BEGIN RSA PRIVATE KEY-----\n..."
}- Run the example:
cd samples/androidpublisher
python example_custom_endpoint.pygoogle-auth >= 1.32.0, < 3.0.0- For credentials interface and JWT supportgoogle-auth-httplib2 >= 0.2.0, < 1.0.0- For HTTP transport
JwtBearerCredentialsclass for RS256 JWT token generation with kid header- Android Publisher batch update example using JWT Bearer authentication
- Documentation with architecture diagrams
googleapiclient/jwt_bearer_credentials.py(new)samples/androidpublisher/example_custom_endpoint.py(new)samples/androidpublisher/README.md(new)docs/jwt-bearer-authentication.md(new)