Skip to content

Authentication strategies

FastAPIAuth and its strategies. In fastapi_alcazaba.auth; JWTStrategy, OAuth2Strategy, OIDCStrategy, SAMLStrategy and UnkeyStrategy in fastapi_alcazaba.auth.strategies.jwt, .oauth2, .oidc, .saml and .unkey, with their extras.

In the guide: Strategies. In an app: the example.

fastapi_alcazaba.auth.FastAPIAuth

FastAPIAuth(*strategies: Strategy, metrics: Metrics | None = None, audit: FastAPIAudit | None = None, actor: Actor | None = None)

The strategies of an app, and the dependencies that authenticate with them.

auth = FastAPIAuth(JWTStrategy(...), SessionStrategy(...))

@app.get("/me")
async def me(user: Annotated[User, Depends(auth.authenticate("jwt", "session"))]): ...

A request is authenticated once: the dependencies of a route that ask for the same strategies get the user the first one found (an API key isn't verified twice).

Parameters:

Name Type Description Default
strategies Strategy

the strategies, by their names; more with use.

()
metrics Metrics | None

where to count the outcomes (fastapi_alcazaba.auth); FastAPICache's by default.

None
audit FastAPIAudit | None

the audit trail that records the failed authentications (auth.failure), the logins of OAuth2, OpenID Connect and SAML (auth.login) and the requests without a scope they need (auth.scope), with the request's route and client; not every request authenticated.

None
actor Actor | None

who the user a strategy found is, in those events: actor(user), an ID. By default the user itself when it's an ID, else its sub or id.

None

use

use(strategy: S) -> S

Add a strategy, under its name; returns it, with its own type (github.redirect()).

authenticate

authenticate(*names: str, optional: bool = False, scopes: Collection[str] = ()) -> Callable[..., Awaitable[Any]]

A dependency that returns the user of the first of these strategies that finds one (all of them, in the order they were added, without names). Without one, a 401 with their challenges (a WebSocket is closed with 1008), or None when optional.

The user's credentials must grant scopes, and those of Security(dependency, scopes=[...]) where it's used (FastAPI's, which the OpenAPI schema shows); one missing is a 403 with WWW-Authenticate: Bearer error="insufficient_scope".

user async

user(connection: HTTPConnection, *names: str) -> Any

The user of the first of these strategies (all of them without names) that finds one; None when none does. For connections that don't go through a dependency.

fastapi_alcazaba.auth.Strategy

Strategy(name: str)

Bases: ABC

One way to tell who makes a request. Subclasses implement authenticate.

Parameters:

Name Type Description Default
name str

what FastAPIAuth.authenticate calls it by.

required

authenticate abstractmethod async

authenticate(connection: HTTPConnection) -> Any

The user who makes the request (a Request or a WebSocket); None when it doesn't carry this strategy's credentials. Raises AuthenticationFailed when they're there but wrong.

openapi_scheme

openapi_scheme() -> Callable[..., Any] | None

The FastAPI security scheme that the OpenAPI schema shows for it, for the docs' Authorize button (HTTPBearer(auto_error=False)...); None for none.

grant

grant(connection: HTTPConnection, scopes: str | Collection[str]) -> None

Record the scopes that the request's credentials grant (a token's scope, a key's), for the routes that need some: call it in authenticate, with the user found.

fastapi_alcazaba.auth.AuthenticationFailed

Bases: Exception

Raised by a strategy whose credentials are in the request but wrong: an expired token, a forged one, an OAuth2 login the provider refused. The message is logged, never sent.

fastapi_alcazaba.auth.granted_scopes

granted_scopes(connection: HTTPConnection) -> frozenset[str] | None

The scopes the credentials of the request's user grant; None when the strategy that found it grants none (a session, a login).

fastapi_alcazaba.auth.strategies.jwt.JWTStrategy

JWTStrategy(key: Secrets | None = None, *, jwks_url: str | None = None, algorithms: Sequence[str] = ('HS256',), audience: str | None = None, issuer: str | None = None, leeway: float = 0, required: Sequence[str] = ('exp', 'sub'), verify: Callable[[dict[str, Any]], Any] | None = None, blocklist: TokenBlocklist | None = None, cookie: str | None = None, signing_key: Secrets | None = None, jwks_lifespan: float = 300, scope_claims: Sequence[str] = ('scope', 'scp'), name: str = 'jwt')

Bases: Strategy

Authenticates by a JWT in Authorization: Bearer <token> (or a cookie), verified with key or the provider's JWKS. The signature, exp, nbf and the claims of required are always checked, aud and iss when given; a token with an aud is refused when no audience is, as it's meant for some other service. alg: none is never accepted.

Parameters:

Name Type Description Default
key Secrets | None

what verifies the tokens: a secret of at least 32 bytes for HS256, HS384 and HS512 (a list rotates them: the first one signs, all of them verify), or a public key (PEM) for RS256, ES256, EdDSA...

None
jwks_url str | None

the provider's JWKS instead of key: its keys are fetched, by the kid of each token, and kept for jwks_lifespan seconds. Needs audience and issuer: the provider signs the tokens of every app it serves with the same keys.

None
algorithms Sequence[str]

those accepted; the first one signs what issue makes.

('HS256',)
audience str | None

the aud the tokens must have.

None
issuer str | None

the iss the tokens must have.

None
leeway float

seconds of tolerance on exp and nbf, for clocks out of step; the blocklist's leeway must be at least as long.

0
required Sequence[str]

claims a token must have.

('exp', 'sub')
verify Callable[[dict[str, Any]], Any] | None

verify(claims): the user, or None to refuse the token. Async, or a plain function, which runs in a thread. By default the user is the claims themselves.

None
blocklist TokenBlocklist | None

refuses the tokens revoked there (by jti, and by sub and iat for "log out everywhere"); tokens then need a jti and an iat.

None
cookie str | None

also reads the token from this cookie, for browsers.

None
signing_key Secrets | None

what issue signs with, for asymmetric algorithms (the private key); by default the first key, for HMAC ones.

None
scope_claims Sequence[str]

the claims whose scopes the token grants, for the routes that need some (Security(..., scopes=[...])): RFC 9068's scope, a string of them separated by spaces, and scp, a list (Entra ID, Okta); permissions for Auth0's.

('scope', 'scp')
name str

what FastAPIAuth.authenticate calls it by.

'jwt'

decode async

decode(token: str) -> dict[str, Any]

The claims of token, verified; raises AuthenticationFailed when it isn't valid.

issue

issue(subject: str, *, expires_in: int = 900, **claims: Any) -> str

A token for subject, valid for expires_in seconds, with iat, exp, a random jti, iss and aud when they're set, and claims.

fastapi_alcazaba.auth.strategies.oauth2.OAuth2Strategy

OAuth2Strategy(*, name: str, authorization_url: str, token_url: str, client_id: str, client_secret: str | SecretStr, redirect_uri: str, scopes: Sequence[str] = (), userinfo_url: str | None = None, verify: Callable[[dict[str, Any], dict[str, Any] | None], Any] | None = None, client_auth: Literal['basic', 'post'] = 'basic', params: Mapping[str, str] | None = None, secure: bool = True, timeout: float = 10.0, client: Any = None)

Bases: AuthorizationCodeFlow

Authenticates the requests that come back from the provider's login page (the callback): checks their state, exchanges their code for tokens, reads the user's profile, and returns the user that verify makes of them. Other requests carry nothing for it (None): it only goes on the callback route.

redirect() starts the login: it sends the browser to the provider with a random state and a PKCE challenge, whose verifier waits in a short-lived cookie (HttpOnly, SameSite=Lax, __Host- when Secure). A callback without that cookie or with another state is refused: nobody can log a user in with their own account (login CSRF) nor use a stolen code.

For OpenID Connect providers, OIDCStrategy also verifies the ID token they send.

Parameters:

Name Type Description Default
name str

what FastAPIAuth.authenticate calls it by, and the cookie's name.

required
authorization_url str

the provider's login page.

required
token_url str

where codes are exchanged for tokens.

required
client_id str

the app's id at the provider.

required
client_secret str | SecretStr

the app's secret at the provider.

required
redirect_uri str

the callback's absolute URL, as registered at the provider.

required
scopes Sequence[str]

what to ask the provider for.

()
userinfo_url str | None

where to read the user's profile with the access token; None for none.

None
verify Callable[[dict[str, Any], dict[str, Any] | None], Any] | None

verify(tokens, profile): your user, or None to refuse the login. tokens is the token endpoint's answer (access_token, refresh_token...), profile the JSON of userinfo_url (None without it). An id_token among them isn't verified: use OIDCStrategy to rely on its claims. Async, or a plain function, which runs in a thread. By default the user is the profile (or the tokens without it).

None
client_auth Literal['basic', 'post']

how the app authenticates to the token endpoint: basic (HTTP Basic, RFC 6749's default) or post (in the form).

'basic'
params Mapping[str, str] | None

more parameters for the login page (prompt, access_type...).

None
secure bool

the cookie is Secure (browsers accept it from http://localhost too).

True
timeout float

seconds for each call to the provider.

10.0
client Any

the httpx2 client to call the provider with; one of its own by default.

None

fastapi_alcazaba.auth.strategies.oidc.OIDCStrategy

OIDCStrategy(*, name: str, issuer: str, client_id: str, client_secret: str | SecretStr, redirect_uri: str, scopes: Sequence[str] = ('openid', 'email', 'profile'), verify: Callable[[dict[str, Any], dict[str, Any] | None], Any] | None = None, userinfo: bool = False, max_age: int | None = None, algorithms: Sequence[str] = ('RS256', 'ES256', 'PS256', 'EdDSA'), leeway: float = 60, authorization_url: str | None = None, token_url: str | None = None, userinfo_url: str | None = None, jwks_url: str | None = None, jwks_lifespan: float = 3600, client_auth: Literal['basic', 'post'] = 'basic', params: Mapping[str, str] | None = None, secure: bool = True, timeout: float = 10.0, client: Any = None)

Bases: AuthorizationCodeFlow

Authenticates the callback of an OpenID Connect login: exchanges its code for tokens, verifies the ID token, and returns the user that verify makes of its claims.

The ID token is refused unless it's signed with one of algorithms by a key of the provider's JWKS (never none, never with the client secret), comes from issuer, is meant for this app (aud has client_id, and azp is it when there are several audiences or an azp), hasn't expired (exp) nor was issued in the future (iat), carries the nonce the login sent, the at_hash of the access token when it has one, and an auth_time within max_age when it's set. A provider that sends no ID token is refused.

Its endpoints and keys come from the issuer's discovery document (/.well-known/openid-configuration), read by await discover() in the lifespan; whose issuer must be issuer exactly. Endpoints given here aren't read from it, and with all of them (authorization_url, token_url, jwks_url) there's nothing to discover.

Parameters:

Name Type Description Default
name str

what FastAPIAuth.authenticate calls it by, and the cookie's name.

required
issuer str

the provider's issuer identifier, as its ID tokens have it in iss.

required
client_id str

the app's id at the provider: the ID tokens' audience.

required
client_secret str | SecretStr

the app's secret at the provider.

required
redirect_uri str

the callback's absolute URL, as registered at the provider.

required
scopes Sequence[str]

what to ask for; openid is required.

('openid', 'email', 'profile')
verify Callable[[dict[str, Any], dict[str, Any] | None], Any] | None

verify(claims, userinfo): your user, or None to refuse the login. claims are the ID token's, verified; userinfo the userinfo endpoint's (with userinfo=True; None otherwise), whose sub is the ID token's. Async, or a plain function, which runs in a thread. By default the user is the claims, with the userinfo's added.

None
userinfo bool

also read the userinfo endpoint, for claims the ID token doesn't carry.

False
max_age int | None

seconds since the user last entered their credentials at the provider, at most: sent as max_age, and checked in auth_time.

None
algorithms Sequence[str]

the signature algorithms accepted.

('RS256', 'ES256', 'PS256', 'EdDSA')
leeway float

seconds of tolerance on exp, iat and auth_time, for clocks out of step.

60
authorization_url str | None

the login page, instead of discovery's.

None
token_url str | None

where codes are exchanged for tokens, instead of discovery's.

None
userinfo_url str | None

the userinfo endpoint, instead of discovery's.

None
jwks_url str | None

the provider's keys, instead of discovery's.

None
jwks_lifespan float

seconds the keys are kept before they're fetched again.

3600
client_auth Literal['basic', 'post']

how the app authenticates to the token endpoint: basic or post.

'basic'
params Mapping[str, str] | None

more parameters for the login page (prompt, login_hint...).

None
secure bool

the cookie is Secure (browsers accept it from http://localhost too).

True
timeout float

seconds for each call to the provider.

10.0
client Any

the httpx2 client to call the provider with; one of its own by default.

None

discover async

discover() -> None

Read the provider's discovery document: its endpoints and where its keys are, for those not given. Raises RuntimeError when it can't be read, or doesn't fit.

verify_id_token async

verify_id_token(id_token: str, *, nonce: str, access_token: str | None = None) -> dict[str, Any]

The claims of id_token, verified as the class says; AuthenticationFailed when it isn't valid. nonce is the login's, and access_token the one it came with.

fastapi_alcazaba.auth.strategies.oauth2.AuthorizationCodeFlow

AuthorizationCodeFlow(*, name: str, client_id: str, client_secret: str | SecretStr, redirect_uri: str, scopes: Sequence[str], client_auth: Literal['basic', 'post'], params: Mapping[str, str] | None, secure: bool, timeout: float, client: Any)

Bases: Strategy

The authorization code flow with PKCE, which OAuth2Strategy and OIDCStrategy share: redirect() sends the browser to the provider, and the callback's request is authenticated by its code. Subclasses set the endpoints and implement _finish.

redirect() starts the login: it sends the browser to the provider with a random state and a PKCE challenge, whose verifier waits in a short-lived cookie (HttpOnly, SameSite=Lax, __Host- when Secure). A callback without that cookie or with another state is refused: nobody can log a user in with their own account (login CSRF) nor use a stolen code.

redirect

redirect(**params: str) -> RedirectResponse

The response that sends the browser to the provider's login page, with params added to the URL; it sets the cookie that the callback checks.

fastapi_alcazaba.auth.SessionStrategy

SessionStrategy(load_user: Callable[[Any], Any], *, key: str = 'user_id', name: str = 'session')

Bases: Strategy

Authenticates by the user id that login kept in the session, with ServerSessionMiddleware (or Starlette's SessionMiddleware).

Parameters:

Name Type Description Default
load_user Callable[[Any], Any]

load_user(user_id): the user, or None when there's none any more (then authentication fails). Async, or a plain function, which runs in a thread.

required
key str

where in the session the user id is kept.

'user_id'
name str

what FastAPIAuth.authenticate calls it by.

'session'

login

login(connection: HTTPConnection, user_id: Any) -> None

Keep user_id (a JSON value) in the session: the next requests are that user's. With ServerSessionMiddleware the session gets a new id, so that one someone planted in the browser before (session fixation) doesn't log them in too.

logout

logout(connection: HTTPConnection) -> None

Empty the session; with ServerSessionMiddleware it's also deleted from the backend.

fastapi_alcazaba.auth.HTTPBearerStrategy

HTTPBearerStrategy(verify: Callable[[str], Any], *, realm: str | None = None, scopes_of: Callable[[Any], str | Collection[str]] | None = None, name: str = 'bearer')

Bases: Strategy

Authenticates by the token of Authorization: Bearer <token>, which verify turns into the user. A token that isn't RFC 6750's syntax, or longer than 4 KiB, fails without reaching verify.

Parameters:

Name Type Description Default
verify Callable[[str], Any]

verify(token): the user, or None when the token is unknown or expired (then authentication fails). Async, or a plain function, which runs in a thread. Keep digests of the tokens, not the tokens: FastAPIAPIKeys does it for API keys.

required
realm str | None

the realm of the 401's challenge, Bearer realm="...".

None
scopes_of Callable[[Any], str | Collection[str]] | None

scopes_of(user): the scopes the token grants (its row's, the scope of the provider's introspection), for the routes that need some; none without it.

None
name str

what FastAPIAuth.authenticate calls it by.

'bearer'

fastapi_alcazaba.auth.strategies.saml.SAMLStrategy

SAMLStrategy(*, entity_id: str, acs_url: str, idp_entity_id: str, idp_sso_url: str, idp_certificate: str | Sequence[str], verify: Callable[[SAMLProfile], Any] | None = None, name_id_format: str | None = None, clock_skew: float = 60, cache: Cache | None = None, name: str = 'saml')

Bases: Strategy

Authenticates the requests the IdP posts to the Assertion Consumer Service (the ACS): checks the signature of their response with the IdP's certificate, and that it answers the login this browser started; the user is what verify makes of the profile. Other requests carry nothing for it (None): it only goes on the ACS route.

redirect() starts the login (SP-initiated: logins the IdP starts by itself aren't accepted). The id of its request waits in a short-lived cookie (__Host-, HttpOnly, Secure, and SameSite=None: the IdP posts to the ACS from its own site), and the answer must carry it.

Checked on what the signature covers: the issuer, the audience (entity_id), the recipient (acs_url), the request it answers, when it's valid (with clock_skew), and that it's a bearer assertion. Each assertion works once: its id is kept in the cache's backend until it expires, so a copy is refused by every process. Not supported: encrypted assertions, signed requests, single logout.

Parameters:

Name Type Description Default
entity_id str

this app's entity id at the IdP (its "SP entity id", or "audience").

required
acs_url str

the ACS's absolute URL, where the IdP posts its answers.

required
idp_entity_id str

the IdP's entity id, the issuer of its answers.

required
idp_sso_url str

the IdP's single sign-on URL (HTTP-Redirect binding).

required
idp_certificate str | Sequence[str]

the IdP's signing certificate (PEM); a list while it rotates them. signxml refuses expired ones.

required
verify Callable[[SAMLProfile], Any] | None

verify(profile): your user, or None to refuse the login. Async, or a plain function, which runs in a thread. By default the user is the SAMLProfile.

None
name_id_format str | None

the NameID format to ask for (urn:oasis:names:tc:SAML:1.1:nameid- format:emailAddress...); the IdP's default without one.

None
clock_skew float

seconds of tolerance on the validity times, for clocks out of step.

60
cache Cache | None

the Cache whose backend remembers the assertions used; FastAPICache's by default. When it fails, logins are refused.

None
name str

what FastAPIAuth.authenticate calls it by, and the cookie's name.

'saml'

redirect

redirect(relay_state: str | None = None) -> RedirectResponse

The response that sends the browser to the IdP with an AuthnRequest, and sets the cookie that the ACS checks. relay_state comes back in the profile (a page to return to; check it before redirecting there).

metadata

metadata() -> str

This app's metadata, for the IdP: its entity id, ACS and NameID format.

fastapi_alcazaba.auth.strategies.saml.SAMLProfile dataclass

SAMLProfile(name_id: str, name_id_format: str | None, issuer: str, session_index: str | None, attributes: Mapping[str, list[str]] = dict(), relay_state: str | None = None)

Who the IdP says logged in, from its signed assertion.

fastapi_alcazaba.auth.strategies.unkey.UnkeyStrategy

UnkeyStrategy(unkey: UnkeyClient, verify: Callable[[UnkeyKey], Any] | None = None, *, permissions: str | None = None, cost: int = 1, ratelimits: Sequence[str] = (), keyspaces: Sequence[str] = (), tags: Sequence[str] = (), header: str | None = None, credits_status: int = HTTP_429_TOO_MANY_REQUESTS, name: str = 'unkey', metrics: Metrics | None = None)

Bases: Strategy

Authenticates by an API key that Unkey verifies: Authorization: Bearer <key> by default, or the header header. Each request is one verification, which spends the key's credits and counts in its rate limits: nothing is cached, so a key deleted or disabled is refused at once (Unkey takes up to 30 seconds to spread a deletion).

What Unkey answers:

  • VALID: the user is verify(key) (an UnkeyKey), the key itself without verify.
  • NOT_FOUND, DISABLED, EXPIRED: authentication fails (a 401, or the next strategy).
  • INSUFFICIENT_PERMISSIONS, FORBIDDEN: a 403; the key is valid, it may not do this.
  • RATE_LIMITED: a 429 with Retry-After; USAGE_EXCEEDED (no credits left): a 429, or a 402 with credits_status=402.
  • Unkey down, slow (timeout) or limiting the workspace: a 503. A root key without the verify permission: UnkeyError, a 500.

A route that needs other permissions or costs more credits has a strategy of its own, with another name and the same client: UnkeyStrategy(verifier, name="unkey-admin", permissions="admin.*").

Parameters:

Name Type Description Default
unkey UnkeyClient

the client, with a root key that may verify keys.

required
verify Callable[[UnkeyKey], Any] | None

verify(key): the user of a valid key (load it by key.external_id), or None to refuse it. Async, or a plain function, which runs in a thread.

None
permissions str | None

what the key must have, in Unkey's query syntax: "documents.read AND users.view".

None
cost int

credits each verification spends (0 for none); a key without a credit limit spends nothing.

1
ratelimits Sequence[str]

names of the key's (or its identity's) rate limits to apply besides those it applies by itself.

()
keyspaces Sequence[str]

the keyspaces (APIs) the key must belong to; any by default.

()
tags Sequence[str]

tags for Unkey's analytics, key=value.

()
header str | None

the header the key comes in (X-API-Key), instead of Authorization: Bearer.

None
credits_status int

what a key out of credits gets: 429, or 402 (Payment Required).

HTTP_429_TOO_MANY_REQUESTS
name str

what FastAPIAuth.authenticate calls it by.

'unkey'
metrics Metrics | None

where to count the verifications (fastapi_alcazaba.auth.unkey), by code; FastAPICache's by default.

None

fastapi_alcazaba.auth.strategies.unkey.UnkeyClient

UnkeyClient(root_key: str | SecretStr, *, url: str = UNKEY_URL, timeout: float = 2.0, client: AsyncClient | None = None)

Calls Unkey's API with a root key. Each call has a deadline, and Unkey a circuit breaker: when it's down, calls fail at once instead of each waiting for it.

Give each use its own root key, with only the permissions it needs: one that may only verify keys for UnkeyStrategy, another that may create and delete them for UnkeyKeys.

Parameters:

Name Type Description Default
root_key str | SecretStr

the root key (a SecretStr from your settings).

required
url str

Unkey's API.

UNKEY_URL
timeout float

seconds for each call, the answer included.

2.0
client AsyncClient | None

the httpx2 client to call it with (a proxy, a transport of your own); one of its own by default, with a ResilientTransport. aclose() closes only its own.

None

call async

call(operation: str, body: Mapping[str, Any]) -> tuple[dict[str, Any], str | None]

POST /v2/<operation> with body: Unkey's data, and its ID of the request. Raises UnkeyUnavailable when it doesn't answer (or answers 5xx or 429), and UnkeyError when it refuses the call.

aclose async

aclose() -> None

Closes the client it made (not one it was given): in the lifespan, at shutdown.

fastapi_alcazaba.auth.strategies.unkey.UnkeyKeys

UnkeyKeys(unkey: UnkeyClient, api_id: str, *, prefix: str | None = None)

Issues and revokes the keys of an Unkey API: for the page where your users make theirs.

Parameters:

Name Type Description Default
unkey UnkeyClient

the client, with a root key that may create and delete keys of api_id.

required
api_id str

the Unkey API the keys belong to.

required
prefix str | None

what the keys start with (sk_live), up to 16 letters, digits and _.

None

create async

create(*, external_id: str | None = None, name: str | None = None, expires: datetime | None = None, credits_left: int | None = None, refill: CreditRefill | None = None, ratelimits: Sequence[KeyRatelimit] = (), permissions: Sequence[str] = (), roles: Sequence[str] = (), meta: Mapping[str, Any] | None = None) -> CreatedKey

A new key, whose key to show its owner once. external_id ties it to its owner in your app (verifications give it back); credits_left are the uses it has (None for no limit), which refill gives back; expires is when it stops working.

delete async

delete(key_id: str, *, permanent: bool = False) -> None

Delete a key: its verifications fail within 30 seconds. permanent erases it whole (its hash and metadata too); otherwise Unkey can still bring it back.

fastapi_alcazaba.auth.strategies.unkey.UnkeyKey dataclass

UnkeyKey(key_id: str, name: str | None = None, external_id: str | None = None, meta: Mapping[str, Any] = dict(), permissions: tuple[str, ...] = (), roles: tuple[str, ...] = (), credits: int | None = None, expires: datetime | None = None, ratelimits: tuple[RatelimitState, ...] = (), request_id: str | None = None)

A key Unkey verified, what UnkeyStrategy gives verify (or the endpoint).

Parameters:

Name Type Description Default
key_id str

Unkey's ID of the key, to update or delete it.

required
name str | None

the key's name.

None
external_id str | None

the ID of its identity in your app (the user, the tenant), when it has one.

None
meta Mapping[str, Any]

what was stored with the key.

dict()
permissions tuple[str, ...]

its permissions, those of its roles included (when some were checked).

()
roles tuple[str, ...]

its roles (when permissions were checked).

()
credits int | None

the credits left; None when it has no limit.

None
expires datetime | None

when it expires; None when it doesn't.

None
ratelimits tuple[RatelimitState, ...]

its rate limits, as this verification left them.

()
request_id str | None

Unkey's ID of the verification, for its support.

None

fastapi_alcazaba.auth.strategies.unkey.RatelimitState dataclass

RatelimitState(name: str, limit: int, remaining: int, reset: float, exceeded: bool)

A rate limit of a key, as its verification left it.

Parameters:

Name Type Description Default
name str

the limit's name.

required
limit int

requests per window.

required
remaining int

requests left in the window.

required
reset float

seconds until the window starts again.

required
exceeded bool

whether this request went past it.

required

fastapi_alcazaba.auth.strategies.unkey.CreatedKey dataclass

CreatedKey(key: SecretStr, key_id: str)

A key UnkeyKeys.create made.

Parameters:

Name Type Description Default
key SecretStr

the key, to show its owner once: Unkey only keeps its hash.

required
key_id str

Unkey's ID of the key, to keep, to delete it later.

required

fastapi_alcazaba.auth.strategies.unkey.KeyRatelimit dataclass

KeyRatelimit(name: str, limit: int, duration: timedelta, auto_apply: bool = True)

A rate limit to give a new key: limit requests per duration.

Parameters:

Name Type Description Default
name str

what verifications name it by (3 to 128 characters).

required
limit int

requests per window.

required
duration timedelta

the window, a second or more.

required
auto_apply bool

applied to every verification, without naming it.

True

fastapi_alcazaba.auth.strategies.unkey.CreditRefill dataclass

CreditRefill(amount: int, interval: Literal['daily', 'monthly'] = 'monthly', day: int | None = None)

Credits given back to a key every day or month.

Parameters:

Name Type Description Default
amount int

the credits it gets back.

required
interval Literal['daily', 'monthly']

daily or monthly.

'monthly'
day int | None

for monthly refills, the day of the month (1 to 31).

None

fastapi_alcazaba.auth.strategies.unkey.UnkeyError

UnkeyError(status: int | None, detail: str, request_id: str | None = None)

Bases: RuntimeError

Unkey refused a call: a root key without the permission, a request it can't take. A mistake to fix, not a refusal of the key: a 500.

Parameters:

Name Type Description Default
status int | None

the HTTP status Unkey answered.

required
detail str

Unkey's explanation.

required
request_id str | None

Unkey's ID of the request, for its support.

None

fastapi_alcazaba.auth.strategies.unkey.UnkeyUnavailable

UnkeyUnavailable(status: int | None, detail: str, request_id: str | None = None)

Bases: UnkeyError

Unkey didn't answer, answered too slowly, with a 5xx, or limited the workspace (a 429): the requests are refused with a 503.