auth.provider API
    Preparing search index...
    interface Client {
        allowedAudiences?: readonly string[];
        allowedAzpForFederationToken?: boolean;
        allowedGrantTypes?: readonly string[];
        allowedRedirectUris: readonly string[];
        allowedScopes: readonly string[];
        allowPlainPkce?: boolean;
        backchannelLogoutSessionRequired?: boolean;
        backchannelLogoutUri?: string;
        clientId: string;
        clientSecret?: string;
        defaultScopes?: readonly string[];
        firstParty?: boolean;
        frontchannelLogoutSessionRequired?: boolean;
        frontchannelLogoutUri?: string;
        postLogoutRedirectUris?: readonly string[];
        senderConstrained?: SenderConstraint;
        tokenEndpointAuthMethod: TokenEndpointAuthMethod;
    }
    Index
    allowedAudiences?: readonly string[]

    Audience URIs this client may receive tokens for.

    Consumers:

    • Token Exchange (RFC 8693) audience parameter selection — when this list is empty or undefined, only the client's own clientId is accepted as an audience target.
    • client_credentials grant default aud claim — selects the first entry (allowedAudiences[0]); when absent, falls back to the issuer (and ultimately omits aud when no issuer is configured).
    allowedAzpForFederationToken?: boolean

    When true, this client MAY call POST /oauth/federation/:name/token to retrieve the user's upstream federation access_token. Deny-by-default (deny-by-absence); must be explicitly opted in per client.

    Why default false: federation access_tokens grant access to the user's external resources (Google Calendar, GitHub API, etc.) — high blast radius. Opt-in prevents accidentally granting this power to a generic OAuth client registration that only needs auth.

    allowedGrantTypes?: readonly string[]

    Grant types this client is explicitly permitted to use.

    Enforced centrally by isGrantTypeAllowed — once at /oauth/token grant dispatch, before the concrete handler runs, and at /authorize against authorization_code — so every current and future grant inherits the check rather than opting in (#268). The central rule:

    • undefined (absent) → no restriction. Absence means the registration declared no policy, not that it declared an empty one; denying here would revoke every grant from every registration written before this field existed.
    • [] (empty) → every grant is denied. An empty allowlist names no grant type, so none can match it.
    • non-empty array → a grant is allowed iff its grant_type string appears in the list, compared exactly.

    A grant handler that declares requiresExplicitGrantAllowlist on its GrantHandler contract composes a stricter rule on top: dispatch denies by absence for that grant, so machine-to-machine access is never acquired by omission (#326). client_credentials (grants/clientCredentials.mts) and the WebAuthn grant (@o3co/auth-provider-webauthn) declare it. Both rules are enforced at dispatch — either can reject, and only the absent case distinguishes them; no handler carries its own copy of the check.

    allowedRedirectUris: readonly string[]
    allowedScopes: readonly string[]
    allowPlainPkce?: boolean

    Whether this client may use the RFC 7636 plain PKCE challenge method (#273).

    PKCE is mandatory for every authorization-code client and S256 is the only method the authorization server accepts — there is no server-wide setting that admits plain, because a deployment-wide one would quietly cover every client at once. This flag is the ONLY way to reach it, and it is deliberately per client: admitting plain is a named exception for a named registration, visible in the client record itself.

    plain stores the verifier as the challenge, so anything that can read the authorization request (browser history, a proxy log, a referrer) learns the verifier too and PKCE stops proving anything. Set this only for a legacy client that genuinely cannot compute SHA-256, and treat it as a migration deadline rather than a configuration.

    Absent and false are the same: S256 only. Like firstParty, the check is a strict === true, so an uncoerced "true" from YAML or an environment variable does not widen the policy.

    Surfaces through PublicClient (via Omit) and AuthenticatedClient (via the /token route's projection) so /authorize and /token read the same value.

    backchannelLogoutSessionRequired?: boolean
    backchannelLogoutUri?: string
    clientId: string
    clientSecret?: string

    Required when tokenEndpointAuthMethod is "client_secret_basic" or "client_secret_post". MUST be absent (undefined) when the method is "none". The ClientEntrySchema superRefine enforces both directions.

    defaultScopes?: readonly string[]

    What an omitted scope parameter grants (#396). Absent means the client has not declared a default: a scope-omitting request is then refused with invalid_scope whenever allowedScopes is non-empty — the old behavior granted the ENTIRE allowlist, making "forgot to send scope" the maximum grant (deny-by-absence, the #326/#363 shape). A client whose allowedScopes is empty is unaffected: there is nothing to over-grant, and scope-less deployments keep working.

    firstParty?: boolean

    Whether this client is first-party — i.e. operated by the same organisation as this authorization server, and trusted to receive the user's identity without the user being asked (#267).

    GET /authorize mints an authorization code as soon as the session is authenticated, with no consent step. That is defensible in a pure first-party OP and only there: a forced top-level navigation from an attacker's page makes a logged-in victim's browser mint a code, bound to the victim's session, delivered to the named client's registered redirect_uri. Registering one semi-trusted client turns the endpoint into an account-linking vector.

    So the assumption is now enforced rather than assumed: /authorize refuses any client that is not true here — one with no firstParty field and one carrying an explicit false alike. There is no opt-out: the one-time oauth.authorize.allowUnmarkedClients migration flag that admitted unmarked registrations (#317) was removed in #330, and a config still setting it fails at boot with migration instructions.

    This does not make /authorize safe against forced navigation for a client that is first-party — that is the accepted model, and the user-interaction step which changes it is consent (#284). What the flag closes is the escalation: a client that should never have been trusted with a silent code cannot be registered into that position by accident.

    frontchannelLogoutSessionRequired?: boolean
    frontchannelLogoutUri?: string
    postLogoutRedirectUris?: readonly string[]
    senderConstrained?: SenderConstraint

    Sender-constraint requirement for this client. See Wave 2 Token- binding Cluster spec §4.8. Surfaces through PublicClient (via Omit) and AuthenticatedClient (via the /token route's projection).

    tokenEndpointAuthMethod: TokenEndpointAuthMethod

    Token-endpoint authentication method. REQUIRED — the schema rejects client entries that omit it; deployments upgrading from v0.5.0 must add the field explicitly per the v0.5.1 migration guide.

    Public clients ("none") MUST present PKCE/S256 at /authorize; confidential clients ("client_secret_basic" / "client_secret_post") MUST present a clientSecret and use the matching transport at /token.