charter.auth, not charter.
OAuth 2.0 declared rather than wrapped: a server is a frozen declaration in RFC
8414’s vocabulary, a client refreshes a grant you already hold, and a flow
covers the two steps of obtaining one that are protocol rather than product.
The host keeps the callback route, the session, and storage. Charter signs
nothing, so JWT-bearer and service-account grants are out of scope.
OAuth2Server
str
required
Where the form POST goes. Empty raises
DeclarationError."client_secret_post" | "client_secret_basic"
default:"\"client_secret_post\""
How the client authenticates (RFC 6749 §2.3.1).
client_secret_post puts the
credentials in the form body, which is what Google, GitHub and most APIs
expect; client_secret_basic puts them in an HTTP Basic header. Any other
value raises DeclarationError.Optional[str]
default:"None"
The issuer identifier, when the server publishes one.
Optional[str]
default:"None"
Where a user is sent for consent. Optional: a declaration without one is still
a working token-refresh-only server, and
OAuth2Flow.authorize raises with instructions if
you start a consent flow against one.Mapping[str, str]
default:"{}"
Extra authorization-URL parameters this registration needs. The one field
discovery can never fill in — Google without
{"access_type": "offline", "prompt": "consent"} returns no refresh token and
reports no error. Normalised to a read-only, key-sorted mapping, so the
declaration stays frozen all the way down.OAuth2Server.discover
.well-known/openid-configuration, then .well-known/oauth-authorization-server.
This is the path for anything an enterprise runs — Okta, Entra ID, Auth0,
Keycloak and Ping all publish one.
token_endpoint_auth_method is chosen from
token_endpoint_auth_methods_supported, preferring client_secret_post. A
document with no token_endpoint, or advertising neither usable method, raises
CredentialError; so does exhausting both well-known paths, with every attempt
named in the message.
The one network call in this library that is not a tool call.
OAuth2Client
CredentialProvider backed by a
token endpoint. Holds one grant — one end user, or one machine identity. Serve
many users by wrapping it in
SubjectProvider.
OAuth2Server
required
The declaration to refresh against. Anything else raises
TypeError.str
required
Your registration’s client id. Empty raises
CredentialError.str
required
Your registration’s client secret. Empty raises
CredentialError.Optional[str]
default:"None"
The grant to renew. Required for the
refresh_token grant — omitting it
raises CredentialError saying that obtaining a grant is your application’s
flow."refresh_token" | "client_credentials"
default:"\"refresh_token\""
refresh_token is the agent acting for an end user; client_credentials is
the agent acting as itself. These are the two grants that are a plain form
POST; any other value raises ValueError.Optional[str]
default:"None"
Space-separated scopes to send with the token request, when the server wants
them narrowed.
Optional[Callable[[Credentials, Optional[str]], None | Awaitable[None]]]
default:"None"
Called after every successful refresh with the new credentials and the refresh
token to store next time — which may be a new one, since some servers rotate
on every use. This is where persistence lives. A callback that raises is
logged at WARNING and does not fail the call the token was fetched for.
int
default:"90"
How early to renew. Capped at half the lifetime the server granted, so a
60-second token does not refresh on every call.
int
default:"20"
Timeout for the token request.
Optional[httpx.AsyncClient]
default:"None"
Reuse an HTTP client for token requests. When omitted, one is created and
closed per request.
invalid_grant answer marks the grant dead for 60 seconds and re-raises the
same error without asking the server again — one revoked user must not become a
stream of requests against an endpoint that rate-limits per client.
Attributes and methods
OAuth2Server
The declaration passed in.
"refresh_token" | "client_credentials"
Which grant this client uses.
Optional[str]
The scope string sent with token requests.
int
The configured leeway, before the lifetime cap is applied.
Optional[str]
The current refresh token, which is not necessarily the one passed in: a
server that rotates hands back a new one on every refresh and Charter adopts
it.
Credentials
The provider protocol. Returns the cached token, or refreshes.
None
Forget the cached token and any dead-grant cool-down. Call it after
re-authorizing, if you are reusing the client rather than building a new one.
OAuth2Client.from_grant
exchange: the grant’s
access token pre-fills the cache, so the first tool call after connecting spends
no refresh. A grant carrying no refresh token is refused with CredentialError
— for a one-shot access token, use
StaticTokenProvider.
OAuth2Flow
client_id, client_secret, redirect_uri — beside the server declaration,
which holds what the server is.
str
required
The exact callback URL registered with the authorization server, never one
built from user input. Empty raises
CredentialError.authorize() and exchange(): the state and the
PKCE verifier are handed to you and taken back, because only your framework
knows which browser is which.
OAuth2Flow.authorize
Iterable[str]
required
What to ask for. Empty raises
CredentialError: an authorization request for
nothing is a bug upstream. scopes_for derives this from a
tool set.Optional[str]
default:"None"
Your own CSRF token. Generated with
secrets.token_urlsafe(32) when omitted.Optional[str]
default:"None"
Which account to pre-select on the consent screen.
bool
default:"True"
S256 PKCE (RFC 7636).
False exists for the rare server that rejects unknown
parameters, and is never the recommended path.Optional[Mapping[str, str]]
default:"None"
Per-request additions, merged last.
authorization_params, then extra_params. Neither map may set client_id,
redirect_uri, state or anything starting with code_challenge — identity
and the CSRF/PKCE material belong to the flow, and an attempt raises
ValueError naming the source. A server with no authorization_endpoint raises
CredentialError.
scope is encoded with %20 rather than +, which some servers reject.
OAuth2Flow.exchange
str
required
The authorization code from the callback. Empty raises
CredentialError.Optional[str]
default:"None"
The verifier
authorize() returned, when PKCE was used.bool
default:"True"
Raise
CredentialError when the server returns no refresh token. This is the
silent failure of the whole flow — the exchange succeeds, the access token
works, and the integration dies within the hour. Pass False only for a
server that genuinely never issues one.AuthorizationRequest
authorize() hands back.
str
Redirect the user here.
str
Put it in the session, server-side and keyed to this browser, and compare it
at the callback with
states_match.Optional[str]
Keep it beside the state and pass it to
exchange(). None when
pkce=False.TokenGrant
str
Valid now, typically for an hour.
Optional[str]
What you persist.
None when the server issued none and
expect_refresh_token=False.Optional[datetime]
Derived from
expires_in, or None when the server sent none.list[str]
What the server says was actually granted, which is not always what was asked
for: some consent screens let a user deselect.
Dict[str, Any]
The untouched response body, for vendor extras.
repr and str mask the tokens, so printing a grant does not put a secret
in a log line.
This is deliberately not
Credentials: that is the injection
currency, handed out on every tool call. A grant is what you persist once.
states_match
state is the one authorize() issued. Compares in
constant time, so the CSRF check is one obvious call rather than a plain ==
that leaks how many leading characters matched.
Verifying state at the callback is the host’s job: it is what keeps an
attacker from splicing their code into your user’s session.
scopes_for
scopes metadata each was declared with and deduped in first-seen order.
Types in these signatures
The three aliases the signatures above are declared in. They are exported fromcharter so a host writing its own wrapper can name them without importing from
a submodule.
Grant
OAuth2Client uses. "refresh_token" acts for a user
who consented; "client_credentials" acts as the application itself, with no
user and so no refresh token to store.
TokenEndpointAuthMethod
OAuth2Server.discover reads it for you.
Getting it wrong is a silent-shaped failure: the server answers
invalid_client, which reads like a wrong secret rather than a wrong envelope.
OnRefresh
Related
- Authorization servers — declarations for the servers you are likeliest to meet
- The OAuth flow — the route, the session and the storage around these two calls
- Credentials