# OAuth apps

> Connecting members, app tokens, token and sign-in stores, scopes.

Source: https://docs.stxapp.io/sdks/python/reference/oauth/

> **Availability.** Available on US demo now. Live access is coming soon in the US and Ontario. See [Environments](https://docs.stxapp.io/environments/).

## `AsyncOAuthClient`

One registered app on one exchange.

Configuration comes from the arguments, then `STX_OAUTH_CLIENT_ID`,
`STX_OAUTH_CLIENT_SECRET`, `STX_OAUTH_REDIRECT_URI`; the exchange
is picked as for `AsyncSTX` (`host=`, or `region=` and `env=`,
or `STX_HOST` / a profile):

```python
oauth = AsyncOAuthClient(
    client_id=CLIENT_ID, client_secret=CLIENT_SECRET,
    redirect_uri="https://your.app/callback", region="us", env="demo",
)
```

Omit `client_secret` for a public client (registered without one):
it identifies with `client_id` in the form and PKCE is its proof; it
cannot mint app tokens. Prefer `discover`, which takes every
endpoint from the exchange's discovery document and checks scopes
against what the exchange offers before sending a member anywhere.

### Constructor

| Parameter | Description |
|---|---|
| `scope` | default scopes to request, space-delimited or a list. |
| `endpoints` | overrides for `authorize`, `token`, `revoke`, `introspect` (paths or absolute URLs). |
| `metadata` | a discovery document already fetched. |
| `transport` | an `httpx.AsyncBaseTransport`, for tests. |

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `endpoints` | `Dict[str, str]` |  |
| `host` | `str` |  |
| `is_public` | `bool` | True without a client secret: PKCE only, no app tokens. |

### Methods

#### `app()`

```python
app(scope: ScopeInput = None) -> OAuthAppAuth
```

An auth provider acting as the app (client credentials).

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |

#### `app_client()`

```python
app_client(scope: ScopeInput = None, **kwargs: Any) -> AsyncSTX
```

An `AsyncSTX` acting as the app: `markets()`, `events()`,
`event_players()`, `market_types()`, `create_markets()` (with
`markets.write`) and, through `websocket()`, the public market
channels. `scope` defaults to every app scope the app holds.

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |
| `kwargs` | `Any` |  |

#### `app_token()`

```python
app_token(scope: ScopeInput = None) -> AppTokenProvider
```

A cached app token that re-mints itself before expiry. The same
scope returns the same provider, so every client built from this
app shares one token.

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |

#### `authorization_url()`

```python
authorization_url(*, scope: ScopeInput = None, state: Optional[str] = None, redirect_uri: Optional[str] = None, prompt: Optional[str] = None, connection: Optional[str] = None, login_hint: Optional[str] = None, extra_params: Optional[Mapping[str, str]] = None) -> Authorization
```

A fresh PKCE pair and `state`, and the authorize URL for them.

`prompt` is `none` (silent reconnect), `login` or `consent`.
`connection` (`google`, `apple`, `x`) skips STX's sign-in page
for that provider; `login_hint` preselects the member's email.
Keep `state` and `code_verifier`; `begin_authorization`
does that for you.

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |
| `state` | `Optional[str]` |  |
| `redirect_uri` | `Optional[str]` |  |
| `prompt` | `Optional[str]` |  |
| `connection` | `Optional[str]` |  |
| `login_hint` | `Optional[str]` |  |
| `extra_params` | `Optional[Mapping[str, str]]` |  |

#### `begin_authorization()`

```python
begin_authorization(pending_store: Any, *, scope: ScopeInput = None, data: Optional[Dict[str, Any]] = None, ttl: float = 600.0, redirect_uri: Optional[str] = None, prompt: Optional[str] = None, connection: Optional[str] = None, login_hint: Optional[str] = None, extra_params: Optional[Mapping[str, str]] = None) -> Authorization
```

Start a sign-in: remember the PKCE verifier (and `data`) in
`pending_store` under a fresh `state` for `ttl` seconds, and
return the URL to redirect the member to. Finish with
`complete_authorization` on the callback.

| Parameter | Type | Description |
|---|---|---|
| `pending_store` | `Any` |  |
| `scope` | `ScopeInput` |  |
| `data` | `Optional[Dict[str, Any]]` |  |
| `ttl` | `float` |  |
| `redirect_uri` | `Optional[str]` |  |
| `prompt` | `Optional[str]` |  |
| `connection` | `Optional[str]` |  |
| `login_hint` | `Optional[str]` |  |
| `extra_params` | `Optional[Mapping[str, str]]` |  |

#### `build_authorize_url()`

```python
build_authorize_url(*, state: str, code_challenge: str, scope: ScopeInput = None, redirect_uri: Optional[str] = None, prompt: Optional[str] = None, connection: Optional[str] = None, login_hint: Optional[str] = None, extra_params: Optional[Mapping[str, str]] = None) -> str
```

The authorize URL for a challenge you already have.

| Parameter | Type | Description |
|---|---|---|
| `state` | `str` |  |
| `code_challenge` | `str` |  |
| `scope` | `ScopeInput` |  |
| `redirect_uri` | `Optional[str]` |  |
| `prompt` | `Optional[str]` |  |
| `connection` | `Optional[str]` |  |
| `login_hint` | `Optional[str]` |  |
| `extra_params` | `Optional[Mapping[str, str]]` |  |

#### `client_credentials()`

```python
client_credentials(scope: ScopeInput = None) -> TokenSet
```

Mint an app token (RFC 6749 section 4.4): the app acting as itself,
no member, no refresh token. App scopes only (`market_data`,
`events`, `markets.write`); omit `scope` for every app scope the
app holds. Needs a client secret. For a cached, self-renewing token
use `app_token`.

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |

#### `complete_authorization()`

```python
complete_authorization(pending_store: Any, callback: CallbackInput, *, store: Any = None, member_key: Union[str, Callable[[Optional[Dict[str, Any]]], str], None] = None) -> CompletedAuthorization
```

Finish a sign-in from the callback URL or its query.

Uses up the `state`, raises the callback's error as a typed
exception (see `read_callback`), exchanges the code with the
stored verifier and, given `store`, saves the tokens under
`member_key` (a string, or a function of the `data` passed to
`begin_authorization`).

| Parameter | Type | Description |
|---|---|---|
| `pending_store` | `Any` |  |
| `callback` | `CallbackInput` |  |
| `store` | `Any` |  |
| `member_key` | `Union[str, Callable[[Optional[Dict[str, Any]]], str], None]` |  |

#### `discover()`

```python
discover(client_id: Optional[str] = None, client_secret: Optional[str] = None, **kwargs: Any) -> AsyncOAuthClient
```

A client whose endpoints come from the exchange's discovery document.

Raises `STXNotAvailableError` (`oauth`) on an exchange without
OAuth. Takes the same arguments as the constructor.

| Parameter | Type | Description |
|---|---|---|
| `client_id` | `Optional[str]` |  |
| `client_secret` | `Optional[str]` |  |
| `kwargs` | `Any` |  |

#### `exchange_code()`

```python
exchange_code(code: str, code_verifier: str, *, redirect_uri: Optional[str] = None) -> TokenSet
```

Exchange an authorization code for tokens, proving PKCE.

| Parameter | Type | Description |
|---|---|---|
| `code` | `str` |  |
| `code_verifier` | `str` |  |
| `redirect_uri` | `Optional[str]` |  |

#### `introspect()`

```python
introspect(token: str) -> Dict[str, Any]
```

A token's `active` state and scope (RFC 7662). Confidential
clients only.

| Parameter | Type | Description |
|---|---|---|
| `token` | `str` |  |

#### `member_client()`

```python
member_client(store: Any, member_key: str, **kwargs: Any) -> AsyncSTX
```

An `AsyncSTX` acting for one connected member.

Every member method (`balance`, `positions`, `orders`,
`place_order`, `cancel_order`...) with the member's token,
refreshed when due, and `websocket()` for their channels. Session
options (`skew`, `on_refresh`, `on_grant_revoked`,
`on_terms_required`) and `AsyncSTX` options (`timeout`,
`retry`, `on_response`) are passed through.

| Parameter | Type | Description |
|---|---|---|
| `store` | `Any` |  |
| `member_key` | `str` |  |
| `kwargs` | `Any` |  |

#### `metadata()`

```python
metadata(*, refresh: bool = False) -> AuthorizationServerMetadata
```

`GET /.well-known/oauth-authorization-server`, fetched once.

Raises `STXNotAvailableError` (`oauth`) when the exchange does
not publish it (a 404).

| Parameter | Type | Description |
|---|---|---|
| `refresh` | `bool` |  |

#### `protected_resource()`

```python
protected_resource() -> Dict[str, Any]
```

`GET /.well-known/oauth-protected-resource` (RFC 9728).

#### `read_callback()`

```python
read_callback(pending_store: Any, callback: CallbackInput) -> CallbackResult
```

Check an authorize callback and use up its pending sign-in, without
calling STX. Pass the result to `redeem_authorization`.

Raises the callback's error as a typed exception, or
`STXOAuthException` with `invalid_request` (no code or state) or
`invalid_state` (unknown, used or expired).

| Parameter | Type | Description |
|---|---|---|
| `pending_store` | `Any` |  |
| `callback` | `CallbackInput` |  |

#### `redeem_authorization()`

```python
redeem_authorization(callback: CallbackResult, *, store: Any = None, member_key: Union[str, Callable[[Optional[Dict[str, Any]]], str], None] = None) -> CompletedAuthorization
```

The second half of `complete_authorization`, for a callback
already read with `read_callback` (one callback URL serving
several apps).

| Parameter | Type | Description |
|---|---|---|
| `callback` | `CallbackResult` |  |
| `store` | `Any` |  |
| `member_key` | `Union[str, Callable[[Optional[Dict[str, Any]]], str], None]` |  |

#### `refresh_token()`

```python
refresh_token(refresh_token: str) -> TokenSet
```

Trade a refresh token for a new pair. STX rotates refresh tokens:
store whatever comes back.

A refused refresh (`invalid_grant`) raises
`STXGrantRevokedException`; a member still being verified raises
`STXAccountPendingException` (keep the tokens). While the member
has new STX terms to accept the refresh succeeds and the result has
`terms_required` and `terms_url`.

| Parameter | Type | Description |
|---|---|---|
| `refresh_token` | `str` |  |

#### `revoke()`

```python
revoke(token: str, token_type_hint: Optional[str] = None) -> None
```

Revoke a token (RFC 7009). A refresh token ends the member's whole
grant; an access token revokes only its pair.

| Parameter | Type | Description |
|---|---|---|
| `token` | `str` |  |
| `token_type_hint` | `Optional[str]` |  |

#### `session()`

```python
session(store: Any, member_key: str, *, skew: float = 30.0, on_refresh: Optional[Callable[[StoredTokens], Any]] = None, on_grant_revoked: Optional[Callable[[STXGrantRevokedException], Any]] = None, on_terms_required: Optional[Callable[[str], Any]] = None) -> OAuthSessionAuth
```

An auth provider for one member, over `store`: attaches their
access token, refreshes it `skew` seconds before expiry and once
on a 401 (storing the rotated pair), and treats a revoked grant as
final (deletes the tokens, raises `STXGrantRevokedException`).

| Parameter | Type | Description |
|---|---|---|
| `store` | `Any` |  |
| `member_key` | `str` |  |
| `skew` | `float` |  |
| `on_refresh` | `Optional[Callable[[StoredTokens], Any]]` |  |
| `on_grant_revoked` | `Optional[Callable[[STXGrantRevokedException], Any]]` |  |
| `on_terms_required` | `Optional[Callable[[str], Any]]` |  |

#### `unlink()`

```python
unlink(store: Any, member_key: str) -> bool
```

Disconnect a member: revoke their grant at the exchange (best
effort) and delete their tokens from `store`. Returns True when the
exchange confirmed the revoke; the local tokens are deleted either
way.

| Parameter | Type | Description |
|---|---|---|
| `store` | `Any` |  |
| `member_key` | `str` |  |

## `OAuthClient`

Blocking OAuth client for one app. Takes the same arguments as
`AsyncOAuthClient` and has the same methods without `await`:

```python
oauth = OAuthClient.discover(
    client_id=CLIENT_ID, client_secret=CLIENT_SECRET,
    redirect_uri="https://your.app/callback", region="us", env="demo",
)
auth = oauth.begin_authorization(pending, data={"user_id": "u1"})
# ... redirect to auth.url; on the callback:
done = oauth.complete_authorization(pending, request_url, store=tokens, member_key="u1")
with oauth.member_client(tokens, "u1") as stx:
    print(stx.balance().available_balance)
```

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `async_client` | `AsyncOAuthClient` |  |
| `base_url` | `str` |  |
| `host` | `str` |  |
| `client_id` | `str` |  |
| `redirect_uri` | `Optional[str]` |  |
| `endpoints` | `Dict[str, str]` |  |
| `is_public` | `bool` |  |

### Methods

#### `app_client()`

```python
app_client(scope: ScopeInput = None, **kwargs: Any) -> STX
```

A blocking `STX` acting as the app. See
`AsyncOAuthClient.app_client`.

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |
| `kwargs` | `Any` |  |

#### `app_token()`

```python
app_token(scope: ScopeInput = None) -> AppToken
```

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |

#### `authorization_url()`

```python
authorization_url(**kwargs: Any) -> Authorization
```

| Parameter | Type | Description |
|---|---|---|
| `kwargs` | `Any` |  |

#### `begin_authorization()`

```python
begin_authorization(pending_store: Any, **kwargs: Any) -> Authorization
```

| Parameter | Type | Description |
|---|---|---|
| `pending_store` | `Any` |  |
| `kwargs` | `Any` |  |

#### `build_authorize_url()`

```python
build_authorize_url(**kwargs: Any) -> str
```

| Parameter | Type | Description |
|---|---|---|
| `kwargs` | `Any` |  |

#### `client_credentials()`

```python
client_credentials(scope: ScopeInput = None) -> TokenSet
```

| Parameter | Type | Description |
|---|---|---|
| `scope` | `ScopeInput` |  |

#### `complete_authorization()`

```python
complete_authorization(pending_store: Any, callback: CallbackInput, *, store: Any = None, member_key: Union[str, Callable[[Optional[Dict[str, Any]]], str], None] = None) -> CompletedAuthorization
```

| Parameter | Type | Description |
|---|---|---|
| `pending_store` | `Any` |  |
| `callback` | `CallbackInput` |  |
| `store` | `Any` |  |
| `member_key` | `Union[str, Callable[[Optional[Dict[str, Any]]], str], None]` |  |

#### `discover()`

```python
discover(client_id: Optional[str] = None, client_secret: Optional[str] = None, **kwargs: Any) -> OAuthClient
```

A client on the endpoints the exchange publishes. Raises
`STXNotAvailableError` (`oauth`) on an exchange without OAuth.

| Parameter | Type | Description |
|---|---|---|
| `client_id` | `Optional[str]` |  |
| `client_secret` | `Optional[str]` |  |
| `kwargs` | `Any` |  |

#### `exchange_code()`

```python
exchange_code(code: str, code_verifier: str, *, redirect_uri: Optional[str] = None) -> TokenSet
```

| Parameter | Type | Description |
|---|---|---|
| `code` | `str` |  |
| `code_verifier` | `str` |  |
| `redirect_uri` | `Optional[str]` |  |

#### `introspect()`

```python
introspect(token: str) -> Dict[str, Any]
```

| Parameter | Type | Description |
|---|---|---|
| `token` | `str` |  |

#### `member_client()`

```python
member_client(store: Any, member_key: str, **kwargs: Any) -> STX
```

A blocking `STX` acting for one connected member. See
`AsyncOAuthClient.member_client`.

| Parameter | Type | Description |
|---|---|---|
| `store` | `Any` |  |
| `member_key` | `str` |  |
| `kwargs` | `Any` |  |

#### `metadata()`

```python
metadata(*, refresh: bool = False) -> AuthorizationServerMetadata
```

| Parameter | Type | Description |
|---|---|---|
| `refresh` | `bool` |  |

#### `protected_resource()`

```python
protected_resource() -> Dict[str, Any]
```

#### `read_callback()`

```python
read_callback(pending_store: Any, callback: CallbackInput) -> CallbackResult
```

| Parameter | Type | Description |
|---|---|---|
| `pending_store` | `Any` |  |
| `callback` | `CallbackInput` |  |

#### `redeem_authorization()`

```python
redeem_authorization(callback: CallbackResult, *, store: Any = None, member_key: Union[str, Callable[[Optional[Dict[str, Any]]], str], None] = None) -> CompletedAuthorization
```

| Parameter | Type | Description |
|---|---|---|
| `callback` | `CallbackResult` |  |
| `store` | `Any` |  |
| `member_key` | `Union[str, Callable[[Optional[Dict[str, Any]]], str], None]` |  |

#### `refresh_token()`

```python
refresh_token(refresh_token: str) -> TokenSet
```

| Parameter | Type | Description |
|---|---|---|
| `refresh_token` | `str` |  |

#### `revoke()`

```python
revoke(token: str, token_type_hint: Optional[str] = None) -> None
```

| Parameter | Type | Description |
|---|---|---|
| `token` | `str` |  |
| `token_type_hint` | `Optional[str]` |  |

#### `unlink()`

```python
unlink(store: Any, member_key: str) -> bool
```

| Parameter | Type | Description |
|---|---|---|
| `store` | `Any` |  |
| `member_key` | `str` |  |

## `AppTokenProvider`

A cached app token. Mints on first use, re-mints `skew` seconds
before expiry, mints once for concurrent callers, and can be invalidated
after a 401 so the next call mints fresh.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `granted_scope` | `Optional[str]` | The scope the exchange granted the current token, once minted. |

### Methods

#### `fresh()`

```python
fresh() -> str
```

Invalidate and mint now.

#### `get()`

```python
get() -> str
```

A valid access token, minting when missing or near expiry.

#### `invalidate()`

```python
invalidate() -> None
```

Drop the cached token; the next `get` mints.

## `AppToken`

A cached app token, blocking. See `AppTokenProvider`.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `granted_scope` | `Optional[str]` |  |

### Methods

#### `fresh()`

```python
fresh() -> str
```

Invalidate and mint now.

#### `get()`

```python
get() -> str
```

A valid access token, minting when missing or near expiry.

#### `invalidate()`

```python
invalidate() -> None
```

## `TokenSet`

A token response (RFC 6749 section 5.1).

`expires_at` is Unix time in seconds, from `expires_in`.
`refresh_token` is absent on an app token. `id_token` is set when
`openid` was granted (verify it before trusting it; `Connect` does).
On a refresh, `terms_required` and `terms_url` say the member has new
STX terms to accept: the tokens work, placing orders waits until they do.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `access_token` | `str` |  |
| `refresh_token` | `Optional[str]` |  |
| `token_type` | `Optional[str]` |  |
| `expires_in` | `Optional[int]` |  |
| `expires_at` | `Optional[float]` |  |
| `scope` | `Optional[str]` |  |
| `id_token` | `Optional[str]` |  |
| `terms_required` | `bool` |  |
| `terms_url` | `Optional[str]` |  |
| `scopes` | `List[str]` | The granted scopes, canonical names. |

### Methods

#### `to_stored()`

```python
to_stored() -> StoredTokens
```

The `StoredTokens` a `TokenStore` keeps.

## `Authorization`

One prepared authorize redirect.

Send the member's browser to `url`; keep `state` and
`code_verifier` server-side until the callback.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `url` | `str` |  |
| `state` | `str` |  |
| `code_verifier` | `str` |  |
| `code_challenge` | `str` |  |
| `redirect_uri` | `str` |  |

## `CallbackResult`

A validated callback: the code and the pending sign-in its state
named (now used up).

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `code` | `str` |  |
| `pending` | `PendingAuthorization` |  |

## `CompletedAuthorization`

What `complete_authorization` returns.

`scopes` may be narrower than requested. `data` is what you passed to
`begin_authorization`; `member_key` the key the tokens were stored
under, when a store was given.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `tokens` | `TokenSet` |  |
| `scopes` | `List[str]` |  |
| `data` | `Optional[Dict[str, Any]]` |  |
| `member_key` | `Optional[str]` |  |

## `AuthorizationServerMetadata`

RFC 8414 metadata, as the exchange publishes it. `raw` is the whole
document.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `issuer` | `str` |  |
| `authorization_endpoint` | `str` |  |
| `token_endpoint` | `str` |  |
| `revocation_endpoint` | `Optional[str]` |  |
| `introspection_endpoint` | `Optional[str]` |  |
| `registration_endpoint` | `Optional[str]` |  |
| `scopes_supported` | `Tuple[str, ...]` |  |
| `grant_types_supported` | `Tuple[str, ...]` |  |
| `code_challenge_methods_supported` | `Tuple[str, ...]` |  |
| `token_endpoint_auth_methods_supported` | `Tuple[str, ...]` |  |
| `raw` | `Dict[str, Any]` |  |

### Methods

#### `from_dict()`

```python
from_dict(doc: Mapping[str, Any]) -> AuthorizationServerMetadata
```

| Parameter | Type | Description |
|---|---|---|
| `doc` | `Mapping[str, Any]` |  |

## `StoredTokens`

One member's tokens, as a `TokenStore` keeps them.

`expires_at` is Unix time in seconds (None when unknown). `scope`
is the granted scope, space-delimited. `terms_required` and
`terms_url` are set while the member has new STX terms to accept.
`to_dict()` and `from_dict()` give a JSON-friendly form to persist.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `access_token` | `str` |  |
| `refresh_token` | `Optional[str]` |  |
| `expires_at` | `Optional[float]` |  |
| `scope` | `Optional[str]` |  |
| `terms_required` | `bool` |  |
| `terms_url` | `Optional[str]` |  |

### Methods

#### `expires_within()`

```python
expires_within(seconds: float) -> bool
```

| Parameter | Type | Description |
|---|---|---|
| `seconds` | `float` |  |

#### `from_dict()`

```python
from_dict(data: Dict[str, Any]) -> StoredTokens
```

| Parameter | Type | Description |
|---|---|---|
| `data` | `Dict[str, Any]` |  |

#### `to_dict()`

```python
to_dict() -> Dict[str, Any]
```

## `MemoryTokenStore`

In-memory `TokenStore`. Fine for scripts, demos and tests; not durable.

### Methods

#### `delete()`

```python
delete(member_key: str) -> None
```

| Parameter | Type | Description |
|---|---|---|
| `member_key` | `str` |  |

#### `get()`

```python
get(member_key: str) -> Optional[StoredTokens]
```

| Parameter | Type | Description |
|---|---|---|
| `member_key` | `str` |  |

#### `set()`

```python
set(member_key: str, tokens: StoredTokens) -> None
```

| Parameter | Type | Description |
|---|---|---|
| `member_key` | `str` |  |
| `tokens` | `StoredTokens` |  |

## `PendingAuthorization`

One sign-in in progress, kept server-side under its `state`.

`expires_at` is Unix time in seconds. `data` is yours: a session id,
a user id, a return URL, carried to the callback.

### Attributes

| Attribute | Type | Description |
|---|---|---|
| `code_verifier` | `str` |  |
| `redirect_uri` | `str` |  |
| `scope` | `Optional[str]` |  |
| `expires_at` | `float` |  |
| `data` | `Optional[Dict[str, Any]]` |  |

### Methods

#### `from_dict()`

```python
from_dict(data: Dict[str, Any]) -> PendingAuthorization
```

| Parameter | Type | Description |
|---|---|---|
| `data` | `Dict[str, Any]` |  |

#### `to_dict()`

```python
to_dict() -> Dict[str, Any]
```

## `MemoryPendingAuthorizationStore`

In-memory `PendingAuthorizationStore`. Expired entries are dropped.

### Methods

#### `put()`

```python
put(state: str, pending: PendingAuthorization) -> None
```

| Parameter | Type | Description |
|---|---|---|
| `state` | `str` |  |
| `pending` | `PendingAuthorization` |  |

#### `take()`

```python
take(state: str) -> Optional[PendingAuthorization]
```

| Parameter | Type | Description |
|---|---|---|
| `state` | `str` |  |

## `MemberScopes`

Member scopes, granted through consent.

## `AppScopes`

App scopes, for `client_credentials` app tokens.

## `OAuthSessionAuth`

Auth provider for one connected member, over a `TokenStore`.

Attaches the member's access token, refreshes it when near expiry and
once on a 401 (storing the rotated pair), and treats a revoked grant as
final: the stored tokens are deleted and `STXGrantRevokedException`
raised. Refreshes run once per member across every client the same
`AsyncOAuthClient` made.

### Methods

#### `auth_headers()`

```python
auth_headers(method: str, path: str) -> Dict[str, str]
```

| Parameter | Type | Description |
|---|---|---|
| `method` | `str` |  |
| `path` | `str` |  |

#### `on_unauthorized()`

```python
on_unauthorized() -> bool
```

#### `refresh()`

```python
refresh(refresh_token: str) -> Optional[StoredTokens]
```

Refresh with rotation, once per member at a time. Returns the new
tokens, or None after a transient failure. Raises
`STXGrantRevokedException` (tokens deleted) or
`STXAccountPendingException` (tokens kept).

| Parameter | Type | Description |
|---|---|---|
| `refresh_token` | `str` |  |

#### `scopes()`

```python
scopes() -> List[str]
```

The granted scopes of the stored tokens.

#### `tokens()`

```python
tokens() -> StoredTokens
```

The member's tokens, refreshed first when at or near expiry.

#### `websocket_headers()`

```python
websocket_headers(reconnect: bool) -> Dict[str, str]
```

| Parameter | Type | Description |
|---|---|---|
| `reconnect` | `bool` |  |

## `OAuthAppAuth`

Auth provider acting as the app. A 401 re-mints once; every socket
reconnect mints fresh, never reusing the token of a connection that
just dropped.

### Methods

#### `auth_headers()`

```python
auth_headers(method: str, path: str) -> Dict[str, str]
```

| Parameter | Type | Description |
|---|---|---|
| `method` | `str` |  |
| `path` | `str` |  |

#### `on_unauthorized()`

```python
on_unauthorized() -> bool
```

#### `websocket_headers()`

```python
websocket_headers(reconnect: bool) -> Dict[str, str]
```

| Parameter | Type | Description |
|---|---|---|
| `reconnect` | `bool` |  |
