Sign in

    moonbitstack/moonapi/security does not have a README file

    SecurityEnforcer

    The runtime guard behind a named scheme: given the request context, the scopes the route required at this call site, and the verification time (Unix seconds), either the authenticated user or the 401/403 response to return. Registered by the App::secure_* functions; a documentation-only scheme (a bare add_security_scheme) has none, so it is described in the spec but not enforced — exactly FastAPI's split between a declared scheme and a wired dependency.

    ApiKey

    pub struct ApiKey {
    loc : ApiKeyIn
    name : String
    verify : (String) -> Bool
    }

    An API-key scheme: the parameter name the key arrives under, where it arrives, and the predicate that accepts a presented key. FastAPI's APIKeyHeader only extracts and leaves the check to a dependency; a route guard needs both, so the check travels with the scheme.

    ApiKey::cookie

    fn ApiKey::cookie(name : String, verify : (String) -> Bool) -> ApiKey

    An API key read from the cookie name (← APIKeyCookie).

    ApiKey::header

    fn ApiKey::header(name : String, verify : (String) -> Bool) -> ApiKey

    An API key read from the request header name (← APIKeyHeader).

    ApiKey::query

    fn ApiKey::query(name : String, verify : (String) -> Bool) -> ApiKey

    An API key read from the query parameter name (← APIKeyQuery).

    ApiKey::read

    fn ApiKey::read(self : ApiKey, ctx :
    Context
    ) -> String?

    The key this request presents for the scheme, None when it carries none.

    ApiKey::scheme

    fn ApiKey::scheme(self : ApiKey) -> SecurityScheme

    The apiKey scheme object this key is documented as.

    ApiKeyIn

    pub(all) enum ApiKeyIn {
    KeyHeader
    KeyQuery
    KeyCookie
    } derive(Eq)

    Where an API key travels (← APIKeyHeader / APIKeyQuery / APIKeyCookie).

    ApiKeyIn::equal

    fn ApiKeyIn::equal(ApiKeyIn, ApiKeyIn) -> Bool

    ApiKeyIn::not_equal

    fn ApiKeyIn::not_equal(x : ApiKeyIn, y : ApiKeyIn) -> Bool

    AuthenticatedUser

    pub(all) struct AuthenticatedUser {
    subject : String
    scopes : Array[String]
    claims : Map[String, Json]
    }

    The identity a verified token carries, injected into a protected handler (← the object FastAPI's get_current_user returns). subject is the sub claim, scopes the granted scopes, and claims the whole verified payload for anything else the handler needs (exp, custom claims).

    AuthenticatedUser::has_scope

    fn AuthenticatedUser::has_scope(self : AuthenticatedUser, scope : String) -> Bool

    Whether this user was granted scope.

    BasicAuth

    pub struct BasicAuth {
    realm : String
    verify : (
    HttpBasicCredentials
    ) -> Bool
    }

    An HTTP Basic scheme (← FastAPI's HTTPBasic): the realm named in the challenge, and the predicate that accepts a presented username/password.

    BasicAuth::challenge

    fn BasicAuth::challenge(self : BasicAuth) -> String

    The WWW-Authenticate value this scheme challenges with.

    BasicAuth::new

    Build an HTTP Basic scheme. A realm is optional and, when given, names the protection space in the challenge so a browser can tell one login from another.

    DeclaredScheme

    pub(all) struct DeclaredScheme {
    name : String
    kind : SecurityScheme
    description : String
    }

    A security scheme as an application declares it: the name routes refer to it by, the kind that shapes the emitted object, and the prose beside it (← the description= every FastAPI security class takes).

    DigestAuth

    pub struct DigestAuth {
    realm : String
    verify : (String) -> Bool
    }

    An HTTP Digest scheme (← FastAPI's HTTPDigest, which likewise carries the credential and no more): verify decides whether the Digest parameter string a client sent is acceptable. Computing the RFC 7616 response digest is the application's, since only it holds the password store.

    DigestAuth::challenge

    fn DigestAuth::challenge(self : DigestAuth) -> String

    The WWW-Authenticate value this scheme challenges with.

    DigestAuth::new

    fn DigestAuth::new(verify : (String) -> Bool, realm? : String) -> DigestAuth

    Build an HTTP Digest scheme, optionally naming the protection realm.

    OAuth2CodeBearer

    pub struct OAuth2CodeBearer {
    authorization_url : String
    token_url : String
    refresh_url : String
    secret : String
    }

    An OAuth2 authorization-code bearer scheme (← FastAPI's OAuth2AuthorizationCodeBearer): the authorization_url a browser is sent to, the token_url the code is exchanged at, an optional refresh_url, and the shared HS256 secret a presented token is verified against.

    OAuth2CodeBearer::new

    fn OAuth2CodeBearer::new(authorization_url : String, token_url : String, secret : String, refresh_url? : String) -> OAuth2CodeBearer

    Build an authorization-code bearer scheme. refresh_url is left out of the document when empty, since a provider that offers no refresh endpoint should not be described as having one.

    OAuth2CodeBearer::scheme

    fn OAuth2CodeBearer::scheme(self : OAuth2CodeBearer, scopes? : Array[(String, String)]) -> SecurityScheme

    The security scheme that describes this flow, advertising scopes.

    OAuth2PasswordBearer

    pub(all) struct OAuth2PasswordBearer {
    token_url : String
    secret : String
    }

    An OAuth2 password-bearer security scheme (← FastAPI's OAuth2PasswordBearer). Holds the token_url (the endpoint that issues tokens, surfaced to API docs) and the shared HS256 secret used to verify presented tokens.

    OAuth2PasswordBearer::authenticate

    Authenticate a request against this scheme and enforce scopes (← FastAPI's Security(get_current_user, scopes=[...])). On success returns the AuthenticatedUser; otherwise the response to return:

    • no bearer token -> 401 {"detail":"Not authenticated"}
    • malformed / bad-signature / expired / not-yet-valid token -> 401 {"detail":"Could not validate credentials"}
    • valid token missing a required scope -> 403 {"detail":"Not enough permissions"}

    Both 401s carry the challenge the required scopes describe — Bearer on its own, or Bearer scope="..." — so a client learns what it should have asked for.

    now_secs is the verification time (Unix seconds), passed in so the check stays pure and testable on every backend.

    OAuth2PasswordBearer::new

    fn OAuth2PasswordBearer::new(token_url : String, secret : String) -> OAuth2PasswordBearer

    Build a password-bearer scheme pointing at the token endpoint at token_url, verifying tokens with secret.

    OAuth2PasswordBearer::scheme

    fn OAuth2PasswordBearer::scheme(self : OAuth2PasswordBearer, scopes? : Array[(String, String)]) -> SecurityScheme

    Build the security scheme that describes this password-bearer flow (← the object FastAPI derives from OAuth2PasswordBearer). scopes are the (name, description) pairs advertised in the OpenAPI document.

    SecurityRequirement

    pub(all) struct SecurityRequirement {
    scheme : String
    scopes : Array[String]
    } derive(Eq)

    A security requirement on a route: the scheme name (which must match a name declared with App::add_security_scheme / App::secure_oauth2) and the scopes the caller must hold. Emitted as one {scheme: [scopes]} entry of the operation's OpenAPI security array.

    SecurityRequirement::equal

    SecurityRequirement::new

    fn SecurityRequirement::new(scheme : String, scopes? : Array[String]) -> SecurityRequirement

    Require scheme with the given scopes (default: none — authentication with no scope check). App::get(..., security=[SecurityRequirement::new("OAuth2",scopes=["items"])]) reads like FastAPI's Security(oauth2, scopes=["items"]).

    SecurityRequirement::not_equal

    SecurityRequirement::to_json

    This requirement as its OpenAPI security entry: {scheme: [scope, ...]}.

    SecurityScheme

    pub(all) enum SecurityScheme {
    OAuth2Password(token_url~ : String, scopes~ : Array[(String, String)])
    OAuth2Code(authorization_url~ : String, token_url~ : String, refresh_url~ : String, scopes~ : Array[(String, String)])
    HttpBearer(bearer_format~ : String)
    ApiKeyHeader(name~ : String)
    ApiKeyQuery(name~ : String)
    ApiKeyCookie(name~ : String)
    HttpBasic
    HttpDigest
    OpenIdConnect(url~ : String)
    }

    A security scheme describing how a client authenticates. Mirrors the OpenAPI scheme types: the two OAuth2 flows a browser or a service actually uses (password and authorization code), HTTP bearer / basic / digest, an API key in a header / query / cookie, and OpenID Connect discovery.

    SecurityScheme::to_openapi_20

    fn SecurityScheme::to_openapi_20(self : SecurityScheme) -> Json?

    This scheme as a Swagger 2.0 security definition, or None when 2.0 cannot express it. Swagger 2.0 has no http type: an HTTP bearer or digest is expressed as an apiKey in the Authorization header (the standard 2.0 workaround), Basic uses the basic type, and OAuth2 flows are the flat 2.0 form (flow + URLs at top level, accessCode being 2.0's name for the authorization-code flow). Swagger 2.0 apiKey only allows header/query, so a cookie key maps to a header. OpenID Connect has no 2.0 equivalent at all, and inventing one would describe an API that does not exist — so it is left out of the 2.0 document.

    SecurityScheme::to_openapi_3x

    fn SecurityScheme::to_openapi_3x(self : SecurityScheme) -> Json

    This scheme as an OpenAPI 3.0 / 3.1 security scheme object.

    SecurityScopes

    pub struct SecurityScopes {
    scopes : Array[String]
    }

    The scopes a route required at the point its security guard runs (← FastAPI's SecurityScopes). A guard reads them to check what the caller was granted and to build the WWW-Authenticate challenge a 401 owes the client.

    SecurityScopes::challenge

    fn SecurityScopes::challenge(self : SecurityScopes) -> String

    The WWW-Authenticate value a bearer challenge carries: Bearer on its own, or Bearer scope="a b" when the route requires scopes, which is how RFC 6750 §3 tells a client what it was missing.

    SecurityScopes::list

    fn SecurityScopes::list(self : SecurityScopes) -> Array[String]

    The required scopes, in declaration order.

    SecurityScopes::new

    fn SecurityScopes::new(scopes? : Array[String]) -> SecurityScopes

    The scopes required at one call site; empty means authentication only.

    SecurityScopes::scope_str

    fn SecurityScopes::scope_str(self : SecurityScopes) -> String

    The scopes as OAuth2's single space-delimited string (← SecurityScopes.scope_str).

    anonymous

    fn anonymous() -> AuthenticatedUser

    The caller an auto_error=false guard admits: no subject, no scopes, no claims. FastAPI's auto_error=False hands the route None and lets it decide; the route here sees a caller it knows nothing about.

    create_access_token

    fn create_access_token(subject : String, secret : String, now_secs : Int64, scopes? : Array[String], expires_in_secs? : Int64, extra? : Map[String, Json], wins? :
    Wins
    , clash? :
    OnClash
    [Map[String, Json]]) -> String

    Mint an HS256 access token for subject (← FastAPI's create_access_token). Stamps sub, iat (= now_secs), exp (= now_secs + expires_in_secs), and, when non-empty, scopes; extra merges in any further claims. Times are Unix seconds. secret is the shared HS256 key.

    extra adds claims. Where it names one the arguments already set — sub, iat, exp, scopes — the arguments win and saying it twice aborts, because a token whose subject is not the subject passed in is a mistake in the program rather than a choice about configuration. wins=Extra hands the decision to extra, and clash can silence it or take a callback.

    forbidden

    fn forbidden(detail : String) ->
    Response

    A 403 Forbidden with a {"detail": ...} body — returned when the token is valid but lacks a required scope. The token authenticated the caller, so this is authorization, not authentication: 403, not 401.

    hs256

    fn hs256(secret : String) ->
    Keyed

    The HS256 key a shared secret stands for.

    This is the whole of moonapi's relationship with cryptography: it names an algorithm and hands mooncred something that can sign. Nothing here knows what SHA-256 is, and a deployment that wants ES256 passes its own signer to mooncred without moonapi being involved.

    refuse

    Admit an unauthenticated caller, or refuse with denied — the one place auto_error is decided, so every guard reads the flag the same way.

    token_response

    fn token_response(access_token : String) ->
    Response

    The 200 token response body {"access_token": ..., "token_type": "bearer"} — the OAuth2 password-grant reply FastAPI's token endpoint returns.

    unauthorized

    fn unauthorized(detail : String, challenge? : String) ->
    Response

    A 401 Unauthorized with a WWW-Authenticate challenge and a {"detail": ...} body — FastAPI's response for a missing or invalid credential. RFC 9110 §15.5.2 requires the challenge, so it is a parameter: Basic realm="…" for Basic, Bearer scope="…" for a scoped bearer route.

    verify_bearer

    fn verify_bearer(ctx :
    Context
    , secret : String, scopes : SecurityScopes, now : Int64, auto_error : Bool) -> Result[AuthenticatedUser,
    Response
    ]

    The guard every JWT-verified scheme shares — the OAuth2 password and authorization-code flows, a plain HTTP bearer, and OpenID Connect all present the same Authorization: Bearer <jwt> credential. Pulls the token, verifies it against secret at now, then checks the scopes the route required.