Where the OAuth client keeps tokens and registered clients.
Configure one as store: {module, opts} in the client's OAuth options;
opts is passed as the last argument of every callback. The default is
ConduitMcp.Client.OAuth.TokenStore.ETS: in memory, lost when the client
(or, for a ConduitMcp.Client.Hub server, the hub) stops. Implement this
behaviour to persist tokens (a database, the OS keychain, an encrypted
file).
Keys
- Tokens are stored under
{principal, issuer, resource}: the host's identity for the user (:principal,nilwhen unused), the authorization server'sissuer, and the MCP server's canonical resource URI. A token is therefore never used for another server, another authorization server, or another user - even when two servers trust the same authorization server (OA-5). - Registered clients (dynamic client registration) are stored under the issuer alone: credentials issued by one authorization server are never presented to another (OA-6).
Values
Tokens are ConduitMcp.Client.OAuth.Tokens structs; the access and
refresh tokens inside are ConduitMcp.Client.Secrets. A client is a
string-keyed map: "client_id", "client_secret" (a Secret or nil),
"token_endpoint_auth_method" and "redirect_uris". A store that
serializes values must call ConduitMcp.Client.Secret.expose/1 and wrap
again on the way back, and should encrypt them at rest.
Single-flight refresh
transaction/3 runs fun while holding a lock on key, so that of many
processes finding the same expired token only one refreshes it and the
others re-read the store inside the lock and find the new token. It is
optional: the default (transaction/4 here) is a node-local
:global.trans/4 on {ConduitMcp.Client.OAuth.TokenStore, key}. A store shared
across nodes (a database) should implement it with its own lock, for
example SELECT ... FOR UPDATE.
Summary
Functions
Runs fun under the store's lock for key: the store's own
transaction/3 when it has one, else a node-local
:global.trans/4.
Types
Callbacks
@callback fetch_tokens(key(), opts :: keyword()) :: {:ok, ConduitMcp.Client.OAuth.Tokens.t()} | :error
@callback put_tokens(key(), ConduitMcp.Client.OAuth.Tokens.t(), opts :: keyword()) :: :ok | {:error, term()}
Functions
Runs fun under the store's lock for key: the store's own
transaction/3 when it has one, else a node-local
:global.trans/4.