Is this related to an existing feature request or issue?
#8466
Which Powertools for AWS Lambda (Python) utility does this relate to?
Other
Summary
Add an experimental OAuth2Client under aws_lambda_powertools.utilities.auth_alpha.oauth2 for Lambda functions that call OAuth2-protected APIs with the client credentials grant.
The client centralizes credential exchange, conservative token caching, bounded retries, concurrent refresh coordination, secret rotation, and safe authenticated HTTP requests. It supports provider-specific audience selection and RFC 8707 resource selection without treating them as interchangeable.
Implementation: #8486.
Use case
A Lambda function calling an OAuth2-protected API currently has to implement several security-sensitive behaviors itself:
- Load a client secret without retaining stale copies longer than intended.
- Form-encode
client_id and client_secret for client_secret_basic authentication.
- Select the intended API using provider-specific
audience, RFC 8707 resource, scopes, or provider configuration.
- Acquire and validate a bearer token within a finite deadline.
- Cache the token conservatively and reacquire it before expiry.
- Coordinate concurrent cache misses so one execution environment does not create a token-endpoint request storm.
- Prevent tokens, secrets, endpoint response bodies, and authorization headers from escaping through errors or object representations.
- Attach the token to a trusted downstream request without following redirects or replaying the request automatically.
A reusable Powertools client makes these behaviors consistent and testable while still allowing applications to choose how secrets are loaded and which HTTP interface they use.
Proposal
Install the optional dependency and configure one client per OAuth resource outside the Lambda handler. load_secret below is an application-supplied callable, such as one backed by the Parameters utility:
from aws_lambda_powertools.utilities.auth_alpha import OAuth2Client
inventory_api = OAuth2Client(
token_url="https://auth.example.com/oauth/token",
client_id="inventory-lambda",
client_secret=load_secret,
resource="https://inventory.example.com",
scopes=["inventory:read"],
timeout_seconds=3,
)
def lambda_handler(event, context):
response = inventory_api.request(
"GET",
"https://inventory.example.com/stock",
timeout=5,
)
if response.status != 200:
raise RuntimeError("Inventory lookup failed")
return response.json()
Applications using another HTTP client can request only the header:
headers = inventory_api.auth_headers()
Public API
| Parameter |
Required |
Behavior |
token_url |
yes |
Trusted HTTPS token endpoint. User information and fragments are rejected. |
client_id |
yes |
Nonempty client identifier used with client_secret_basic. |
client_secret |
yes |
Nonempty string or callable returning one. The callable runs for every exchange attempt. |
scopes |
no |
Valid scope strings sent as one space-delimited scope value. |
audience |
no |
Provider-specific token-request field, such as an Auth0 API identifier. |
resource |
no |
One absolute resource URI without a fragment, following RFC 8707 request semantics. |
timeout_seconds |
no |
Positive finite budget for the complete token acquisition operation, including retries; default 3 seconds. |
audience and resource are mutually exclusive and are not aliases. The token's target is immutable for the client instance; the later downstream URL does not change it. Applications use a separate client for each independently authorized resource.
Exchange and cache behavior
- Support only
grant_type=client_credentials with client_secret_basic.
- Form-encode the client identifier and secret before creating the HTTP Basic credentials. Never send the secret in the request body.
- Accept only a nonempty bearer access token. Reject malformed token types, token values, and lifetimes.
- Track token lifetime from the start of the token request using a monotonic clock.
- Reuse a token only while more than 30 seconds of its advertised lifetime remain.
- Return newly acquired tokens with 30 seconds or less remaining, or with no
expires_in, without caching them. Do not loop trying to replace a valid short-lived token.
- Never return a token whose advertised lifetime has elapsed, including when a concurrent waiter receives an exchange result.
- Keep caches private to each
OAuth2Client instance.
- Coordinate concurrent misses and refreshes as one in-flight exchange. Waiters share the resulting token or sanitized failure while retaining their own deadline.
- Invoke a callable secret provider on every exchange attempt so retries and later reacquisitions can observe rotation. The client does not maintain a separate secret cache.
- Retry transient transport errors, HTTP 429, and HTTP 5xx responses at most twice, with 100 ms and 200 ms backoff, only when the acquisition budget permits. Do not retry invalid-client, invalid-scope, or other permanent responses.
HTTP interfaces and safety
auth_headers() returns a new [REDACTED: credential] dictionary for an application-owned HTTP client.
request() is a synchronous convenience interface that:
- accepts only trusted HTTPS destinations;
- rejects an existing or malformed
Authorization header;
- validates methods and header names/values before loading credentials;
- disables redirects and downstream retries to avoid credential forwarding or request replay;
- uses a downstream timeout separate from token acquisition;
- forwards only supported body, fields, JSON, and multipart options;
- returns the underlying
urllib3 response for explicit status and body handling; and
- preserves response-size and read-deadline limits in the shared authentication transport.
TokenExchangeError exposes a stable reason and retryability without token-endpoint details. DownstreamRequestError reports downstream transport failure without leaking the destination's response body, credentials, tokens, authorization headers, or active exception chains. repr(client) contains no configuration or cached credential material.
Packaging and documentation
- Add an
[oauth2] package extra containing only the explicitly declared urllib3 dependency.
- Keep imports lazy so importing
auth_alpha does not import urllib3, PyJWT, or cryptography until the associated feature is used.
- Reuse credential-free authentication errors and bounded HTTP transport internals shared with JWT verification without changing existing JWT exception imports.
- Document installation, trusted destinations, resource selection, timeout behavior, secret rotation, diagnostics, testing, and MCP downstream-token separation.
- Include Lambda examples for client credentials, application-owned headers, and sanitized diagnostics.
Acceptance criteria
- Encode scopes,
audience, and resource correctly; reject conflicting or malformed configuration before loading credentials.
- Keep tokens for different resources isolated, including when scopes overlap.
- Exercise cached, uncached, short-lived, expired, and malformed token responses.
- Exercise concurrent success, failure, refresh-boundary, waiter-timeout, and uncacheable-token paths with one in-flight exchange.
- Exercise permanent errors, transient recovery, retry limits, backoff, and exhausted deadlines.
- Exercise secret-loader rotation, invalid values, arbitrary exceptions, and deadline consumption.
- Verify authenticated requests reject unsafe overrides and do not follow redirects or retry downstream operations.
- Verify errors, tracebacks, logs, and object representations never disclose credentials or tokens.
- Verify the OAuth2 feature imports and operates without JWT or cryptography dependencies.
Out of scope
- Authorization-code, device-code, password, refresh-token, and other interactive or delegated grants.
- OAuth 2.0 Token Exchange (RFC 8693) and token introspection (RFC 7662).
client_secret_post, private_key_jwt, mTLS, DPoP, and other client-authentication methods.
- Inbound JWT verification and Lambda authorizer/middleware behavior, which are handled separately from this outbound client.
- Native asynchronous token acquisition or an async HTTP client lifecycle.
- Shared token caches across client instances, processes, or Lambda execution environments.
- Per-request audience/resource overrides or deriving token audience from a downstream URL.
- Automatic provider discovery, secret retrieval, or secret caching. Applications supply trusted configuration and may integrate Parameters through the callable.
- Automatic downstream retries, redirect following, response-status interpretation, or requests to untrusted destinations.
- Token revocation checks when a provider does not expose them through the token response.
Potential challenges
- Provider differences: OAuth providers differ in resource selection, accepted scopes, token response fields, and client authentication. Documentation must state which convention each example uses and avoid implying that
audience and resource are portable aliases.
- Credential destination trust: A bearer token is reusable by its holder. The client cannot determine whether an application-supplied downstream URL belongs to the configured resource, so callers must pass only trusted URLs. Redirects remain disabled.
- Short token lifetimes: The 30-second refresh boundary intentionally makes short-lived tokens uncacheable. Concurrent callers can share the current exchange result, but later calls perform another exchange.
- Deadline composition: Secret loading is synchronous and consumes the acquisition budget, but the client cannot interrupt an arbitrary secret-loader callback. Applications must configure timeout behavior in their secret provider.
- Lambda concurrency: Coordination is limited to one execution environment. Separate environments maintain independent caches and can exchange simultaneously.
- Dependency availability:
urllib3 must be packaged through the [oauth2] extra rather than relying on another package's or a runtime's transitive version.
- Experimental API: The namespace remains
auth_alpha; naming and behavior may change before general availability based on maintainer feedback and provider interoperability testing.
Dependencies and Integrations
| Component |
Integration |
urllib3 |
Explicit optional dependency in [oauth2]; used for bounded token and downstream HTTPS requests. |
| Parameters utility |
Optional application integration through the callable secret provider; the OAuth2 client does not own the Parameters cache. |
| Authentication internals |
Reuses deadline, validation, transport, and credential-free error handling shared with JWT functionality. |
| Logger |
Applications can log stable reason/retryability values without logging token-endpoint bodies or credentials. |
| MCP servers |
Enables a server to obtain a separate downstream API token instead of forwarding an incoming bearer token. No MCP dependency is added. |
Alternative solutions
| Option | Tradeoff |
|---|---|
| Implement the exchange in each application | Avoids a Powertools API but repeats sensitive caching, concurrency, deadline, retry, and redaction logic. |
| Use Authlib or another full OAuth library | Appropriate for applications needing broader grants and authentication methods, but substantially larger than this focused client-credentials surface. |
| Call the token endpoint directly with `urllib3`, `requests`, or `httpx` | Keeps HTTP-client choice flexible but leaves lifecycle, rotation, concurrency, and safe error behavior to every application. |
| Use only `auth_headers()` | Supported when applications already own an HTTP client; the `request()` helper remains optional convenience. |
Acknowledgment
Is this related to an existing feature request or issue?
#8466
Which Powertools for AWS Lambda (Python) utility does this relate to?
Other
Summary
Add an experimental
OAuth2Clientunderaws_lambda_powertools.utilities.auth_alpha.oauth2for Lambda functions that call OAuth2-protected APIs with the client credentials grant.The client centralizes credential exchange, conservative token caching, bounded retries, concurrent refresh coordination, secret rotation, and safe authenticated HTTP requests. It supports provider-specific
audienceselection and RFC 8707resourceselection without treating them as interchangeable.Implementation: #8486.
Use case
A Lambda function calling an OAuth2-protected API currently has to implement several security-sensitive behaviors itself:
client_idandclient_secretforclient_secret_basicauthentication.audience, RFC 8707resource, scopes, or provider configuration.A reusable Powertools client makes these behaviors consistent and testable while still allowing applications to choose how secrets are loaded and which HTTP interface they use.
Proposal
Install the optional dependency and configure one client per OAuth resource outside the Lambda handler.
load_secretbelow is an application-supplied callable, such as one backed by the Parameters utility:Applications using another HTTP client can request only the header:
Public API
token_urlclient_idclient_secret_basic.client_secretscopesscopevalue.audienceresourcetimeout_secondsaudienceandresourceare mutually exclusive and are not aliases. The token's target is immutable for the client instance; the later downstream URL does not change it. Applications use a separate client for each independently authorized resource.Exchange and cache behavior
grant_type=client_credentialswithclient_secret_basic.expires_in, without caching them. Do not loop trying to replace a valid short-lived token.OAuth2Clientinstance.HTTP interfaces and safety
auth_headers()returns a new[REDACTED: credential]dictionary for an application-owned HTTP client.request()is a synchronous convenience interface that:Authorizationheader;urllib3response for explicit status and body handling; andTokenExchangeErrorexposes a stable reason and retryability without token-endpoint details.DownstreamRequestErrorreports downstream transport failure without leaking the destination's response body, credentials, tokens, authorization headers, or active exception chains.repr(client)contains no configuration or cached credential material.Packaging and documentation
[oauth2]package extra containing only the explicitly declaredurllib3dependency.auth_alphadoes not importurllib3, PyJWT, orcryptographyuntil the associated feature is used.Acceptance criteria
audience, andresourcecorrectly; reject conflicting or malformed configuration before loading credentials.Out of scope
client_secret_post,private_key_jwt, mTLS, DPoP, and other client-authentication methods.Potential challenges
audienceandresourceare portable aliases.urllib3must be packaged through the[oauth2]extra rather than relying on another package's or a runtime's transitive version.auth_alpha; naming and behavior may change before general availability based on maintainer feedback and provider interoperability testing.Dependencies and Integrations
urllib3[oauth2]; used for bounded token and downstream HTTPS requests.Alternative solutions
Acknowledgment