Documentation
¶
Overview ¶
Package oauthprovider makes the app an OAuth 2.1 authorization server and OpenID Connect provider: other applications ("clients") send their users here to sign in, and receive an authorization code, access and refresh tokens, and an ID token. It is better-auth's oauth-provider plugin.
It is opt-in and its own module. The protocol endpoints (authorize, token, userinfo, introspection, revocation, dynamic registration, discovery and the key set) are served by Plugin.Handler in the wire format the RFCs fix; each one is a self-served auth operation underneath (auth.OpInfo .SelfServed), so it runs the same pipeline as every other operation: rate limits, hooks, tracing. The management operations (consent, the owner's client registry, the user's consents) are ordinary operations every transport mounts under /oauthprovider.
Security rules, each pinned by a test: a redirect URI matches a registered one exactly; PKCE (S256) is mandatory for public clients and the default for every client; a code is single use, bound to its client, redirect URI and PKCE verifier, and its reuse revokes every token issued from it; a refresh token rotates on every use and the reuse of a rotated one revokes its whole family; client secrets are compared in constant time; every secret (client secret, code, access and refresh token) is stored as its SHA-256 and shown once.
Index ¶
- Constants
- Variables
- func CertificateThumbprint(cert *x509.Certificate) string
- func IsCIMDClientID(id string) bool
- func ValidCIMDClientID(raw string) bool
- type AccessToken
- type AccessTokenClaimsFunc
- type AccessTokenClaimsInfo
- type AuthorizeRequest
- type AuthorizeResult
- type CIMDFetchPolicy
- type CIMDFetcher
- type CIMDOption
- func CIMDAllow(fn func(clientID string) bool) CIMDOption
- func CIMDFetch(f CIMDFetcher) CIMDOption
- func CIMDMaxCacheEntries(n int) CIMDOption
- func CIMDOnClientCreated(fn func(ctx context.Context, c Client, document json.RawMessage)) CIMDOption
- func CIMDOnClientRefreshed(fn func(ctx context.Context, c, previous Client, document json.RawMessage)) CIMDOption
- func CIMDOriginBoundFields(fields ...string) CIMDOption
- func CIMDPolicy(pol CIMDFetchPolicy) CIMDOption
- func CIMDProfileMCP() CIMDOption
- func CIMDRevalidate(d time.Duration) CIMDOption
- func CIMDTransport(...) CIMDOption
- type CIMDResponse
- type ClaimsFunc
- type ClaimsInfo
- type Client
- type ClientAuthMethod
- type ClientCredentials
- type ClientDiscoverer
- type ClientIDRequest
- type ClientList
- type ClientMetadata
- type ClientPrivilegesFunc
- type ClientResourceLink
- type ClientSpec
- type Confirmation
- type Consent
- type ConsentIDRequest
- type ConsentList
- type ConsentRequest
- type ContinueRequest
- type EndSessionConfirmRequest
- type EndSessionRequest
- type EndSessionResult
- type Extension
- type ExtensionClaims
- type ExtensionGrant
- type GateInfo
- type GrantDecision
- type GuardedToken
- type InitialAccessTokenFunc
- type IntrospectRequest
- type Introspection
- type IssueRequest
- type JWKSFetcher
- type Metadata
- type Option
- func AccessTokenClaims(fn AccessTokenClaimsFunc) Option
- func AccessTokenTTL(d time.Duration) Option
- func AdvertisedClaims(claims ...string) Option
- func AdvertisedScopes(scopes ...string) Option
- func AllowDynamicRegistration() Option
- func AllowPublicClientPrelogin() Option
- func AllowUnauthenticatedRegistration() Option
- func AssertionMaxLifetime(d time.Duration) Option
- func BackchannelHTTPClient(c *http.Client) Option
- func BasePath(path string) Option
- func CacheResources(ttl time.Duration) Option
- func CacheResourcesIn(kv auth.SecondaryStorage, ttl time.Duration) Option
- func Claims(fn ClaimsFunc) Option
- func ClientIDMetadataDocuments(opts ...CIMDOption) Option
- func ClientJWKSFetch(f JWKSFetcher) Option
- func ClientPrivileges(fn ClientPrivilegesFunc) Option
- func ClientReference(fn func(ctx context.Context, c *auth.Call) string) Option
- func CodeTTL(d time.Duration) Option
- func ConsentPage(u string) Option
- func DPoPProofMaxAge(d time.Duration) Option
- func DPoPSigningAlgorithms(algs ...jwt.Alg) Option
- func DeviceAuthorizationEndpoint(u string) Option
- func EncryptClientSecrets(e SecretEncryption) Option
- func EnforcePerClientResources(on bool) Option
- func Extend(exts ...Extension) Option
- func FormatRefreshToken(f RefreshTokenFormat) Option
- func GenerateAccessToken(fn TokenGenerator) Option
- func GenerateClientID(fn func() string) Option
- func GenerateClientSecret(fn func() string) Option
- func GenerateRefreshToken(fn TokenGenerator) Option
- func GrantTypes(grants ...string) Option
- func HashStoredSecrets(fn SecretHasher) Option
- func IDTokenTTL(d time.Duration) Option
- func IdentifierValidator(fn func(identifier string) bool) Option
- func Issuer(iss string) Option
- func LoginPage(u string) Option
- func M2MAccessTokenTTL(d time.Duration) Option
- func MCP() Option
- func NoDefaultRateLimits() Option
- func PairwiseSecret(secret string) Option
- func PostLogin(g PostLoginGate) Option
- func ProtectedResource(resource string, scopes ...string) Option
- func RateLimit(endpoint auth.EndpointID, limit int, window time.Duration) Option
- func RefreshReplay(e SecretEncryption) Option
- func RefreshReuseInterval(d time.Duration) Option
- func RefreshTokenTTL(d time.Duration) Option
- func RefuseJWTRevocation() Option
- func RegistrationAllowedResources(ids ...string) Option
- func RegistrationAllowedScopes(scopes ...string) Option
- func RegistrationDefaultResources(ids ...string) Option
- func RegistrationDefaultScopes(scopes ...string) Option
- func RegistrationRequirePKCE(on bool) Option
- func RegistrationSecretExpiration(d time.Duration) Option
- func RegistrationSecretExpiresAt(t time.Time) Option
- func RequestURIResolver(fn RequestURIResolverFunc) Option
- func RevokeAccessTokenOnly() Option
- func ScopeExpirations(ttls map[string]time.Duration) Option
- func Scopes(scopes ...string) Option
- func SelectAccount(g SelectAccountGate) Option
- func SelectAccountFallsBackToLogin() Option
- func SignupPage(u string) Option
- func SignupRedirect(fn SignupRedirectFunc) Option
- func TokenResponseFields(fn func(ctx context.Context, info TokenFieldsInfo) (map[string]any, error)) Option
- func TrustedClients(clients ...TrustedClient) Option
- func UserInfoInsufficientScope() Option
- func ValidateInitialAccessToken(fn InitialAccessTokenFunc) Option
- func ValidateRedirectURI(fn RedirectValidator) Option
- func WithGrant(g ExtensionGrant) Option
- type Plugin
- func (p *Plugin) AuthenticateClient(ctx context.Context, cred ClientCredentials, audience string) (Client, error)
- func (p *Plugin) Authorize(ctx context.Context, in AuthorizeRequest) (AuthorizeResult, error)
- func (p *Plugin) CheckScopes(ctx context.Context, client Client, scopes []string, resource string) ([]string, error)
- func (p *Plugin) Client(ctx context.Context, clientID string) (Client, error)
- func (p *Plugin) ClientResourceLinks(ctx context.Context, clientID string) ([]ClientResourceLink, error)
- func (p *Plugin) Clients(ctx context.Context) (ClientList, error)
- func (p *Plugin) ConfirmEndSession(ctx context.Context, in EndSessionConfirmRequest) (EndSessionResult, error)
- func (p *Plugin) Consent(ctx context.Context, in ConsentRequest) (AuthorizeResult, error)
- func (p *Plugin) ConsentByID(ctx context.Context, id string) (Consent, error)
- func (p *Plugin) Consents(ctx context.Context) (ConsentList, error)
- func (p *Plugin) ConsumeClientAssertion(ctx context.Context, clientID, assertion, audience string) error
- func (p *Plugin) Continue(ctx context.Context, in ContinueRequest) (AuthorizeResult, error)
- func (p *Plugin) CreateClient(ctx context.Context, spec ClientSpec) (Registration, error)
- func (p *Plugin) CreateOwnedClient(ctx context.Context, in ClientMetadata) (Registration, error)
- func (p *Plugin) CreateResource(ctx context.Context, r Resource) (Resource, error)
- func (p *Plugin) DeleteClient(ctx context.Context, clientID string) error
- func (p *Plugin) DeleteOwnedClient(ctx context.Context, clientID string) error
- func (p *Plugin) DeleteResource(ctx context.Context, identifier string) error
- func (p *Plugin) EndSession(ctx context.Context, in EndSessionRequest) (EndSessionResult, error)
- func (p *Plugin) Handler(opts ...authhttp.Option) http.Handler
- func (p *Plugin) HashToken(token string, kind SecretKind) []byte
- func (p *Plugin) Init(a *auth.Auth) error
- func (p *Plugin) Introspect(ctx context.Context, in IntrospectRequest) (Introspection, error)
- func (p *Plugin) IssueTokens(ctx context.Context, in IssueRequest) (TokenResponse, error)
- func (p *Plugin) LinkClient(ctx context.Context, identifier, clientID string) (alreadyLinked bool, err error)
- func (p *Plugin) LinkClientWithMetadata(ctx context.Context, identifier, clientID string, metadata json.RawMessage) (alreadyLinked bool, err error)
- func (p *Plugin) Metadata() Metadata
- func (p *Plugin) Name() string
- func (p *Plugin) Operations() []auth.Operation
- func (p *Plugin) OptionErr() error
- func (p *Plugin) OwnedClient(ctx context.Context, clientID string) (Client, error)
- func (p *Plugin) PatchResource(ctx context.Context, identifier string, patch ResourcePatch) (Resource, error)
- func (p *Plugin) PublicClientInfo(ctx context.Context, clientID string) (PublicClient, error)
- func (p *Plugin) PublicClientPrelogin(ctx context.Context, in PreloginRequest) (PublicClient, error)
- func (p *Plugin) RateLimits() []auth.RateRule
- func (p *Plugin) Register(ctx context.Context, in ClientMetadata) (Registration, error)
- func (p *Plugin) RequireToken(o RequireOptions) func(http.Handler) http.Handler
- func (p *Plugin) Requires() []string
- func (p *Plugin) Resource(ctx context.Context, identifier string) (Resource, error)
- func (p *Plugin) ResourceMetadataFor(resource string) (ResourceMetadata, bool)
- func (p *Plugin) Resources(ctx context.Context) ([]Resource, error)
- func (p *Plugin) ResumeURL(query url.Values) string
- func (p *Plugin) RevealClientSecret(ctx context.Context, clientID string) (string, error)
- func (p *Plugin) Revoke(ctx context.Context, in RevokeRequest) error
- func (p *Plugin) RevokeConsent(ctx context.Context, clientID string) error
- func (p *Plugin) RotateSecret(ctx context.Context, clientID string) (Registration, error)
- func (p *Plugin) SeedResources(ctx context.Context, mode SeedMode, seeds ...ResourceSeed) error
- func (p *Plugin) SessionEnded(ctx context.Context, userID, id string) error
- func (p *Plugin) SessionReplaced(ctx context.Context, old, new auth.Session) error
- func (p *Plugin) SetClientDisabled(ctx context.Context, clientID string, disabled bool) error
- func (p *Plugin) Sweep(ctx context.Context, cutoff time.Time) (int64, error)
- func (p *Plugin) Tables() []sqlb.Table
- func (p *Plugin) Token(ctx context.Context, in TokenRequest) (TokenResponse, error)
- func (p *Plugin) TypedMigrations(engine string, t auth.SQLTypes) ([]auth.Migration, error)
- func (p *Plugin) UnlinkClient(ctx context.Context, identifier, clientID string) error
- func (p *Plugin) UpdateClient(ctx context.Context, clientID string, spec ClientSpec) (Client, error)
- func (p *Plugin) UpdateConsent(ctx context.Context, in UpdateConsentRequest) (Consent, error)
- func (p *Plugin) UpdateOwnedClient(ctx context.Context, in UpdateClientRequest) (Client, error)
- func (p *Plugin) UpdateResource(ctx context.Context, r Resource) (Resource, error)
- func (p *Plugin) UserInfo(ctx context.Context, token string) (map[string]any, error)
- func (p *Plugin) UserSessionsEnded(ctx context.Context, userID, exceptID string) error
- func (p *Plugin) UserTables() []auth.UserTable
- func (p *Plugin) Validate(c *auth.Config) error
- func (p *Plugin) ValidateCIMDDocument(clientID string, document []byte) (ClientMetadata, error)
- func (p *Plugin) VerifyAccessToken(ctx context.Context, token string) (AccessToken, error)
- func (p *Plugin) VerifyOAuthQuery(raw string) error
- func (p *Plugin) VerifyPresentedToken(ctx context.Context, pt PresentedToken) (AccessToken, error)
- func (p *Plugin) WriteInsufficientScope(w http.ResponseWriter, o RequireOptions, missing ...string)
- type PostLoginGate
- type PreloginRequest
- type PresentedToken
- type PublicClient
- type RedirectError
- type RedirectValidator
- type RefreshTokenFormat
- type RegisteredMetadata
- type Registration
- type RequestURIResolverFunc
- type RequireOptions
- type Resource
- type ResourceGuard
- type ResourceMetadata
- type ResourcePatch
- type ResourceSeed
- type RevokeRequest
- type SecretEncryption
- type SecretHasher
- type SecretKind
- type SeedMode
- type SelectAccountGate
- type Signer
- type SignupRedirectFunc
- type TokenFieldsInfo
- type TokenGenerator
- type TokenRequest
- type TokenResponse
- type TrustedClient
- type UpdateClientRequest
- type UpdateConsentRequest
- type UserInfoRequest
Constants ¶
const ( CIMDFieldClientURI = "client_uri" CIMDFieldLogoURI = "logo_uri" CIMDFieldTosURI = "tos_uri" CIMDFieldPolicyURI = "policy_uri" CIMDFieldRedirectURIs = "redirect_uris" CIMDFieldPostLogoutURIs = "post_logout_redirect_uris" )
CIMD origin-bound fields: the document members CIMDOriginBoundFields may name.
const ( DefaultCIMDMinimumFetchInterval = time.Second DefaultCIMDMaxConcurrent = 16 DefaultCIMDMaxConcurrentPerOrigin = 4 DefaultCIMDMaxPerMinute = 120 DefaultCIMDMaxPerOriginPerMinute = 30 // DefaultCIMDMaxCacheEntries bounds the governor's per-URL and // per-origin records, better-auth's maxCacheEntries. DefaultCIMDMaxCacheEntries = 1000 )
better-auth's metadataFetchPolicy defaults.
const ( // DPoPProofType is a proof's typ header. DPoPProofType = "dpop+jwt" // SchemeDPoP is the Authorization scheme and token_type of a bound // token. SchemeDPoP = "DPoP" // HeaderDPoP carries the proof. HeaderDPoP = "DPoP" // DefaultDPoPProofMaxAge is how old a proof's iat may be, // better-auth's dpop.proofMaxAgeSeconds default. //ds:def id=oauthprovider-defaultdpopmaxage-r4n8wq2k owner=@khanakia stability=stable DefaultDPoPProofMaxAge = 300 * time.Second )
DPoP (RFC 9449), as better-auth's core verifies it.
const ( // WellKnownOpenID is the OpenID Connect Discovery document, served under // the issuer's path. WellKnownOpenID = "/.well-known/openid-configuration" // WellKnownOAuth is the RFC 8414 metadata document, served before the // issuer's path (and, for clients that append it, after it). WellKnownOAuth = "/.well-known/oauth-authorization-server" )
Discovery paths and HTTP constants.
const ( // PageConfirm and PageSuccess are the pages EndSessionResult asks the // transport to render. PageConfirm = "confirm" PageSuccess = "success" )
RP-initiated logout constants, better-auth's.
const ( EndpointAuthorize auth.EndpointID = "oauthprovider.authorize" EndpointToken auth.EndpointID = "oauthprovider.token" EndpointUserInfo auth.EndpointID = "oauthprovider.userinfo" EndpointIntrospect auth.EndpointID = "oauthprovider.introspect" EndpointRevoke auth.EndpointID = "oauthprovider.revoke" EndpointRegister auth.EndpointID = "oauthprovider.register" EndpointConsent auth.EndpointID = "oauthprovider.consent" EndpointCreateClient auth.EndpointID = "oauthprovider.create_client" EndpointListClients auth.EndpointID = "oauthprovider.list_clients" EndpointGetClient auth.EndpointID = "oauthprovider.get_client" EndpointUpdateClient auth.EndpointID = "oauthprovider.update_client" EndpointRotateSecret auth.EndpointID = "oauthprovider.rotate_secret" EndpointDeleteClient auth.EndpointID = "oauthprovider.delete_client" EndpointPublicClient auth.EndpointID = "oauthprovider.public_client" EndpointListConsents auth.EndpointID = "oauthprovider.list_consents" EndpointRevokeConsent auth.EndpointID = "oauthprovider.revoke_consent" EndpointGetConsent auth.EndpointID = "oauthprovider.get_consent" EndpointUpdateConsent auth.EndpointID = "oauthprovider.update_consent" EndpointEndSession auth.EndpointID = "oauthprovider.end_session" EndpointEndSessionConfirm auth.EndpointID = "oauthprovider.end_session_confirm" EndpointPublicClientPrelogin auth.EndpointID = "oauthprovider.public_client_prelogin" EndpointContinue auth.EndpointID = "oauthprovider.continue" )
Operation ids. The self-served ones are served by Handler; the others by every transport, under /oauthprovider.
const ( ScopeOpenID = "openid" ScopeProfile = "profile" ScopeEmail = "email" ScopeOfflineAccess = "offline_access" )
Standard scopes. ScopeOpenID asks for an ID token, ScopeOfflineAccess for a refresh token; ScopeProfile and ScopeEmail release the claims OpenID Connect Core section 5.4 assigns them.
const ( GrantAuthorizationCode = "authorization_code" GrantRefreshToken = "refresh_token" GrantClientCredentials = "client_credentials" )
Grant types.
const ( AuthMethodBasic = "client_secret_basic" AuthMethodPost = "client_secret_post" AuthMethodNone = "none" // AuthMethodPrivateKeyJWT authenticates with a JWT the client signs // with a key from its registered JWKS (RFC 7523 section 2.2). AuthMethodPrivateKeyJWT = "private_key_jwt" )
Client authentication methods at the token endpoint (RFC 7591 section 2).
const ( // DefaultCodeTTL is how long an authorization code may wait to be // exchanged. RFC 6749 section 4.1.2 recommends at most ten minutes. //ds:def id=oauthprovider-defaultcodettl-r5m2qw8x owner=@khanakia stability=stable DefaultCodeTTL = 10 * time.Minute // DefaultAccessTokenTTL is an access token's lifetime. //ds:def id=oauthprovider-defaultaccessttl-h4k9zt3p owner=@khanakia stability=stable DefaultAccessTokenTTL = time.Hour // DefaultRefreshTokenTTL is a refresh token's lifetime; each rotation // starts a new one. //ds:def id=oauthprovider-defaultrefreshttl-b8x3nq6v owner=@khanakia stability=stable DefaultRefreshTokenTTL = 30 * 24 * time.Hour // DefaultAssertionMaxLifetime is the longest a private_key_jwt client // assertion may live (exp minus now), better-auth's assertionMaxLifetime. DefaultAssertionMaxLifetime = 5 * time.Minute // DefaultIDTokenTTL is an ID token's lifetime: 10 hours, better-auth's // default, as the OpenID Connect specification recommends. DefaultIDTokenTTL = 10 * time.Hour )
Lifetimes. Each has an option; the defaults are better-auth's.
const ( PrefixAccessToken = "uboa_" PrefixRefreshToken = "ubor_" PrefixCode = "uboc_" PrefixClientSecret = "ubos_" )
Token prefixes, so a leaked token is recognisable by secret scanners and by kind: uboa_ an access token, ubor_ a refresh token, uboc_ a code, ubos_ a client secret.
const ( SubjectPublic = "public" SubjectPairwise = "pairwise" )
Subject types (OpenID Connect Core section 8).
const ( ActionCreate = "create" ActionRead = "read" ActionUpdate = "update" ActionDelete = "delete" ActionList = "list" ActionRotate = "rotate" // ActionConfigureCCScopes sets a client's client_credentials ceiling // through the owner operations. Without a ClientPrivileges function it // is always refused: the ceiling grants machine access, so the app must // say who may give it. ActionConfigureCCScopes = "configure-client-credentials-scopes" )
Client actions ClientPrivileges is asked about, better-auth's clientPrivileges actions.
const ( //ds:def id=oauthprovider-tableresource-p6w3kq9m owner=@khanakia stability=stable TableNameResource = "auth_oauth_resource" TableAliasResource = "oars" //ds:def id=oauthprovider-tableclientresource-z2n7xv4q owner=@khanakia stability=stable TableNameClientResource = "auth_oauth_client_resource" TableAliasClientResource = "oacr" ColIdentifier = "identifier" ColAccessTTL = "access_token_ttl" ColRefreshTTL = "refresh_token_ttl" ColAllowedScopes = "allowed_scopes" ColCustomClaims = "custom_claims" ColDPoPRequired = "dpop_required" ColDisabled = "disabled" ColMetadata = "metadata" ColResourceID = "resource_id" )
The resource registry, better-auth's oauthResource and oauthClientResource: per-resource token policy, and which clients may ask for which resource.
const ( // DefaultResourceCacheTTL is how long CacheResources keeps a resource // when it is given 0. DefaultResourceCacheTTL = time.Minute // MaxResourceCacheTTL caps CacheResources: a resource disabled or // deleted on another instance is served at most this long after. MaxResourceCacheTTL = 15 * time.Minute )
Bounds of CacheResources.
const ( //ds:def id=oauthprovider-tableclient-n4x8kq2v owner=@khanakia stability=stable TableNameClient = "auth_oauth_client" TableAliasClient = "oac" //ds:def id=oauthprovider-tablecode-t6m3wp9z owner=@khanakia stability=stable TableNameCode = "auth_oauth_code" TableAliasCode = "oaco" //ds:def id=oauthprovider-tabletoken-c2h7vb5r owner=@khanakia stability=stable TableNameToken = "auth_oauth_token" TableAliasToken = "oat" //ds:def id=oauthprovider-tableconsent-y9q4kd3s owner=@khanakia stability=stable TableNameConsent = "auth_oauth_consent" TableAliasConsent = "oacn" )
The plugin's tables. No foreign keys (ADR-0002): UserTables names the user columns, so auth.DeleteUser removes a user's rows.
const ( ColID = "id" ColClientID = "client_id" ColUserID = "user_id" ColScopes = "scopes" ColCreatedAt = "created_at" ColUpdatedAt = "updated_at" ColSessionID = "session_id" ColAuthTime = "auth_time" )
Column names shared by several tables.
const ( // ColSecretHash is the SHA-256 of the client secret; NULL for a public // client (token_endpoint_auth_method none). ColSecretHash = "secret_hash" ColName = "name" ColRedirectURIs = "redirect_uris" ColGrantTypes = "grant_types" ColAuthMethod = "auth_method" ColSkipConsent = "skip_consent" ColRequirePKCE = "require_pkce" // ColIntrospectAny lets the client introspect tokens issued to other // clients: a resource server. Set only from Go (CreateClient). ColIntrospectAny = "introspect_any" ColDisabledAt = "disabled_at" ColClientURI = "client_uri" ColLogoURI = "logo_uri" ColContacts = "contacts" ColTosURI = "tos_uri" ColPolicyURI = "policy_uri" )
auth_oauth_client columns. The id is the client_id.
const ( ColCodeHash = "code_hash" ColRedirectURI = "redirect_uri" ColChallenge = "code_challenge" ColChallengeMethod = "code_challenge_method" ColNonce = "nonce" ColExpiresAt = "expires_at" ColConsumedAt = "consumed_at" )
auth_oauth_code columns.
const ( ColAccessHash = "access_hash" ColRefreshHash = "refresh_hash" ColFamilyID = "family_id" ColAccessExpiresAt = "access_expires_at" ColRefreshExpiresAt = "refresh_expires_at" ColRotatedAt = "rotated_at" ColRevokedAt = "revoked_at" )
auth_oauth_token columns. One row is one issuance: an access token and, with offline_access, its refresh token. FamilyID ties every row descended from one code (the code's id) or one client_credentials grant, so a replayed code or a reused refresh token revokes them all.
const ( // ColPostLogoutURIs are the client's registered RP-initiated logout // targets, newline-joined. ColPostLogoutURIs = "post_logout_redirect_uris" // ColEndSession lets the client end the user's session through the // end-session endpoint. ColEndSession = "end_session" // ColJWKS is the client's public key set (RFC 7517 JSON) for // private_key_jwt. ColJWKS = "jwks" // ColDiscovery names how a client row came to be: "" registered, "cimd" // fetched from its client_id URL. ColDiscovery = "discovery" // ColFetchedAt is when a CIMD client's document was last fetched. ColFetchedAt = "fetched_at" // ColResource is the RFC 8707 resource a code or token is bound to; "" // for none. ColResource = "resource" // ColSubjectType is the client's subject_type: NULL or "public", or // "pairwise" (migration 0003). ColSubjectType = "subject_type" // ColUserInfoClaims is the claim names an authorization request asked // for in its claims parameter, space-joined (migration 0004). ColUserInfoClaims = "userinfo_claims" // ColBackchannelURI and ColBackchannelSession are the client's // back-channel logout registration (migration 0005). ColBackchannelURI = "backchannel_logout_uri" ColBackchannelSession = "backchannel_logout_session_required" // ColDPoPBound, ColDPoPJKT and ColJKT are DPoP (migration 0006): the // client's dpop_bound_access_tokens, a code's dpop_jkt, and the // thumbprint of the key a token is bound to. ColDPoPBound = "dpop_bound" ColDPoPJKT = "dpop_jkt" ColJKT = "jkt" // Migration 0008: the application type and the restricted fields. ColAppType = "application_type" ColCCScopes = "client_credentials_scopes" ColReferenceID = "reference_id" ColSecretExpires = "client_secret_expires_at" // Migration 0009: a document client's freshness and validators. ColCIMDExpires = "cimd_expires_at" ColCIMDETag = "cimd_etag" ColCIMDLastMod = "cimd_last_modified" // Migration 0010: RFC 7591 software identifiers, and a resource's // signing algorithm. ColSoftwareID = "software_id" ColSoftwareVersion = "software_version" ColSoftwareStatement = "software_statement" ColSigningAlg = "signing_algorithm" // Migration 0012: the client certificate thumbprint a token is bound // to (RFC 8705), and the post-login reference of codes, tokens and // consents (reference_id, as on the client). ColX5T = "x5t_s256" // Migration 0013: the URL a private_key_jwt client publishes its key // set at, instead of an inline jwks. ColJWKSURI = "jwks_uri" // Migration 0014: an access token revoked on its own (its refresh token // kept, RevokeAccessTokenOnly), the encrypted replay of a rotation // (RefreshReplay), the resources a consent covers, a client-resource // link's metadata, a client secret kept encrypted // (EncryptClientSecrets), and a resource's signing key pin. ColAccessRevokedAt = "access_revoked_at" ColReplay = "rotation_replay" ColReplayExpires = "rotation_replay_expires_at" ColResources = "resources" ColSecretEncrypted = "secret_encrypted" ColSigningKeyID = "signing_key_id" ColPolicyVersion = "policy_version" // TableNameAssertion records spent private_key_jwt assertion ids until // they expire, so one assertion authenticates once. //ds:def id=oauthprovider-tableassertion-v5k2np8w owner=@khanakia stability=stable TableNameAssertion = "auth_oauth_assertion" TableAliasAssertion = "oaas" )
Columns added by migration 0002.
const AccessTokenType = "at+jwt"
AccessTokenType is the JWS typ header of a JWT access token, RFC 9068's and better-auth's. A resource server verifying one offline passes it in jwt.Verifier.Types.
const ClientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
ClientAssertionType is the only client_assertion_type accepted (RFC 7523 section 2.2).
const DefaultBasePath = "/oauth2"
DefaultBasePath is where Handler serves the protocol endpoints, under the issuer's path: <issuer>/oauth2/token and so on. BasePath moves it.
const ( // DefaultCIMDRevalidate is how long a fetched document is trusted // before it is fetched again (better-auth's metadataRevalidationInterval). //ds:def id=oauthprovider-defaultcimdrevalidate-h3w8kq5n owner=@khanakia stability=stable DefaultCIMDRevalidate = time.Hour )
CIMD (OAuth Client ID Metadata Document): a client_id that is an https URL names a JSON document the client publishes about itself. The provider fetches it on first use, keeps it as a client row, and fetches it again when it is older than the revalidation interval. better-auth's cimd plugin.
const (
// LogoutTokenType is the logout token's typ header.
LogoutTokenType = "logout+jwt"
)
OpenID Connect Back-Channel Logout 1.0, as better-auth sends it.
const MCPRefreshReuseInterval = 30 * time.Second
MCPRefreshReuseInterval is the refresh-token reuse window MCP sets, better-auth's mcp() default of 30 seconds: MCP clients often refresh from several processes at once.
const PluginName = "oauthprovider"
PluginName is the plugin's permanent identity, and the segment its management operations are served under.
const WellKnownProtectedResource = "/.well-known/oauth-protected-resource"
WellKnownProtectedResource is the RFC 9728 protected resource metadata document, served before the resource's path.
Variables ¶
var ( ErrAccountSelectionRequired = auth.NewError("oauthprovider.account_selection_required", http.StatusForbidden, "The user must choose an account.") ErrInteractionRequired = auth.NewError("oauthprovider.interaction_required", http.StatusForbidden, "The user must complete a step before this authorization.") )
The OpenID Connect prompt=none errors of a gate: the picker is account_selection_required, the sign-up and post-login pages interaction_required.
var ( ErrInvalidRequest = auth.NewError("oauthprovider.invalid_request", http.StatusBadRequest, "The request is missing a parameter, repeats one, or is otherwise malformed.") ErrInvalidClient = auth.NewError("oauthprovider.invalid_client", http.StatusUnauthorized, "Client authentication failed.") ErrInvalidGrant = auth.NewError("oauthprovider.invalid_grant", http.StatusBadRequest, "The grant is invalid, expired, revoked, or was issued to another client.") ErrUnsupportedGrantType = auth.NewError("oauthprovider.unsupported_grant_type", http.StatusBadRequest, "The grant type is not supported.") ErrUnsupportedResponseType = auth.NewError("oauthprovider.unsupported_response_type", http.StatusBadRequest, "Only response_type=code is supported.") ErrInvalidScope = auth.NewError("oauthprovider.invalid_scope", http.StatusBadRequest, "A requested scope is unknown or not allowed for this client.") ErrAccessDenied = auth.NewError("oauthprovider.access_denied", http.StatusForbidden, "The user or the server denied the request.") ErrLoginRequired = auth.NewError("oauthprovider.login_required", http.StatusUnauthorized, "The user must sign in.") ErrConsentRequired = auth.NewError("oauthprovider.consent_required", http.StatusForbidden, "The user must consent.") ErrInvalidToken = auth.NewError("oauthprovider.invalid_token", http.StatusUnauthorized, "The access token is invalid, expired or revoked.") ErrInsufficientScope = auth.NewError("oauthprovider.insufficient_scope", http.StatusForbidden, "The access token lacks a required scope.") ErrInvalidRedirectURI = auth.NewError("oauthprovider.invalid_redirect_uri", http.StatusBadRequest, "A redirect URI is not an absolute https URL, a loopback http URL or a private-use scheme, or carries a fragment.") ErrInvalidClientMetadata = auth.NewError("oauthprovider.invalid_client_metadata", http.StatusBadRequest, "The client metadata is not valid.") ErrRegistrationClosed = auth.NewError("oauthprovider.registration_closed", http.StatusForbidden, "Dynamic client registration is not open.") ErrInvalidTarget = auth.NewError("oauthprovider.invalid_target", http.StatusBadRequest, "The requested resource is unknown or not allowed.") ErrRequestNotSupported = auth.NewError("oauthprovider.request_not_supported", http.StatusBadRequest, "Request objects are not supported.") ErrInvalidRequestURI = auth.NewError("oauthprovider.invalid_request_uri", http.StatusBadRequest, "request_uri is invalid or expired.") ErrRequestURINotSupported = auth.NewError("oauthprovider.request_uri_not_supported", http.StatusBadRequest, "request_uri is not supported.") ErrClientNotFound = auth.NewError("oauthprovider.not_found", http.StatusNotFound, "Not found.") // ErrClientDisabled is an authorization request for a client an admin // disabled, better-auth's client_disabled. Like an unknown client // (invalid_client) it is shown to the user and never sent to the // redirect URI; errors.Is also sees ErrBadRedirectTarget. ErrClientDisabled = auth.NewError("oauthprovider.client_disabled", http.StatusBadRequest, "The client is disabled.") // ErrBadRedirectTarget is an authorize request whose client_id is // unknown or whose redirect_uri is not registered: the one case where the // error is never sent to the redirect URI, because sending it there // would make the provider an open redirector. ErrBadRedirectTarget = auth.NewError("oauthprovider.invalid_redirect_target", http.StatusBadRequest, "The client is unknown or the redirect URI is not registered for it.") )
Errors. The part after "oauthprovider." is the RFC 6749 / 7591 / 6750 error code Handler answers, so a Go caller and an HTTP client see the same reason.
var ( ErrNoIssuer = errors.New("oauthprovider: Issuer is required: an absolute https URL (http only for a loopback host) with no query or fragment") ErrNoLoginPage = errors.New("oauthprovider: LoginPage is required: where the authorize endpoint sends a browser that is not signed in") ErrBadGrantTypes = errors.New("oauthprovider: GrantTypes needs authorization_code for refresh_token, and only known grants") ErrBadScopes = errors.New("oauthprovider: a scope is non-empty and contains no whitespace") ErrBadTTL = errors.New("oauthprovider: every lifetime is positive") ErrNoJWT = errors.New("oauthprovider: a signer is required to sign ID tokens; pass the jwt plugin (or another Signer) to New, and list a signer that is a plugin in auth.WithPlugins before this one") ErrBadResource = errors.New("oauthprovider: a ProtectedResource is an absolute https URL (http only for a loopback host) with no query or fragment") ErrIssuerMatch = errors.New("oauthprovider: the jwt plugin's Issuer must equal the provider's Issuer, or ID tokens would name another issuer") // ErrNoSecret is a feature that signs with a key from auth.WithSecret, // configured without one: a client with EnableEndSession (the logout // confirmation cookie), AllowPublicClientPrelogin (it verifies the // signed authorization query) and PostLogin (its marker, joined with // ErrBadGate). auth.New refuses it, and CreateClient and UpdateClient // refuse EnableEndSession, rather than the first request answering 500. ErrNoSecret = errors.New("oauthprovider: auth.WithSecret is required by this feature") )
Configuration errors, returned by auth.New.
var ( // ErrUnsupportedTokenType is a revocation of a still-valid JWT access // token under RefuseJWTRevocation (RFC 7009 section 2.2.1). ErrUnsupportedTokenType = auth.NewError("oauthprovider.unsupported_token_type", http.StatusBadRequest, "This token type cannot be revoked; it expires on its own.") // ErrInvalidUser is a grant whose user has been deleted since it was // issued, better-auth's invalid_user. ErrInvalidUser = auth.NewError("oauthprovider.invalid_user", http.StatusBadRequest, "The user of this grant no longer exists.") // ErrUnsupportedPromptSelectAccount is prompt=select_account with no // SelectAccount page, better-auth's unsupported_prompt_select_account; // SelectAccountFallsBackToLogin sends such a request to the login page // instead. ErrUnsupportedPromptSelectAccount = auth.NewError("oauthprovider.unsupported_prompt_select_account", http.StatusBadRequest, "This server cannot show an account picker.") )
Errors better-auth answers under these names.
var ( TableResource = sqlb.Table{Name: TableNameResource, Alias: TableAliasResource} ResourceIDCol = sqlb.Col[string](TableResource, ColID) ResourceIdent = sqlb.Col[string](TableResource, ColIdentifier) ResourceName = sqlb.Col[*string](TableResource, ColName) ResourceAccess = sqlb.Col[*int64](TableResource, ColAccessTTL) ResourceRefresh = sqlb.Col[*int64](TableResource, ColRefreshTTL) ResourceScopes = sqlb.Col[*string](TableResource, ColAllowedScopes) ResourceClaims = sqlb.Col[*string](TableResource, ColCustomClaims) ResourceDPoP = sqlb.Col[bool](TableResource, ColDPoPRequired) ResourceDisabled = sqlb.Col[bool](TableResource, ColDisabled) ResourceMeta = sqlb.Col[*string](TableResource, ColMetadata) ResourceAlg = sqlb.Col[*string](TableResource, ColSigningAlg) ResourceCreated = sqlb.Col[time.Time](TableResource, ColCreatedAt) ResourceUpdated = sqlb.Col[time.Time](TableResource, ColUpdatedAt) TableClientResource = sqlb.Table{Name: TableNameClientResource, Alias: TableAliasClientResource} LinkClient = sqlb.Col[string](TableClientResource, ColClientID) LinkResource = sqlb.Col[string](TableClientResource, ColResourceID) LinkCreated = sqlb.Col[time.Time](TableClientResource, ColCreatedAt) )
Typed registry columns. A resource's id is the hex SHA-256 of its identifier, because an identifier is a URL longer than an indexable key on every engine.
var ( TableClient = sqlb.Table{Name: TableNameClient, Alias: TableAliasClient} ClientIDCol = sqlb.Col[string](TableClient, ColID) ClientSecret = sqlb.Col[[]byte](TableClient, ColSecretHash) ClientName = sqlb.Col[string](TableClient, ColName) ClientRedirs = sqlb.Col[string](TableClient, ColRedirectURIs) ClientScopes = sqlb.Col[string](TableClient, ColScopes) ClientGrants = sqlb.Col[string](TableClient, ColGrantTypes) ClientMethod = sqlb.Col[string](TableClient, ColAuthMethod) ClientSkip = sqlb.Col[bool](TableClient, ColSkipConsent) ClientPKCE = sqlb.Col[bool](TableClient, ColRequirePKCE) ClientIntro = sqlb.Col[bool](TableClient, ColIntrospectAny) ClientOwner = sqlb.Col[*string](TableClient, ColUserID) ClientOff = sqlb.Col[*time.Time](TableClient, ColDisabledAt) ClientURI = sqlb.Col[*string](TableClient, ColClientURI) ClientLogo = sqlb.Col[*string](TableClient, ColLogoURI) ClientContact = sqlb.Col[*string](TableClient, ColContacts) ClientTos = sqlb.Col[*string](TableClient, ColTosURI) ClientPolicy = sqlb.Col[*string](TableClient, ColPolicyURI) ClientCreated = sqlb.Col[time.Time](TableClient, ColCreatedAt) ClientUpdated = sqlb.Col[time.Time](TableClient, ColUpdatedAt) TableCode = sqlb.Table{Name: TableNameCode, Alias: TableAliasCode} CodeID = sqlb.Col[string](TableCode, ColID) CodeHash = sqlb.Col[[]byte](TableCode, ColCodeHash) CodeClient = sqlb.Col[string](TableCode, ColClientID) CodeUser = sqlb.Col[string](TableCode, ColUserID) CodeSession = sqlb.Col[string](TableCode, ColSessionID) CodeRedirect = sqlb.Col[string](TableCode, ColRedirectURI) CodeScopes = sqlb.Col[string](TableCode, ColScopes) CodeChallenge = sqlb.Col[*string](TableCode, ColChallenge) CodeMethod = sqlb.Col[*string](TableCode, ColChallengeMethod) CodeNonce = sqlb.Col[*string](TableCode, ColNonce) CodeAuthTime = sqlb.Col[time.Time](TableCode, ColAuthTime) CodeExpires = sqlb.Col[time.Time](TableCode, ColExpiresAt) CodeConsumed = sqlb.Col[*time.Time](TableCode, ColConsumedAt) CodeCreated = sqlb.Col[time.Time](TableCode, ColCreatedAt) TableToken = sqlb.Table{Name: TableNameToken, Alias: TableAliasToken} TokenID = sqlb.Col[string](TableToken, ColID) TokenAccessHash = sqlb.Col[[]byte](TableToken, ColAccessHash) TokenRefresh = sqlb.Col[[]byte](TableToken, ColRefreshHash) TokenFamily = sqlb.Col[string](TableToken, ColFamilyID) TokenClient = sqlb.Col[string](TableToken, ColClientID) TokenUser = sqlb.Col[*string](TableToken, ColUserID) TokenSession = sqlb.Col[*string](TableToken, ColSessionID) TokenScopes = sqlb.Col[string](TableToken, ColScopes) // TokenRefreshNull is the refresh hash as a nullable column, for the // insert of a row without a refresh token. TokenRefreshNull = sqlb.Col[*[]byte](TableToken, ColRefreshHash) TokenAccessExpires = sqlb.Col[time.Time](TableToken, ColAccessExpiresAt) TokenRefreshExpires = sqlb.Col[*time.Time](TableToken, ColRefreshExpiresAt) TokenAuthTime = sqlb.Col[*time.Time](TableToken, ColAuthTime) TokenCreated = sqlb.Col[time.Time](TableToken, ColCreatedAt) TokenRotated = sqlb.Col[*time.Time](TableToken, ColRotatedAt) TokenRevoked = sqlb.Col[*time.Time](TableToken, ColRevokedAt) TableConsent = sqlb.Table{Name: TableNameConsent, Alias: TableAliasConsent} ConsentID = sqlb.Col[string](TableConsent, ColID) ConsentUser = sqlb.Col[string](TableConsent, ColUserID) ConsentClient = sqlb.Col[string](TableConsent, ColClientID) ConsentScopes = sqlb.Col[string](TableConsent, ColScopes) ConsentCreated = sqlb.Col[time.Time](TableConsent, ColCreatedAt) ConsentUpdated = sqlb.Col[time.Time](TableConsent, ColUpdatedAt) )
Typed tables and columns.
var ( ClientPostLogout = sqlb.Col[*string](TableClient, ColPostLogoutURIs) ClientEndSession = sqlb.Col[bool](TableClient, ColEndSession) ClientJWKS = sqlb.Col[*string](TableClient, ColJWKS) ClientDiscovery = sqlb.Col[*string](TableClient, ColDiscovery) ClientFetchedAt = sqlb.Col[*time.Time](TableClient, ColFetchedAt) CodeResource = sqlb.Col[*string](TableCode, ColResource) ClientSubject = sqlb.Col[*string](TableClient, ColSubjectType) CodeClaims = sqlb.Col[*string](TableCode, ColUserInfoClaims) TokenClaims = sqlb.Col[*string](TableToken, ColUserInfoClaims) ConsentClaims = sqlb.Col[*string](TableConsent, ColUserInfoClaims) ClientBackURI = sqlb.Col[*string](TableClient, ColBackchannelURI) ClientBackSess = sqlb.Col[bool](TableClient, ColBackchannelSession) ClientDPoP = sqlb.Col[bool](TableClient, ColDPoPBound) CodeJKT = sqlb.Col[*string](TableCode, ColDPoPJKT) TokenJKT = sqlb.Col[*string](TableToken, ColJKT) ClientAppType = sqlb.Col[*string](TableClient, ColAppType) ClientCCScopes = sqlb.Col[*string](TableClient, ColCCScopes) ClientRefID = sqlb.Col[*string](TableClient, ColReferenceID) ClientMeta = sqlb.Col[*string](TableClient, ColMetadata) ClientSecretExp = sqlb.Col[*time.Time](TableClient, ColSecretExpires) ClientCIMDExpires = sqlb.Col[*time.Time](TableClient, ColCIMDExpires) ClientCIMDETag = sqlb.Col[*string](TableClient, ColCIMDETag) ClientCIMDLastMod = sqlb.Col[*string](TableClient, ColCIMDLastMod) ClientSoftwareID = sqlb.Col[*string](TableClient, ColSoftwareID) ClientSoftwareVersion = sqlb.Col[*string](TableClient, ColSoftwareVersion) ClientSoftwareStatement = sqlb.Col[*string](TableClient, ColSoftwareStatement) TokenResource = sqlb.Col[*string](TableToken, ColResource) // The binding and reference columns of migration 0012. TokenX5T = sqlb.Col[*string](TableToken, ColX5T) // ClientJWKSURI is migration 0013's jwks_uri. ClientJWKSURI = sqlb.Col[*string](TableClient, ColJWKSURI) // The columns of migration 0014. TokenAccessRevoked = sqlb.Col[*time.Time](TableToken, ColAccessRevokedAt) TokenReplay = sqlb.Col[*string](TableToken, ColReplay) TokenReplayExpires = sqlb.Col[*time.Time](TableToken, ColReplayExpires) ConsentResources = sqlb.Col[*string](TableConsent, ColResources) ClientSecretEnc = sqlb.Col[*string](TableClient, ColSecretEncrypted) ResourceKeyID = sqlb.Col[*string](TableResource, ColSigningKeyID) ResourceVersion = sqlb.Col[int64](TableResource, ColPolicyVersion) LinkMeta = sqlb.Col[*string](TableClientResource, ColMetadata) TokenRef = sqlb.Col[*string](TableToken, ColReferenceID) CodeRef = sqlb.Col[*string](TableCode, ColReferenceID) ConsentRef = sqlb.Col[*string](TableConsent, ColReferenceID) TableAssertion = sqlb.Table{Name: TableNameAssertion, Alias: TableAliasAssertion} AssertionID = sqlb.Col[string](TableAssertion, ColID) AssertionExpires = sqlb.Col[time.Time](TableAssertion, ColExpiresAt) )
var AuthMethodValues = []string{AuthMethodBasic, AuthMethodPost, AuthMethodNone, AuthMethodPrivateKeyJWT}
AuthMethodValues is every client authentication method a client may register with.
var DPoPAlgorithms = []string{string(jwt.EdDSA), string(jwt.ES256), string(jwt.ES512), string(jwt.PS256), string(jwt.RS256)}
DPoPAlgorithms are the proof algorithms accepted unless DPoPSigningAlgorithms replaces them: better-auth's default list. A proof's key usually carries no alg, so its type decides the family and the header's alg is checked against it (jwt.PublicKeyForAlg).
var DefaultCIMDOriginBoundFields = []string{CIMDFieldPostLogoutURIs, CIMDFieldClientURI}
DefaultCIMDOriginBoundFields are better-auth's originBoundFields default: redirect URIs are left out because exact matching at authorize already binds them, and native clients redirect to another origin than their document's.
var DefaultGrantTypes = []string{GrantAuthorizationCode, GrantClientCredentials, GrantRefreshToken}
DefaultGrantTypes is every grant the provider serves unless GrantTypes narrows it.
var DefaultRateLimits = map[auth.EndpointID]int{
EndpointToken: 20,
EndpointAuthorize: 30,
EndpointIntrospect: 100,
EndpointRevoke: 30,
EndpointRegister: 5,
EndpointUserInfo: 60,
}
DefaultRateLimits are better-auth's per-endpoint limits, per client IP per minute: token 20, authorize 30, introspect 100, revoke 30, register 5, userinfo 60. RateLimit replaces one; NoDefaultRateLimits drops them all so only the app's own rules apply.
var DefaultScopes = []string{ScopeOpenID, ScopeProfile, ScopeEmail, ScopeOfflineAccess}
DefaultScopes is what the provider supports unless Scopes replaces it.
var ErrBadCIMDField = errors.New("oauthprovider: CIMDOriginBoundFields names a field that is not a URL member of a client metadata document")
ErrBadCIMDField is a CIMDOriginBoundFields name that is not one of the CIMDField constants; auth.New fails with it.
var ErrBadDPoPAlgorithms = errors.New("oauthprovider: DPoPSigningAlgorithms needs at least one algorithm from jwt.AlgValues")
ErrBadDPoPAlgorithms is a DPoPSigningAlgorithms list that is empty or names an algorithm the jwt plugin cannot verify: a configuration error, so auth.New fails.
var ErrBadEncryption = errors.New("oauthprovider: a SecretEncryption needs Encrypt and Decrypt, and Decrypt(Encrypt(x)) == x")
ErrBadEncryption is a SecretEncryption without both functions, or whose Decrypt does not give back what Encrypt sealed; auth.New fails with it.
var ErrBadExtension = errors.New("oauthprovider: invalid extension")
ErrBadExtension is an Extension Validate refuses.
var ErrBadExtensionGrant = errors.New("oauthprovider: an extension grant needs an absolute-URI type that is not built in, registered once, with a handler")
ErrBadExtensionGrant is a WithGrant whose type is not an absolute URI, is a built-in grant, is registered twice, or has no handler.
var ErrBadGate = errors.New("oauthprovider: a gate needs its page (trusted) and its decision, and PostLogin needs auth.WithSecret")
ErrBadGate is a gate without its page or decision, or a post-login gate without auth.WithSecret.
var ErrBadPairwiseSecret = errors.New("oauthprovider: PairwiseSecret needs at least 32 characters")
ErrBadPairwiseSecret is a PairwiseSecret shorter than 32 characters.
var ErrBadResourceCacheTTL = fmt.Errorf("oauthprovider: the CacheResources TTL is between 0 (the default, %s) and %s", DefaultResourceCacheTTL, MaxResourceCacheTTL)
ErrBadResourceCacheTTL is a configuration error: a CacheResources TTL that is negative or above MaxResourceCacheTTL.
var ErrBadSecretHasher = errors.New("oauthprovider: HashStoredSecrets must return the same 32 bytes for the same input")
ErrBadSecretHasher is a HashStoredSecrets function that is not deterministic or does not return 32 bytes; auth.New fails with it.
var ErrBadSeedMode = errors.New("oauthprovider: SeedResources mode is insertOnly, merge or overwrite")
ErrBadSeedMode is a SeedResources mode that is not a SeedMode constant.
var ErrBadTrustedClient = errors.New("oauthprovider: invalid trusted client")
ErrBadTrustedClient is a TrustedClient Init refuses.
var ErrCIMDBusy = auth.NewError("oauthprovider.temporarily_unavailable", http.StatusTooManyRequests, "Client metadata cannot be fetched now. Try again shortly.")
ErrCIMDBusy is a metadata fetch the governor refused: too many in flight, over a per-minute budget, or a retry too soon after a failure. Excess fetches are refused, never queued.
var ErrCIMDFetch = errors.New("oauthprovider: client metadata document refused")
ErrCIMDFetch is a metadata document that could not be fetched or is not acceptable; every such failure is answered invalid_client.
var ErrForbidden = auth.NewError("oauthprovider.forbidden", http.StatusForbidden, "You may not do this with this client.")
ErrForbidden is an owner operation ClientPrivileges refused.
var ErrInvalidDPoPProof = auth.NewError("oauthprovider.invalid_dpop_proof", http.StatusBadRequest, "The DPoP proof is missing or invalid.")
ErrInvalidDPoPProof is a missing, malformed, mismatched or replayed DPoP proof.
var ErrInvalidSignature = auth.NewError("oauthprovider.invalid_signature", http.StatusUnauthorized, "The authorization request was altered or has expired.")
ErrInvalidSignature is an oauth_query whose signature, set of signed names or expiry does not verify.
var ErrNoGrantType = errors.New("oauthprovider: IssueTokens needs a GrantType")
ErrNoGrantType is an IssueTokens call without a grant type: better-auth's issueTokens refuses one, because the grant is what TokenResponseFields and the audit trail see.
var ErrResourceExists = auth.NewError("oauthprovider.resource_exists", 409, "This resource is already registered.")
ErrResourceExists is a CreateResource for an identifier already registered.
var ErrSecretNotRecoverable = auth.NewError("oauthprovider.secret_not_recoverable", http.StatusNotFound, "This client's secret cannot be read back.")
ErrSecretNotRecoverable is RevealClientSecret for a client whose secret was stored only as a hash: without EncryptClientSecrets, or created before it was turned on, or a client with no secret.
var ErrShortGenerated = errors.New("oauthprovider: a generated client id or secret is at least 22 characters, and a client id is never an https URL")
ErrShortGenerated is a GenerateClientID or GenerateClientSecret function that returned fewer than 22 characters, or a client id shaped like a metadata document URL: a programming error, so registration fails.
Functions ¶
func CertificateThumbprint ¶
func CertificateThumbprint(cert *x509.Certificate) string
CertificateThumbprint is RFC 8705's x5t#S256 of a certificate: base64url(SHA-256(DER)), unpadded. A mutual-TLS ClientAuthMethod returns it in its Confirmation.
func IsCIMDClientID ¶
IsCIMDClientID reports whether a client_id is shaped like a client metadata document URL, better-auth's isCimdClientIdUrlCandidate: an https URL. Generated client ids never are.
func ValidCIMDClientID ¶
ValidCIMDClientID applies CIMD section 3's client_id URL rules (https, a host, a path other than "/", no dot segments, fragment or user information), better-auth's validateClientIdUrl.
Types ¶
type AccessToken ¶
type AccessToken struct {
ID string `json:"id"`
ClientID string `json:"client_id"`
UserID string `json:"user_id,omitempty"`
SessionID string `json:"session_id,omitempty"`
Scopes []string `json:"scopes"`
IssuedAt time.Time `json:"issued_at"`
ExpiresAt time.Time `json:"expires_at"`
// Resource is the RFC 8707 resource the token is bound to (its aud), or
// "" for a token for no particular resource.
Resource string `json:"resource,omitempty"`
// JKT is the thumbprint of the DPoP key the token is bound to, or "".
JKT string `json:"jkt,omitempty"`
// X5TS256 is the thumbprint of the client certificate the token is
// bound to (RFC 8705), or "".
X5TS256 string `json:"x5t#S256,omitempty"`
// ReferenceID is the post-login reference (an organization) the
// authorization was given for, or "".
ReferenceID string `json:"reference_id,omitempty"`
// contains filtered or unexported fields
}
AccessToken is a verified access token, for a resource server.
func TokenFrom ¶
func TokenFrom(ctx context.Context) (AccessToken, bool)
TokenFrom returns the access token RequireToken verified for this request.
func (AccessToken) HasResource ¶
func (t AccessToken) HasResource(resource string) bool
HasResource reports whether the token was issued for resource, one of the resources it names.
func (AccessToken) HasScope ¶
func (t AccessToken) HasScope(scope string) bool
HasScope reports whether the token carries scope.
type AccessTokenClaimsFunc ¶
type AccessTokenClaimsFunc func(ctx context.Context, info AccessTokenClaimsInfo) map[string]any
AccessTokenClaimsFunc adds claims to a JWT access token, better-auth's customAccessTokenClaims. A registered or provider claim it returns is ignored.
type AccessTokenClaimsInfo ¶
type AccessTokenClaimsInfo struct {
// User is the token's user; nil for a client_credentials token.
User *auth.User
ClientID string
Scopes []string
Resource string
// ReferenceID is the post-login reference (PostLogin's
// ConsentReferenceID), or "".
ReferenceID string
}
AccessTokenClaimsInfo is what AccessTokenClaims receives.
type AuthorizeRequest ¶
type AuthorizeRequest struct {
ResponseType string `json:"response_type"`
ClientID string `json:"client_id"`
RedirectURI string `json:"redirect_uri"`
Scope string `json:"scope"`
State string `json:"state"`
CodeChallenge string `json:"code_challenge"`
CodeChallengeMethod string `json:"code_challenge_method"`
Nonce string `json:"nonce"`
Prompt string `json:"prompt"`
ResponseMode string `json:"response_mode"`
// Resource is the RFC 8707 resource indicator: a ProtectedResource the
// tokens are for. Tokens for a resource are JWTs whose aud is it.
// Several resources (the parameter repeated over HTTP) are written
// space-separated; their policies combine (resourcePolicy).
Resource string `json:"resource"`
// MaxAge is the OpenID Connect max_age in seconds: a session older than
// this signs in again.
MaxAge string `json:"max_age"`
// LoginHint is passed to the login page untouched.
LoginHint string `json:"login_hint"`
// Display and UILocales (OpenID Connect Core section 3.1.2.1) are
// passed to the login, consent and other pages untouched, for them to
// choose a layout and a language.
Display string `json:"display"`
UILocales string `json:"ui_locales"`
// IDTokenHint is an ID token this provider issued to the client earlier
// (expired is fine). When the signed-in user is not its subject, the
// browser goes to the login page, or prompt=none fails with
// login_required; one this provider did not sign for this client is
// invalid_request.
IDTokenHint string `json:"id_token_hint"`
// Request and RequestURI are refused (request_not_supported,
// request_uri_not_supported): this provider takes plain parameters.
Request string `json:"request"`
RequestURI string `json:"request_uri"`
// Claims is the OpenID Connect claims request (a JSON object); it needs
// openid. Its userinfo names are released at userinfo whatever the
// scopes, once consented.
Claims string `json:"claims"`
// DPoPJKT binds the code to a DPoP key thumbprint (RFC 9449 section
// 10): the token request must carry a proof by that key.
DPoPJKT string `json:"dpop_jkt"`
}
AuthorizeRequest is an authorization request, RFC 6749 section 4.1.1 with PKCE (RFC 7636) and the OpenID Connect parameters. Handler fills it from the query string; the json tags are the parameter names.
type AuthorizeResult ¶
type AuthorizeResult struct {
RedirectTo string `json:"redirect_to"`
}
AuthorizeResult is where to send the browser: the client's redirect URI with a code, the login page, or the consent page.
type CIMDFetchPolicy ¶
type CIMDFetchPolicy struct {
// MinimumFetchInterval paces retries of a URL whose last fetch failed.
MinimumFetchInterval time.Duration
// MaxConcurrent and MaxConcurrentPerOrigin bound fetches in flight.
MaxConcurrent int
MaxConcurrentPerOrigin int
// MaxPerMinute and MaxPerOriginPerMinute bound fetches in a rolling
// minute. A fresh document, or a request joining a fetch already in
// flight for its URL, spends none.
MaxPerMinute int
MaxPerOriginPerMinute int
}
CIMDFetchPolicy bounds metadata fetches on one instance, better-auth's metadataFetchPolicy. Zero fields take the defaults above.
type CIMDFetcher ¶
CIMDFetcher fetches a metadata document's bytes. The default refuses every address that is not public (RFC 6890 special-purpose ranges), dials the address it checked rather than resolving twice, follows no redirect, gives up after 5 seconds, and reads at most 5 KB of an exactly 200 application/json answer. Replace it only for tests, or for an egress proxy that enforces the same rules.
type CIMDOption ¶
type CIMDOption func(*cimdConfig)
CIMDOption configures ClientIDMetadataDocuments.
func CIMDAllow ¶
func CIMDAllow(fn func(clientID string) bool) CIMDOption
CIMDAllow is a policy gate run before any fetch: false refuses the client_id (invalid_client), better-auth's isMetadataDocumentUrlAllowed.
func CIMDFetch ¶
func CIMDFetch(f CIMDFetcher) CIMDOption
CIMDFetch replaces the fetcher; see CIMDFetcher.
func CIMDMaxCacheEntries ¶
func CIMDMaxCacheEntries(n int) CIMDOption
CIMDMaxCacheEntries overrides DefaultCIMDMaxCacheEntries.
func CIMDOnClientCreated ¶
func CIMDOnClientCreated(fn func(ctx context.Context, c Client, document json.RawMessage)) CIMDOption
CIMDOnClientCreated runs after a document client is first stored, better-auth's onClientCreated; best effort: a panic is recovered and logged.
func CIMDOnClientRefreshed ¶
func CIMDOnClientRefreshed(fn func(ctx context.Context, c, previous Client, document json.RawMessage)) CIMDOption
CIMDOnClientRefreshed runs after a document client is refreshed from a new document, with the client before, better-auth's onClientRefreshed; best effort.
func CIMDOriginBoundFields ¶
func CIMDOriginBoundFields(fields ...string) CIMDOption
CIMDOriginBoundFields replaces DefaultCIMDOriginBoundFields, better-auth's originBoundFields: every URL in these members must be on the client_id's origin (a loopback host and a private-use scheme are allowed in the two redirect fields). No fields turns origin binding off, which lets a document claim another site's pages; the jwks_uri is bound whatever this says.
func CIMDPolicy ¶
func CIMDPolicy(pol CIMDFetchPolicy) CIMDOption
CIMDPolicy sets the fetch governor.
func CIMDProfileMCP ¶
func CIMDProfileMCP() CIMDOption
CIMDProfileMCP requires client_name and redirect_uris, as the MCP specification's pinned CIMD draft does (better-auth's metadataProfile "mcp-2026-07-28").
func CIMDRevalidate ¶
func CIMDRevalidate(d time.Duration) CIMDOption
CIMDRevalidate overrides DefaultCIMDRevalidate.
func CIMDTransport ¶
func CIMDTransport(fn func(ctx context.Context, rawURL string, conditional http.Header) (CIMDResponse, error)) CIMDOption
CIMDTransport replaces the fetcher with one that sees the conditional headers (If-None-Match, If-Modified-Since) and answers the response, for revalidation and HTTP freshness; CIMDFetch's byte fetcher has neither.
type CIMDResponse ¶
CIMDResponse is a fetched metadata document: its status (200, or 304 for a conditional request), body and headers.
type ClaimsFunc ¶
ClaimsFunc adds claims to the ID token and the userinfo answer for a user and the scopes they granted, better-auth's customIdTokenClaims and customUserInfoClaims. A registered or standard claim it returns overrides the plugin's, except sub, iss, aud, exp, iat and nonce, which are always the plugin's.
type ClaimsInfo ¶
ClaimsInfo is what an extension's ID token and userinfo claims are decided from.
type Client ¶
type Client struct {
RegisteredMetadata
ClientID string `json:"client_id"`
Public bool `json:"public"`
SkipConsent bool `json:"skip_consent"`
RequirePKCE bool `json:"require_pkce"`
IntrospectAny bool `json:"introspect_any"`
// ClientCredentialsScopes is the client_credentials ceiling.
ClientCredentialsScopes []string `json:"client_credentials_scopes,omitempty"`
// SecretExpiresAt is when the secret stops; nil never.
SecretExpiresAt *time.Time `json:"client_secret_expires_at,omitempty"`
ReferenceID string `json:"reference_id,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
EndSession bool `json:"enable_end_session"`
Disabled bool `json:"disabled"`
OwnerID string `json:"owner_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Client is a registered client as its owner sees it, without the secret.
type ClientAuthMethod ¶
type ClientAuthMethod struct {
// Method is the token_endpoint_auth_method name clients register; it
// may not be a built-in one.
Method string
// AssertionTypes are the client_assertion_type URIs (absolute) whose
// assertion this method consumes; RFC 7523's jwt-bearer stays the
// provider's. None means the method takes the client_id alone, plus
// what Authenticate reads from the credentials (a TLS certificate).
AssertionTypes []string
// Authenticate decides whether the credentials prove the client. A
// Confirmation binds the tokens issued to it (cnf); nil binds nothing.
// Any error is invalid_client.
Authenticate func(ctx context.Context, client Client, cred ClientCredentials) (*Confirmation, error)
}
ClientAuthMethod is a token endpoint authentication method an extension serves, better-auth's clientAuthentication strategies: mutual TLS (RFC 8705), an attestation, a client assertion of another type.
type ClientCredentials ¶
type ClientCredentials struct {
// ID and Secret are the client_id and client_secret.
ID string `json:"client_id"`
Secret string `json:"client_secret"`
// Method is how they arrived: AuthMethodBasic for the Authorization
// header, AuthMethodPost for the form, AuthMethodNone for a client_id
// alone. Handler sets it; it must match the client's registered method.
Method string `json:"client_auth_method"`
// Assertion is a private_key_jwt client assertion (Method
// AuthMethodPrivateKeyJWT), or an extension method's.
Assertion string `json:"client_assertion"`
// AssertionType is the client_assertion_type of an extension method's
// assertion; "" for private_key_jwt.
AssertionType string `json:"client_assertion_type,omitempty"`
// Certificates are the TLS client certificates of the connection, for
// a mutual-TLS ClientAuthMethod. Handler fills them.
Certificates []*x509.Certificate `json:"-"`
}
ClientCredentials is how a request authenticated its client at the token, revocation and introspection endpoints. Handler fills it from the Authorization header or the form; a Go caller fills it directly.
type ClientDiscoverer ¶
type ClientDiscoverer struct {
// ID is the discovery kind stored on the client; unique, not "cimd".
ID string
// Matches reports whether a client_id is this discovery's.
Matches func(clientID string) bool
// Resolve answers the client's metadata, checked as a registration is.
// An error leaves the client unknown (and is logged).
Resolve func(ctx context.Context, clientID string) (ClientMetadata, error)
// Refresh is how long a resolved client is used before it is resolved
// again; 0 resolves it once.
Refresh time.Duration
}
ClientDiscoverer resolves a client_id that is not registered, better-auth's clientDiscovery (CIMD is one): the client is stored the first time, with its discovery kind, and resolved again after Refresh.
type ClientIDRequest ¶
type ClientIDRequest struct {
ClientID string `json:"client_id"`
}
ClientIDRequest names one client.
type ClientList ¶
type ClientList struct {
Clients []Client `json:"clients"`
}
ClientList is the owner's clients.
type ClientMetadata ¶
type ClientMetadata struct {
Name string `json:"client_name,omitempty"`
RedirectURIs []string `json:"redirect_uris"`
// GrantTypes defaults to authorization_code (and refresh_token when
// the provider serves it).
GrantTypes []string `json:"grant_types,omitempty"`
// ResponseTypes may only be ["code"]; it is accepted for RFC 7591
// clients that send it.
ResponseTypes []string `json:"response_types,omitempty"`
// AuthMethod defaults to client_secret_basic; none makes a public
// client, which has no secret and must use PKCE.
AuthMethod string `json:"token_endpoint_auth_method,omitempty"`
// Scope is the space-separated scopes the client may request; empty is
// every scope the provider supports.
Scope string `json:"scope,omitempty"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TosURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
Contacts []string `json:"contacts,omitempty"`
// PostLogoutRedirectURIs are where the end-session endpoint may send
// the browser after signing the user out; exact match, as redirects.
PostLogoutRedirectURIs []string `json:"post_logout_redirect_uris,omitempty"`
// JWKS is the client's public key set (RFC 7517 {"keys": [...]}), for
// private_key_jwt, which requires it. Public asymmetric keys only.
JWKS json.RawMessage `json:"jwks,omitempty"`
// JWKSURI is where the client publishes its key set instead of JWKS
// (one or the other, never both): an https URL on an origin the app
// trusts, or on the client_id's origin for a metadata document client.
// The set is fetched on first use and cached (see JWKSFetcher).
JWKSURI string `json:"jwks_uri,omitempty"`
// SubjectType is "public" (the default) or "pairwise", which needs the
// provider's PairwiseSecret and redirect URIs on one host.
SubjectType string `json:"subject_type,omitempty"`
// BackchannelLogoutURI receives a logout token when a session the
// client holds tokens for ends (OpenID Connect Back-Channel Logout):
// https, or http on a loopback host, no fragment.
BackchannelLogoutURI string `json:"backchannel_logout_uri,omitempty"`
// BackchannelLogoutSessionRequired says the client needs sid in the
// logout token; this provider always sends it.
BackchannelLogoutSessionRequired bool `json:"backchannel_logout_session_required,omitempty"`
// DPoPBoundAccessTokens makes every token request carry a DPoP proof,
// and every token the client gets bound to its key (RFC 9449 section 5).
DPoPBoundAccessTokens bool `json:"dpop_bound_access_tokens,omitempty"`
// Resources are the resources to link the new client to beyond
// RegistrationDefaultResources; each must be a default or one of
// RegistrationAllowedResources (invalid_target otherwise).
Resources []string `json:"resources,omitempty"`
// ApplicationType is "web" (the default) or "native", better-auth's
// redirect rules: a web client uses https on non-loopback hosts only; a
// native one https on non-loopback hosts, http on exactly localhost,
// 127.0.0.1 or [::1], or a reverse-domain private-use scheme.
ApplicationType string `json:"application_type,omitempty"`
// SoftwareID, SoftwareVersion and SoftwareStatement are RFC 7591's
// software identifiers, stored as given.
SoftwareID string `json:"software_id,omitempty"`
SoftwareVersion string `json:"software_version,omitempty"`
SoftwareStatement string `json:"software_statement,omitempty"`
// CredentialsScopes asks for a client_credentials ceiling when an owner
// creates a client through the operations, better-auth's
// client_credentials_scopes: allowed only when ClientPrivileges exists
// and grants ActionConfigureCCScopes, refused (forbidden) for dynamic
// registration without a session, and never taken from a metadata
// document. Go callers set ClientSpec.ClientCredentialsScopes instead.
CredentialsScopes []string `json:"client_credentials_scopes,omitempty"`
// InitialAccessToken is the RFC 7591 Bearer token Handler read from the
// registration request; never stored or returned.
InitialAccessToken string `json:"-"`
// Extra are the registration's members this provider does not define
// (RFC 7591 extensions). Dynamic registration keeps them as the
// client's Metadata, better-auth's metadata, without the names the
// provider owns (the client's own fields, public, type, metadata).
// Handler fills it.
Extra map[string]any `json:"-"`
// contains filtered or unexported fields
}
ClientMetadata is a client's registration, RFC 7591 section 2: what dynamic registration and create-client accept, and what a client's owner may change. Restricted fields (skip consent, PKCE relaxation, introspection of other clients' tokens) are not here: only ClientSpec, from Go, sets them.
It is an input only: the fields the provider defaults (client_name, grant_types, response_types, token_endpoint_auth_method, scope) are omitempty, so a generated client may leave them out. The answer carries RegisteredMetadata instead, where they are always present, because RFC 7591 section 3.2.1 returns every registered value. The two types have the same fields, so RegisteredMetadata(m) converts one to the other, and a field added to one and not the other stops that conversion compiling.
type ClientPrivilegesFunc ¶
type ClientPrivilegesFunc func(ctx context.Context, action string, c *auth.Call, client *Client) bool
ClientPrivilegesFunc decides whether the signed-in user may perform an action on a client (nil for create and list), better-auth's clientPrivileges; false is ErrForbidden.
type ClientResourceLink ¶
type ClientResourceLink struct {
ClientID string `json:"client_id"`
Resource string `json:"resource"`
Metadata json.RawMessage `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
ClientResourceLink is one client-resource link, better-auth's oauthClientResource row: no foreign keys (ADR-0002); deleting the client or the resource deletes its links in code.
type ClientSpec ¶
type ClientSpec struct {
ClientMetadata
// SkipConsent marks a first-party client: the user is never asked.
SkipConsent bool
// OptionalPKCE lets a confidential client omit PKCE. A public client
// always needs it, whatever this says.
OptionalPKCE bool
// IntrospectAny lets the client introspect tokens issued to any client:
// a resource server.
IntrospectAny bool
// OwnerID is the user who manages the client through the operations;
// "" is a client only Go code manages.
OwnerID string
// EnableEndSession lets the client sign the user out through the
// end-session endpoint (RP-initiated logout). It needs auth.WithSecret,
// which signs the logout confirmation; without one it is ErrNoSecret.
EnableEndSession bool
// ClientCredentialsScopes are the client_credentials ceiling,
// better-auth's clientCredentialsScopes: without them the grant is
// unauthorized_client, and an omitted scope takes them all. User scopes
// are never allowed.
ClientCredentialsScopes []string
// SecretExpiresAt ends the client secret; nil never does.
SecretExpiresAt *time.Time
// ReferenceID names an owner other than a user (an organization, a
// team), better-auth's referenceId; Metadata is the app's JSON.
ReferenceID string
Metadata json.RawMessage
// contains filtered or unexported fields
}
ClientSpec is ClientMetadata plus the restricted fields only the app's Go code may set, better-auth's admin create-client.
type Confirmation ¶
type Confirmation struct {
JKT string `json:"jkt,omitempty"`
X5TS256 string `json:"x5t#S256,omitempty"`
}
Confirmation is RFC 7800's cnf: a DPoP key thumbprint (RFC 9449) or a client certificate thumbprint (RFC 8705, which an extension ClientAuthMethod binds; the token type stays Bearer).
type Consent ¶
type Consent struct {
ID string `json:"id"`
ClientID string `json:"client_id"`
ClientName string `json:"client_name"`
Scopes []string `json:"scopes"`
// Resources are the RFC 8707 resources the consent covers, better-auth's
// consent resources: a request for another one asks again.
Resources []string `json:"resources,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Consent is one client a user has consented to, with the scopes.
type ConsentIDRequest ¶
type ConsentIDRequest struct {
ID string `json:"id"`
}
ConsentIDRequest names one of the signed-in user's consents.
type ConsentList ¶
type ConsentList struct {
Consents []Consent `json:"consents"`
}
ConsentList is the signed-in user's consents.
type ConsentRequest ¶
type ConsentRequest struct {
Query string `json:"oauth_query"`
Accept bool `json:"accept"`
// Scope, when set, grants only these of the requested scopes.
Scope string `json:"scope,omitempty"`
// Claims, when set, is an OpenID Connect claims request object
// ({"userinfo": {...}}) granting only these of the requested userinfo
// claims, better-auth's consent claims; a claim the request did not ask
// for is invalid_request.
Claims json.RawMessage `json:"claims,omitempty"`
}
ConsentRequest is the consent page's answer. Query is the authorize request's query string, which the page received; it is validated again in full, so nothing the page sends is trusted.
type ContinueRequest ¶
type ContinueRequest struct {
Query string `json:"oauth_query"`
Selected bool `json:"selected,omitempty"`
Created bool `json:"created,omitempty"`
PostLogin bool `json:"post_login,omitempty"`
}
ContinueRequest resumes an authorization from a gate's page, better-auth's oauth2Continue: Query is the signed query the page received; Selected, Created and PostLogin say which gate was passed.
type EndSessionConfirmRequest ¶
EndSessionConfirmRequest is the confirmation form's post.
type EndSessionRequest ¶
type EndSessionRequest struct {
// IDTokenHint is an ID token this provider issued. With a valid one
// naming the browser's user, the session ends at once; without one,
// or with one for someone else, a browser is asked to confirm first.
IDTokenHint string `json:"id_token_hint"`
// ClientID names the client when there is no hint; with one, it must be
// an audience of the hint.
ClientID string `json:"client_id"`
// PostLogoutRedirectURI is used only when the client registered it
// exactly; otherwise the success page says it was not registered.
PostLogoutRedirectURI string `json:"post_logout_redirect_uri"`
State string `json:"state"`
// Browser is true for a top-level browser navigation that accepts HTML,
// which Handler decides from the request; only such a request may be
// shown the confirmation page.
Browser bool `json:"-"`
}
EndSessionRequest is an OpenID Connect RP-Initiated Logout request.
type EndSessionResult ¶
type EndSessionResult struct {
RedirectTo string `json:"redirect_to,omitempty"`
// Page is PageConfirm, PageSuccess, or "" for a non-browser caller that
// needs no page.
Page string `json:"page,omitempty"`
// Notice is shown on the success page.
Notice string `json:"notice,omitempty"`
}
EndSessionResult is where to send the browser, or which page to show.
type Extension ¶
type Extension struct {
// ID names the extension in errors and logs; unique and not empty.
ID string
// Grants are extension grants, as WithGrant registers them.
Grants []ExtensionGrant
// Metadata adds members to the discovery document; a member the
// provider sets (or an earlier extension added) is kept.
Metadata map[string]any
// Claims adds claims to the tokens and userinfo.
Claims ExtensionClaims
// ClientAuthentication adds token endpoint authentication methods.
ClientAuthentication []ClientAuthMethod
// ClientDiscovery resolves client_ids the provider has not stored.
ClientDiscovery []ClientDiscoverer
}
Extension is one module's additions to the provider, better-auth's OAuthProviderExtension: extension grants, discovery fields, claims, client authentication methods and client discovery. Extend installs a list of them (better-auth's extensions). Everything an extension adds is additive: it never replaces what the provider or the app set, and between two extensions the first wins.
type ExtensionClaims ¶
type ExtensionClaims struct {
// AccessToken adds claims to JWT access tokens; the per-issuance
// GrantDecision.AccessTokenClaims, AccessTokenClaims and a resource's
// CustomClaims override them, in that order.
AccessToken func(ctx context.Context, info AccessTokenClaimsInfo) map[string]any
// IDToken adds claims to ID tokens; a claim a granted scope releases
// (email, name) is never taken from it.
IDToken func(ctx context.Context, info ClaimsInfo) map[string]any
// UserInfo adds claims to the userinfo response, the same way.
UserInfo func(ctx context.Context, info ClaimsInfo) map[string]any
}
ExtensionClaims are an extension's claims. Each is additive: a claim the provider, the app's own options, or an earlier extension set is kept, and the reserved claims (iss, sub, aud, exp, cnf and the rest) are never taken from it.
type ExtensionGrant ¶
type ExtensionGrant struct {
// Type is the grant_type, an absolute URI such as
// "urn:ietf:params:oauth:grant-type:device_code".
Type string
// Handle validates the request and answers who the tokens are for. Its
// errors reach the client: see ErrorNamespace.
Handle func(ctx context.Context, client Client, in TokenRequest) (GrantDecision, error)
// ErrorNamespace, when set, makes an *auth.Error whose code is
// "<namespace>.<rest>" answer error "<rest>" on the wire, so a plugin's
// own codes (device.authorization_pending) keep their RFC names.
ErrorNamespace string
}
ExtensionGrant serves one extension grant_type at the token endpoint, better-auth's extendOAuthProvider({grants}): the device authorization grant (RFC 8628) is one. The provider authenticates the client and checks it registered the grant type before calling Handle; Handle decides who the tokens are for, and the provider issues them through the same path as every other grant (refresh and ID token rules unchanged).
type GateInfo ¶
GateInfo is what a gate decides from: the signed-in call, the client and the requested scopes.
type GrantDecision ¶
type GrantDecision struct {
// UserID is who the tokens act for; "" is the client itself, as in
// client_credentials (no refresh token, no ID token, no user scope).
UserID string
// SessionID is recorded on the token row; optional.
SessionID string
// Scopes are the granted scopes; each must be allowed for the client
// (and the resource), or the request is invalid_scope.
Scopes []string
// Resource is the RFC 8707 resource, or "".
Resource string
// AuthTime is when the user authenticated, for the ID token.
AuthTime *time.Time
// AccessTokenClaims are this issuance's JWT access token claims: over
// the extensions', under AccessTokenClaims and the resource's.
AccessTokenClaims map[string]any
// IDTokenClaims are added to the ID token, never replacing a claim.
IDTokenClaims map[string]any
}
GrantDecision is what an extension grant decided.
type GuardedToken ¶
type GuardedToken struct {
Subject string
ClientID string
Scopes []string
Claims map[string]json.RawMessage
}
GuardedToken is what ResourceGuard verified, in the request context (GuardedTokenFrom).
func GuardedTokenFrom ¶
func GuardedTokenFrom(ctx context.Context) (GuardedToken, bool)
GuardedTokenFrom returns the token ResourceGuard verified.
type InitialAccessTokenFunc ¶
InitialAccessTokenFunc validates an RFC 7591 initial access token sent as a Bearer token to the registration endpoint, better-auth's validateInitialAccessToken: ok admits the registration (even when open registration is off), and referenceID becomes the client's ReferenceID.
type IntrospectRequest ¶
type IntrospectRequest struct {
Token string `json:"token"`
TokenTypeHint string `json:"token_type_hint"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
ClientAuthMethod string `json:"client_auth_method"`
// ClientAssertion is a private_key_jwt assertion; Handler checks
// client_assertion_type.
ClientAssertion string `json:"client_assertion"`
// ClientAssertionType and ClientCertificates are for an extension
// ClientAuthMethod (an assertion of its type, mutual TLS).
ClientAssertionType string `json:"client_assertion_type,omitempty"`
ClientCertificates []*x509.Certificate `json:"-"`
}
IntrospectRequest is RFC 7662 section 2.1, with the caller's client credentials.
type Introspection ¶
type Introspection struct {
Active bool `json:"active"`
Scope string `json:"scope,omitempty"`
ClientID string `json:"client_id,omitempty"`
Subject string `json:"sub,omitempty"`
TokenType string `json:"token_type,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
IssuedAt int64 `json:"iat,omitempty"`
Issuer string `json:"iss,omitempty"`
Audience string `json:"aud,omitempty"`
// Audiences are the resources of a token issued for several; aud is
// then written as their array, and Audience holds them space-separated.
Audiences []string `json:"-"`
// Confirmation is cnf: the DPoP key a bound token needs.
Confirmation *Confirmation `json:"cnf,omitempty"`
// Extra are the token's resource's custom claims; a member named like
// one of the fields above is ignored.
Extra map[string]any `json:"-"`
}
Introspection is RFC 7662 section 2.2. An inactive token answers only active false.
func (Introspection) MarshalJSON ¶
func (in Introspection) MarshalJSON() ([]byte, error)
MarshalJSON writes the fields and, beside them, Extra's claims that do not collide with a field or a protected claim.
type IssueRequest ¶
type IssueRequest struct {
// GrantType names the grant the tokens come from. Required.
GrantType string
// ClientID is the client the tokens are for; it must exist and be
// enabled.
ClientID string
// UserID and SessionID are the user the tokens act for and the session
// behind them; "" for a client's own token, which then cannot carry a
// user scope.
UserID, SessionID string
Scopes []string
// Resource makes the access token a JWT for that resource (RFC 8707).
Resource string
// Nonce and AuthTime go into the ID token.
Nonce string
AuthTime *time.Time
// Confirmation binds the tokens (cnf): a DPoP key thumbprint or a
// client certificate's, better-auth's issueTokens confirmation. The
// token type becomes DPoP for a JKT.
Confirmation *Confirmation
// ReferenceID is the post-login reference the authorization was given
// for.
ReferenceID string
}
IssueRequest is what IssueTokens mints, better-auth's OAuthTokenIssueParams. It is a raw minting primitive: the caller has already authenticated the client and decided that these scopes, this user and this resource are allowed (CheckScopes helps); nothing here asks the user.
type JWKSFetcher ¶
JWKSFetcher fetches a client's jwks_uri, answering the body of an exactly 200 response of a JSON media type. The default refuses every address that is not public, follows no redirect, gives up after 5 seconds, and reads at most 64 KiB. Replace it only for tests, or for an egress proxy that enforces the same rules.
type Metadata ¶
type Metadata struct {
// Extra are the extensions' members (Extension.Metadata), written beside
// the provider's and never replacing one.
Extra map[string]any `json:"-"`
Issuer string `json:"issuer"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
TokenEndpoint string `json:"token_endpoint"`
UserInfoEndpoint string `json:"userinfo_endpoint,omitempty"`
JWKSURI string `json:"jwks_uri"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
RevocationEndpoint string `json:"revocation_endpoint"`
EndSessionEndpoint string `json:"end_session_endpoint,omitempty"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint,omitempty"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
ScopesSupported []string `json:"scopes_supported"`
ResponseTypesSupported []string `json:"response_types_supported"`
ResponseModesSupported []string `json:"response_modes_supported"`
GrantTypesSupported []string `json:"grant_types_supported"`
SubjectTypesSupported []string `json:"subject_types_supported"`
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported,omitempty"`
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
IntrospectionAuthMethodsSupported []string `json:"introspection_endpoint_auth_methods_supported"`
RevocationAuthMethodsSupported []string `json:"revocation_endpoint_auth_methods_supported"`
// The private_key_jwt assertion algorithms, every jwt.AlgValues entry,
// for the three endpoints that authenticate clients.
TokenAuthSigningAlgValuesSupported []string `json:"token_endpoint_auth_signing_alg_values_supported"`
IntrospectionAuthSigningAlgValuesSupported []string `json:"introspection_endpoint_auth_signing_alg_values_supported"`
RevocationAuthSigningAlgValuesSupported []string `json:"revocation_endpoint_auth_signing_alg_values_supported"`
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
ClaimsSupported []string `json:"claims_supported,omitempty"`
PromptValuesSupported []string `json:"prompt_values_supported"`
IssParameterSupported bool `json:"authorization_response_iss_parameter_supported"`
CIMDSupported bool `json:"client_id_metadata_document_supported,omitempty"`
ClaimsParameterSupported bool `json:"claims_parameter_supported,omitempty"`
BackchannelLogoutSupported bool `json:"backchannel_logout_supported,omitempty"`
BackchannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported,omitempty"`
ACRValuesSupported []string `json:"acr_values_supported,omitempty"`
RequestParameterSupported bool `json:"request_parameter_supported"`
RequestURIParameterSupported bool `json:"request_uri_parameter_supported"`
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported"`
}
Metadata is the authorization server metadata (RFC 8414) and OpenID Connect Discovery document.
func (Metadata) MarshalJSON ¶
MarshalJSON writes the document and, beside it, Extra's members that do not collide with one of its own.
type Option ¶
type Option func(*Plugin)
Option configures the plugin; pass options to New.
func AccessTokenClaims ¶
func AccessTokenClaims(fn AccessTokenClaimsFunc) Option
AccessTokenClaims installs an AccessTokenClaimsFunc.
func AccessTokenTTL ¶
AccessTokenTTL overrides DefaultAccessTokenTTL.
func AdvertisedClaims ¶
AdvertisedClaims replaces claims_supported in discovery, better-auth's advertisedMetadata.claims_supported.
func AdvertisedScopes ¶
AdvertisedScopes replaces scopes_supported in discovery with a subset of Scopes, better-auth's advertisedMetadata.scopes_supported; the provider still serves every scope.
func AllowDynamicRegistration ¶
func AllowDynamicRegistration() Option
AllowDynamicRegistration opens RFC 7591 registration at <base>/register to signed-in users, better-auth's allowDynamicClientRegistration. Off by default: anyone who can register a client can show your users a consent screen under a name they chose.
func AllowPublicClientPrelogin ¶
func AllowPublicClientPrelogin() Option
AllowPublicClientPrelogin serves the public-client-prelogin operation: the consent or login page may read a client's public fields before the user signs in, with the signed query it was given, better-auth's allowPublicClientPrelogin.
func AllowUnauthenticatedRegistration ¶
func AllowUnauthenticatedRegistration() Option
AllowUnauthenticatedRegistration lets a caller with no session register too, better-auth's allowUnauthenticatedClientRegistration, which MCP clients expect. It implies AllowDynamicRegistration. Such a client has no owner, so only the app's Go code can manage it.
func AssertionMaxLifetime ¶
AssertionMaxLifetime overrides DefaultAssertionMaxLifetime.
func BackchannelHTTPClient ¶
BackchannelHTTPClient replaces the client that delivers logout tokens, for tests or an egress proxy; it must not follow redirects. The default follows none and times out after 5 seconds.
func CacheResources ¶
CacheResources keeps the registered resources the provider reads when it issues, refreshes or introspects a token in this process's memory for ttl (0 is DefaultResourceCacheTTL, at most MaxResourceCacheTTL), better-auth's cachedResources. Without it each issuance reads the resource's row by primary key. Off by default. CacheResourcesIn keeps them in a store of the app's choosing instead, such as Redis.
CreateResource, UpdateResource, PatchResource, SeedResources and DeleteResource drop the entry once they have written, so this instance's own writes are seen at once (and every instance's, with a shared store). What stays true only up to the TTL, as in better-auth: a resource created, changed, disabled or deleted on another instance with a per-process cache (or by SQL), or inside a transaction a concurrent issuance read before it committed, is served as it was until the entry expires. Resource and Resources, the admin reads, always read the database. An identifier with no row is remembered only when it is a ProtectedResource declared in code, so a client asking for random identifiers cannot grow the cache. A failing store is logged and the database is read instead.
func CacheResourcesIn ¶
func CacheResourcesIn(kv auth.SecondaryStorage, ttl time.Duration) Option
CacheResourcesIn is CacheResources in kv instead of this process's memory: any auth.SecondaryStorage, such as contrib/redis's NewStorage or the one the app already passes to auth.WithSecondaryStorage, so every instance sees one cache and a write on any instance drops the entry for all of them. Only Get, Set and Delete are used. Entries carry their own expiry, judged by the core's clock, and are stored with ttl so the store also forgets them. nil is refused (auth.ErrNilOption).
func Claims ¶
func Claims(fn ClaimsFunc) Option
Claims adds the app's claims to ID tokens and userinfo answers.
func ClientIDMetadataDocuments ¶
func ClientIDMetadataDocuments(opts ...CIMDOption) Option
ClientIDMetadataDocuments accepts client ids that are https URLs of client metadata documents. Off by default: it makes the provider fetch URLs a caller chooses, which the default fetcher confines to public addresses.
func ClientJWKSFetch ¶
func ClientJWKSFetch(f JWKSFetcher) Option
ClientJWKSFetch replaces the jwks_uri fetcher; see JWKSFetcher.
func ClientPrivileges ¶
func ClientPrivileges(fn ClientPrivilegesFunc) Option
ClientPrivileges installs a ClientPrivilegesFunc over the owner operations (create, get, list, update, rotate, delete).
func ClientReference ¶
ClientReference makes clients belong to a reference (an organization, a team) instead of the user, better-auth's clientReference: fn returns the signed-in user's current reference (such as the active organization), a created client takes it as ReferenceID with no owning user, and the owner operations see the clients of that reference. "" falls back to the user.
func ConsentPage ¶
ConsentPage is where the authorize endpoint sends a signed-in user who has not yet consented to the client and scopes, with the authorize request's parameters appended. The page shows the client (PublicClient) and posts the user's answer to the consent operation, which answers the URL to send the browser to. Without a ConsentPage, a client that needs consent is answered consent_required; clients created with SkipConsent never need it.
func DPoPProofMaxAge ¶
DPoPProofMaxAge overrides DefaultDPoPProofMaxAge.
func DPoPSigningAlgorithms ¶
DPoPSigningAlgorithms replaces DPoPAlgorithms, better-auth's dpop.signingAlgorithms: the proof algorithms accepted, advertised as dpop_signing_alg_values_supported and named in a DPoP challenge.
func DeviceAuthorizationEndpoint ¶
DeviceAuthorizationEndpoint advertises the RFC 8628 device authorization endpoint in discovery; the module serving it passes its URL.
func EncryptClientSecrets ¶
func EncryptClientSecrets(e SecretEncryption) Option
EncryptClientSecrets also stores each new client secret encrypted, better-auth's storeClientSecret "encrypted", so RevealClientSecret can read it back (for client_secret_jwt-style use, or to show it again). Authentication still compares the hash in constant time. Off by default because a recoverable secret is a weaker store than a hash: whoever holds the key and a copy of the database holds every secret. auth.New logs a warning while it is on.
func EnforcePerClientResources ¶
EnforcePerClientResources(false) lets any client ask for any resource; the default (true, as better-auth) needs a client-resource link.
func FormatRefreshToken ¶
func FormatRefreshToken(f RefreshTokenFormat) Option
FormatRefreshToken installs a RefreshTokenFormat; both functions are required.
func GenerateAccessToken ¶
func GenerateAccessToken(fn TokenGenerator) Option
GenerateAccessToken replaces the opaque access token generator.
func GenerateClientID ¶
GenerateClientID replaces the client id generator, better-auth's generateClientId. It must return at least 22 characters and never an https URL (ErrShortGenerated otherwise, at registration).
func GenerateClientSecret ¶
GenerateClientSecret replaces the random part of a client secret (after the ubos_ prefix), better-auth's generateClientSecret; at least 22 characters from a cryptographic source.
func GenerateRefreshToken ¶
func GenerateRefreshToken(fn TokenGenerator) Option
GenerateRefreshToken replaces the refresh token generator.
func GrantTypes ¶
GrantTypes narrows DefaultGrantTypes. refresh_token needs authorization_code (ErrBadGrantTypes otherwise), as better-auth requires.
func HashStoredSecrets ¶
func HashStoredSecrets(fn SecretHasher) Option
HashStoredSecrets replaces SHA-256 as the stored form of every secret; see SecretHasher. better-auth's "encrypted" client secrets exist for client_secret_jwt, which needs the plaintext back; this provider does not offer that method, so it keeps only one-way forms.
func IdentifierValidator ¶
IdentifierValidator replaces the RFC 8707 identifier check (absolute URI, no fragment), better-auth's identifierValidator.
func Issuer ¶
Issuer is the provider's identifier, the iss of every ID token and the base of every endpoint URL: an absolute https URL with no query or fragment, such as "https://auth.example.com". Required. The jwt plugin's Issuer must be the same string.
func LoginPage ¶
LoginPage is where the authorize endpoint sends a browser that is not signed in, with the authorize request's parameters appended to its query. After signing the user in, the page sends the browser to Plugin.ResumeURL(those parameters). Required: a same-site path or a URL on a trusted origin.
func M2MAccessTokenTTL ¶
M2MAccessTokenTTL is the lifetime of a client_credentials access token, better-auth's m2mAccessTokenExpiresIn; 0 takes AccessTokenTTL.
func MCP ¶
func MCP() Option
MCP configures the provider for Model Context Protocol clients, better-auth's mcp(): open unauthenticated dynamic registration (MCP clients register themselves) and a MCPRefreshReuseInterval reuse window. Options after it override either. There is no separate MCP plugin: the resource side is RequireToken (in process) or ResourceGuard (another process), and ProtectedResource declares the MCP server's URL and scopes.
func NoDefaultRateLimits ¶
func NoDefaultRateLimits() Option
NoDefaultRateLimits drops every DefaultRateLimits rule.
func PairwiseSecret ¶
PairwiseSecret enables pairwise subject identifiers, better-auth's pairwiseSecret: a client registered with subject_type "pairwise" sees each user under its own sub, so two clients cannot join their users by sub. At least 32 characters; changing it changes every pairwise sub.
func PostLogin ¶
func PostLogin(g PostLoginGate) Option
PostLogin installs the post-login gate; it needs auth.WithSecret, which signs the marker the consent page carries once the gate is passed (auth.New refuses it without one, with ErrBadGate and ErrNoSecret).
func ProtectedResource ¶
ProtectedResource declares a resource server this provider issues tokens for, such as an MCP server: its canonical URL (RFC 8707 / 9728: https, or http on loopback, with no query or fragment) and the scopes it accepts. Handler then serves its RFC 9728 metadata, which MCP clients read to find the authorization server. better-auth's mcp({resource}).
func RateLimit ¶
RateLimit sets the per-IP limit of one endpoint to limit calls per window, replacing its default. limit 0 removes the endpoint's default, better-auth's rateLimit.<endpoint>: false.
func RefreshReplay ¶
func RefreshReplay(e SecretEncryption) Option
RefreshReplay makes a refresh token reused inside RefreshReuseInterval answer the response its first use got, better-auth's refreshTokenReuseInterval replay, instead of a new pair: the response is stored encrypted until the window ends, and a reuse asking for other scopes, another resource or another DPoP key is invalid_grant without revoking the family. Needs RefreshReuseInterval. Off by default because it stores issued tokens in a recoverable form; auth.New logs a warning while it is on.
func RefreshReuseInterval ¶
RefreshReuseInterval lets a refresh token that was just rotated be presented again within d without revoking its family, for a client that refreshes from two tabs or retries after a lost response. Each such reuse issues a new pair; after d, reuse revokes the family as before. 0 (the default) allows no reuse. better-auth's refreshTokenReuseInterval, which replays the cached response instead: this library keeps no plaintext token to replay.
func RefreshTokenTTL ¶
RefreshTokenTTL overrides DefaultRefreshTokenTTL.
func RefuseJWTRevocation ¶
func RefuseJWTRevocation() Option
RefuseJWTRevocation answers the revocation of a still-valid JWT access token with unsupported_token_type (an expired or unknown one is a 200 no-op), better-auth's behaviour, which tells the client the token stays usable by offline verifiers until it expires. Off by default: the provider then revokes it, so in-process checks and introspection refuse it at once. auth.New logs a warning while it is on.
func RegistrationAllowedResources ¶
RegistrationAllowedResources are the extra resources a registration may ask for in its resources member, beyond the defaults, better-auth's clientRegistrationAllowedResources.
func RegistrationAllowedScopes ¶
RegistrationAllowedScopes widens what a registration may ask for beyond RegistrationDefaultScopes, better-auth's clientRegistrationAllowedScopes: a registration naming a scope outside both lists is invalid_scope. With neither option, any scope the provider supports may be registered.
func RegistrationDefaultResources ¶
RegistrationDefaultResources are linked to every client created through registration, the operations or Go, better-auth's clientRegistrationDefaultResources.
func RegistrationDefaultScopes ¶
RegistrationDefaultScopes are the scopes a dynamically registered client gets when its registration names none, better-auth's clientRegistrationDefaultScopes; nil gives it every scope (Scopes).
func RegistrationRequirePKCE ¶
RegistrationRequirePKCE(false) lets confidential clients created by dynamic registration omit PKCE, better-auth's clientRegistrationRequirePKCE (default true). Public clients always need it.
func RegistrationSecretExpiration ¶
RegistrationSecretExpiration gives secrets of dynamically registered confidential clients a lifetime, better-auth's clientRegistrationClientSecretExpiration; 0 (the default) never expires.
func RegistrationSecretExpiresAt ¶
RegistrationSecretExpiresAt ends the secrets of dynamically registered confidential clients at one instant, better-auth's clientRegistrationClientSecretExpiration given a Date; a time already past on the app's clock (auth.WithClock) is refused at auth.New (ErrBadTTL). With RegistrationSecretExpiration too, the earlier end wins.
func RequestURIResolver ¶
func RequestURIResolver(fn RequestURIResolverFunc) Option
RequestURIResolver accepts request_uri at authorize: every parameter comes from the stored request, except client_id, which the authorization URL carries. Without it, request_uri is request_uri_not_supported.
func RevokeAccessTokenOnly ¶
func RevokeAccessTokenOnly() Option
RevokeAccessTokenOnly makes revoking an access token end that token only, better-auth's behaviour: its refresh token keeps working. Off by default, because a client that revokes an access token after a leak usually wants the whole grant gone, and RFC 7009 lets the server end both; auth.New logs a warning while it is on.
func ScopeExpirations ¶
ScopeExpirations gives scopes shorter access-token lifetimes, better-auth's scopeExpirations: a token carrying several gets the earliest. A value longer than the default changes nothing.
func Scopes ¶
Scopes replaces DefaultScopes with every scope the provider supports. Leaving ScopeOpenID out makes it a plain OAuth 2.1 server: no ID tokens, no userinfo and no openid-configuration document.
func SelectAccount ¶
func SelectAccount(g SelectAccountGate) Option
SelectAccount installs the select-account gate.
func SelectAccountFallsBackToLogin ¶
func SelectAccountFallsBackToLogin() Option
SelectAccountFallsBackToLogin sends prompt=select_account, when no SelectAccount page is configured, to the login page so the user can sign in as someone else; the default is better-auth's unsupported_prompt_select_account.
func SignupPage ¶
SignupPage is where prompt=create sends the browser, with the authorize parameters, better-auth's signup.page. Without it, prompt=create goes to LoginPage.
func SignupRedirect ¶
func SignupRedirect(fn SignupRedirectFunc) Option
SignupRedirect installs the sign-up gate.
func TokenResponseFields ¶
func TokenResponseFields(fn func(ctx context.Context, info TokenFieldsInfo) (map[string]any, error)) Option
TokenResponseFields adds members to the token response envelope (not to the token), better-auth's customTokenResponseFields; the standard members cannot be replaced, and an error from fn fails the request.
func TrustedClients ¶
func TrustedClients(clients ...TrustedClient) Option
TrustedClients installs in-memory clients.
func UserInfoInsufficientScope ¶
func UserInfoInsufficientScope() Option
UserInfoInsufficientScope answers userinfo for a token without openid with RFC 6750's 403 insufficient_scope instead of better-auth's 400 invalid_scope (the default).
func ValidateInitialAccessToken ¶
func ValidateInitialAccessToken(fn InitialAccessTokenFunc) Option
ValidateInitialAccessToken installs an InitialAccessTokenFunc. Without one, a registration that presents a token is refused (invalid_token) rather than falling through to open registration.
func ValidateRedirectURI ¶
func ValidateRedirectURI(fn RedirectValidator) Option
ValidateRedirectURI installs a RedirectValidator.
func WithGrant ¶
func WithGrant(g ExtensionGrant) Option
WithGrant registers an extension grant and adds its type to the grant types the provider serves and advertises.
type Plugin ¶
type Plugin struct {
// contains filtered or unexported fields
}
Plugin is the OAuth 2.1 / OpenID Connect provider. Build it with New.
func New ¶
New builds the provider. signer signs ID tokens and publishes its keys at <base>/jwks: usually the jwt plugin, which must then also be in auth.WithPlugins, before this plugin. Any other Signer that is itself a plugin must be registered before this one too; one that is not a plugin needs no registration.
func (*Plugin) AuthenticateClient ¶
func (p *Plugin) AuthenticateClient(ctx context.Context, cred ClientCredentials, audience string) (Client, error)
AuthenticateClient checks a client's credentials as the token endpoint does, for a module that serves an endpoint of its own (a device authorization endpoint). audience is the absolute URL a private_key_jwt assertion must name (that endpoint's URL; the issuer is accepted too). Every failure is ErrInvalidClient.
func (*Plugin) Authorize ¶
func (p *Plugin) Authorize(ctx context.Context, in AuthorizeRequest) (AuthorizeResult, error)
Authorize runs an authorization request from Go: the redirect to answer, or an error. A *RedirectError carries the redirect that reports it to the client; any other error must be shown to the user, never redirected.
func (*Plugin) CheckScopes ¶
func (p *Plugin) CheckScopes(ctx context.Context, client Client, scopes []string, resource string) ([]string, error)
CheckScopes decides the scopes a client may be granted for resource ("" for none), for a module that issues through WithGrant: ErrInvalidScope for a scope the provider or the client does not allow; for a resource, ErrInvalidTarget when it is unknown, disabled or (EnforcePerClientResources) not linked to the client, and the scopes narrowed to its allowlist (ErrInvalidScope when none is left). It answers the effective scopes.
func (*Plugin) ClientResourceLinks ¶
func (p *Plugin) ClientResourceLinks(ctx context.Context, clientID string) ([]ClientResourceLink, error)
ClientResourceLinks lists the resources a client is linked to, with each link's metadata.
func (*Plugin) Clients ¶
func (p *Plugin) Clients(ctx context.Context) (ClientList, error)
Clients lists the signed-in user's clients.
func (*Plugin) ConfirmEndSession ¶
func (p *Plugin) ConfirmEndSession(ctx context.Context, in EndSessionConfirmRequest) (EndSessionResult, error)
ConfirmEndSession completes a confirmed logout from Go.
func (*Plugin) Consent ¶
func (p *Plugin) Consent(ctx context.Context, in ConsentRequest) (AuthorizeResult, error)
Consent records the signed-in user's answer from Go; see ConsentRequest.
func (*Plugin) ConsentByID ¶
ConsentByID returns one of the signed-in user's consents.
func (*Plugin) Consents ¶
func (p *Plugin) Consents(ctx context.Context) (ConsentList, error)
Consents lists the signed-in user's consents.
func (*Plugin) ConsumeClientAssertion ¶
func (p *Plugin) ConsumeClientAssertion(ctx context.Context, clientID, assertion, audience string) error
ConsumeClientAssertion verifies a private_key_jwt client assertion (RFC 7523) for the client it names and spends its jti, better-auth's consumeClientAssertion: signed by a key of the client's jwks or jwks_uri, iss and sub the client, aud audience (the endpoint URL) or the issuer, exp within AssertionMaxLifetime, and a jti never seen before. Every failure is ErrInvalidClient.
func (*Plugin) Continue ¶
func (p *Plugin) Continue(ctx context.Context, in ContinueRequest) (AuthorizeResult, error)
Continue resumes an authorization after a gate's page, from Go.
func (*Plugin) CreateClient ¶
func (p *Plugin) CreateClient(ctx context.Context, spec ClientSpec) (Registration, error)
CreateClient registers a client from Go, with the restricted fields only trusted code may set (better-auth's admin create-client). It runs outside the operation pipeline on purpose, like jwt.Sign: nothing a request sends reaches it.
func (*Plugin) CreateOwnedClient ¶
func (p *Plugin) CreateOwnedClient(ctx context.Context, in ClientMetadata) (Registration, error)
CreateOwnedClient registers a client the signed-in user owns. It needs a fresh sign-in, because the secret outlives the session that made it.
func (*Plugin) CreateResource ¶
CreateResource registers a resource from Go, better-auth's admin POST /admin/oauth2/resources: invalid_target for a bad identifier, resource_exists for a duplicate.
func (*Plugin) DeleteClient ¶
DeleteClient deletes any client from Go, with its codes, tokens and consents.
func (*Plugin) DeleteOwnedClient ¶
DeleteOwnedClient deletes an owned client with its codes, tokens and consents.
func (*Plugin) DeleteResource ¶
DeleteResource removes a resource and its client links; tokens issued for it become inactive.
func (*Plugin) EndSession ¶
func (p *Plugin) EndSession(ctx context.Context, in EndSessionRequest) (EndSessionResult, error)
EndSession runs an RP-initiated logout from Go.
func (*Plugin) Handler ¶
Handler serves the protocol endpoints and the discovery documents, at their paths under the issuer: mount it at the root of the issuer's host, or at each path. opts are the authhttp options the app's auth handler uses (cookie prefix, trusted proxies), so the authorize endpoint reads the same session. Every endpoint calls its operation through the pipeline; an unknown path answers 404.
func (*Plugin) HashToken ¶
func (p *Plugin) HashToken(token string, kind SecretKind) []byte
HashToken is the stored form of a token or secret, better-auth's hashToken: SHA-256, or HashStoredSecrets' function. A caller uses it to find or revoke a row by a token's value; the provider never stores the value itself.
func (*Plugin) Init ¶
Init captures the core, checks the pages against the trusted origins, and checks that the jwt plugin signs with the provider's issuer.
func (*Plugin) Introspect ¶
func (p *Plugin) Introspect(ctx context.Context, in IntrospectRequest) (Introspection, error)
Introspect runs an introspection request from Go.
func (*Plugin) IssueTokens ¶
func (p *Plugin) IssueTokens(ctx context.Context, in IssueRequest) (TokenResponse, error)
IssueTokens mints an access token (and a refresh token with offline_access, and an ID token with openid) outside the token endpoint, better-auth's getOAuthProviderApi(...).issueTokens: for an app endpoint that runs its own grant. The resource's policy, the lifetimes, the token response fields and the hashed storage all apply as at the token endpoint.
func (*Plugin) LinkClient ¶
func (p *Plugin) LinkClient(ctx context.Context, identifier, clientID string) (alreadyLinked bool, err error)
LinkClient lets a client ask for a resource; linking twice answers alreadyLinked true and changes nothing, as better-auth.
func (*Plugin) LinkClientWithMetadata ¶
func (p *Plugin) LinkClientWithMetadata(ctx context.Context, identifier, clientID string, metadata json.RawMessage) (alreadyLinked bool, err error)
LinkClientWithMetadata is LinkClient with the link's metadata, better-auth's oauthClientResource.metadata: any JSON value (invalid_request otherwise). A link that exists already keeps its metadata.
func (*Plugin) Operations ¶
Operations lists every operation; the self-served ones are wired but mounted only by Handler.
func (*Plugin) OptionErr ¶
OptionErr is every error the options recorded, such as auth.ErrNilOption naming an option given nil; auth.New refuses to start while it is not nil (auth.OptionChecker).
func (*Plugin) OwnedClient ¶
OwnedClient returns one of the signed-in user's clients; another user's is ErrClientNotFound.
func (*Plugin) PatchResource ¶
func (p *Plugin) PatchResource(ctx context.Context, identifier string, patch ResourcePatch) (Resource, error)
PatchResource applies a ResourcePatch to the resource identifier names and returns the result; ErrInvalidTarget when there is none. Two patches racing each other can lose one field update; an admin edit is rare enough that the provider takes no lock for it.
func (*Plugin) PublicClientInfo ¶
PublicClientInfo returns what a consent page may show about a client.
func (*Plugin) PublicClientPrelogin ¶
func (p *Plugin) PublicClientPrelogin(ctx context.Context, in PreloginRequest) (PublicClient, error)
PublicClientPrelogin returns a client's public fields to a page that holds the signed query the provider gave it, before sign-in; it needs AllowPublicClientPrelogin.
func (*Plugin) RateLimits ¶
RateLimits declares the per-IP rules: the defaults unless dropped, with every RateLimit override on top.
func (*Plugin) Register ¶
func (p *Plugin) Register(ctx context.Context, in ClientMetadata) (Registration, error)
Register runs RFC 7591 registration from Go, as Handler's register endpoint does: closed unless AllowDynamicRegistration, and open to a caller with no session only under AllowUnauthenticatedRegistration.
func (*Plugin) RequireToken ¶
RequireToken guards a resource server route, such as an MCP endpoint: it verifies the bearer access token (VerifyAccessToken) and the required scopes, and puts the token in the context (TokenFrom). A missing or bad token is 401 and a missing scope 403, each with the WWW-Authenticate Bearer challenge of RFC 6750 and RFC 9728 (resource_metadata, scope). better-auth's requireMcpAuth.
func (*Plugin) Requires ¶
Requires names the signer when it is a plugin (the jwt plugin by default), so auth.New refuses a configuration that forgot to register it; a Signer that is not a plugin requires nothing.
func (*Plugin) Resource ¶
Resource returns a registered resource; ErrInvalidTarget when there is none.
func (*Plugin) ResourceMetadataFor ¶
func (p *Plugin) ResourceMetadataFor(resource string) (ResourceMetadata, bool)
ResourceMetadataFor builds the document for a declared resource; false when it was not declared with ProtectedResource. User scopes (openid, profile, email, offline_access) are never advertised, as better-auth does not: they are the provider's, not the resource's.
func (*Plugin) ResumeURL ¶
ResumeURL is the authorize endpoint with query, for the login page to send the browser back to once the user has signed in. query is the parameters the login page received.
func (*Plugin) RevealClientSecret ¶
RevealClientSecret reads a client's secret back under EncryptClientSecrets; ErrSecretNotRecoverable otherwise. It is Go-only: no operation exposes it.
func (*Plugin) Revoke ¶
func (p *Plugin) Revoke(ctx context.Context, in RevokeRequest) error
Revoke runs a revocation request from Go.
func (*Plugin) RevokeConsent ¶
RevokeConsent withdraws the signed-in user's consent to a client and revokes the client's tokens for them.
func (*Plugin) RotateSecret ¶
RotateSecret replaces an owned confidential client's secret; the old one stops at once.
func (*Plugin) SeedResources ¶
SeedResources writes resources from the app's configuration into the registry, better-auth's resources option with resourceSeedMode: call it at startup, after migrations (better-auth seeds at init and defers when the table is missing; this library leaves the moment to the app, which knows when its migrations ran). An identifier the registry refuses is ErrInvalidTarget, and nothing after it is written. Safe to run on every start and from several processes at once: a concurrent insert of the same resource is treated as the row existing.
func (*Plugin) SessionEnded ¶
SessionEnded ends what this provider issued on a session: its access tokens stop, its refresh tokens too unless offline_access was granted (Back-Channel Logout section 2.7), and every client with a backchannel_logout_uri is sent a logout token after the commit.
func (*Plugin) SessionReplaced ¶
SessionReplaced moves tokens issued on a pending session to the session that replaced it, so a later logout of the new one reaches them.
func (*Plugin) SetClientDisabled ¶
SetClientDisabled pauses or resumes a client from Go. A disabled client fails authentication and its authorize requests; its tokens stop at introspection and userinfo.
func (*Plugin) Sweep ¶
Sweep deletes codes and tokens that can no longer be used: expired codes, and token rows whose access and refresh tokens have both expired.
func (*Plugin) Token ¶
func (p *Plugin) Token(ctx context.Context, in TokenRequest) (TokenResponse, error)
Token runs a token request from Go.
func (*Plugin) TypedMigrations ¶
TypedMigrations creates the four tables for engine. Every hash is UNIQUE, so a lookup by hash finds at most one row; a consent is unique per user and client.
func (*Plugin) UnlinkClient ¶
UnlinkClient removes a client-resource link.
func (*Plugin) UpdateClient ¶
func (p *Plugin) UpdateClient(ctx context.Context, clientID string, spec ClientSpec) (Client, error)
UpdateClient replaces any client's metadata and restricted fields from Go, better-auth's admin update-client. The authentication method cannot change, as for an owner.
func (*Plugin) UpdateConsent ¶
UpdateConsent replaces one of the signed-in user's consents' scopes.
func (*Plugin) UpdateOwnedClient ¶
UpdateOwnedClient replaces an owned client's metadata.
func (*Plugin) UpdateResource ¶
UpdateResource replaces a resource's policy; the identifier is the key and never changes.
func (*Plugin) UserSessionsEnded ¶
UserSessionsEnded is SessionEnded for every session of a user but one.
func (*Plugin) UserTables ¶
UserTables names every user column, so DeleteUser removes the user's clients, codes, tokens and consents.
func (*Plugin) Validate ¶
Validate checks the issuer, the pages, the scopes, the grants, the lifetimes, and that every feature that signs has auth.WithSecret.
func (*Plugin) ValidateCIMDDocument ¶
func (p *Plugin) ValidateCIMDDocument(clientID string, document []byte) (ClientMetadata, error)
ValidateCIMDDocument checks a client metadata document fetched from clientID with this provider's CIMD rules, better-auth's validateCimdMetadata: the client registration it would make, or an ErrCIMDFetch error naming the rule. Without ClientIDMetadataDocuments it is ErrCIMDFetch.
func (*Plugin) VerifyAccessToken ¶
VerifyAccessToken checks an access token for a resource server: it exists, is neither expired nor revoked, and its client is enabled. Every failure is ErrInvalidToken.
func (*Plugin) VerifyOAuthQuery ¶
VerifyOAuthQuery checks an oauth_query a page posted back: exactly one signature, valid under a current or previous secret (compared in constant time), and not expired. Without a secret no query is signed and every query passes. better-auth's verifyOAuthQueryParams.
func (*Plugin) VerifyPresentedToken ¶
func (p *Plugin) VerifyPresentedToken(ctx context.Context, pt PresentedToken) (AccessToken, error)
VerifyPresentedToken is VerifyAccessToken with the DPoP rules: a bound token needs the DPoP scheme and a proof of its key for this request (with ath), and the DPoP scheme needs a bound token. Failures are ErrInvalidToken or ErrInvalidDPoPProof.
func (*Plugin) WriteInsufficientScope ¶
func (p *Plugin) WriteInsufficientScope(w http.ResponseWriter, o RequireOptions, missing ...string)
WriteInsufficientScope answers a request a RequireToken route found to need more scopes once it looked at the arguments, better-auth's createInsufficientScopeError thrown from a handler: 403 insufficient_scope naming the missing scopes, with the route's challenge.
type PostLoginGate ¶
type PostLoginGate struct {
// Page is the post-login page, a same-site path or a trusted URL.
Page string
// ShouldRedirect decides whether the user must pass Page; required.
ShouldRedirect func(ctx context.Context, info GateInfo) bool
// ConsentReferenceID, when set, is the reference (an organization id)
// a consent is given for: a consent for another reference asks again,
// and the codes and tokens carry it (AccessToken.ReferenceID,
// AccessTokenClaimsInfo.ReferenceID).
ConsentReferenceID func(ctx context.Context, info GateInfo) string
}
PostLoginGate is better-auth's postLogin: a page after sign-in (choosing an organization or team) whose choice the consent and the tokens are tied to.
type PreloginRequest ¶
type PreloginRequest struct {
ClientID string `json:"client_id"`
OAuthQuery string `json:"oauth_query"`
}
PreloginRequest asks for a client's public fields before sign-in.
type PresentedToken ¶
type PresentedToken struct {
// Scheme is SchemeDPoP or "Bearer".
Scheme string
Token string
// Proof is the DPoP header; Method and URL are the request's, which the
// proof must name.
Proof string
Method string
URL string
// Certificate is the connection's TLS client certificate, which a
// token bound to one (cnf x5t#S256, RFC 8705) must come with.
Certificate *x509.Certificate
}
PresentedToken is an access token as a request presented it.
type PublicClient ¶
type PublicClient struct {
ClientID string `json:"client_id"`
Name string `json:"client_name"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TosURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
Contacts []string `json:"contacts,omitempty"`
}
PublicClient is what a consent page may show about a client.
type RedirectError ¶
RedirectError is an authorization error that is answered by redirecting to the client's redirect URI with error and state (RFC 6749 section 4.1.2.1). Err is the reason; errors.Is sees through it.
func (*RedirectError) Error ¶
func (e *RedirectError) Error() string
Error is the reason's message; the redirect itself is RedirectTo.
func (*RedirectError) Unwrap ¶
func (e *RedirectError) Unwrap() error
Unwrap returns the reason, so errors.Is matches the error being redirected.
type RedirectValidator ¶
RedirectValidator decides a redirect URI at authorize, better-auth's validateRedirectUri: it gets the requested URI, the client's registered ones and the built-in exact-match result, and its answer replaces that result, both for issuing the code and for whether early errors may be sent to the URI. It relaxes RFC 9700's exact matching, so accept only hosts you control.
type RefreshTokenFormat ¶
type RefreshTokenFormat struct {
Encode func(ctx context.Context, token, sessionID string) (string, error)
Decode func(ctx context.Context, encoded string) (string, error)
}
RefreshTokenFormat encodes refresh tokens on the wire, better-auth's formatRefreshToken (a JWE carrying the session id, say). Encode receives the random token and the session id, Decode answers the random token back; PrefixRefreshToken goes before the encoding, and the stored hash is always the random token's, so a format can be added or changed without touching stored rows, and a token Decode refuses is simply unknown.
type RegisteredMetadata ¶
type RegisteredMetadata struct {
Name string `json:"client_name"`
RedirectURIs []string `json:"redirect_uris"`
GrantTypes []string `json:"grant_types"`
ResponseTypes []string `json:"response_types"`
AuthMethod string `json:"token_endpoint_auth_method"`
Scope string `json:"scope"`
ClientURI string `json:"client_uri,omitempty"`
LogoURI string `json:"logo_uri,omitempty"`
TosURI string `json:"tos_uri,omitempty"`
PolicyURI string `json:"policy_uri,omitempty"`
Contacts []string `json:"contacts,omitempty"`
PostLogoutRedirectURIs []string `json:"post_logout_redirect_uris,omitempty"`
JWKS json.RawMessage `json:"jwks,omitempty"`
JWKSURI string `json:"jwks_uri,omitempty"`
SubjectType string `json:"subject_type,omitempty"`
BackchannelLogoutURI string `json:"backchannel_logout_uri,omitempty"`
BackchannelLogoutSessionRequired bool `json:"backchannel_logout_session_required,omitempty"`
DPoPBoundAccessTokens bool `json:"dpop_bound_access_tokens,omitempty"`
Resources []string `json:"resources,omitempty"`
ApplicationType string `json:"application_type,omitempty"`
SoftwareID string `json:"software_id,omitempty"`
SoftwareVersion string `json:"software_version,omitempty"`
SoftwareStatement string `json:"software_statement,omitempty"`
CredentialsScopes []string `json:"client_credentials_scopes,omitempty"`
InitialAccessToken string `json:"-"`
Extra map[string]any `json:"-"`
// contains filtered or unexported fields
}
RegisteredMetadata is a client's registration as the provider answers it, RFC 7591 section 3.2.1: ClientMetadata's fields after the provider applied its defaults, with those defaulted fields always present on the wire (see ClientMetadata for what each field means). Client embeds it.
type Registration ¶
type Registration struct {
Client
// Secret is a plain string on purpose: this is the one place it is
// meant to be seen.
Secret string `json:"client_secret,omitempty"`
ClientIDIssuedAt int64 `json:"client_id_issued_at"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at"`
}
Registration is the answer to a registration: the client and, for a confidential client, its secret, shown this once (RFC 7591 section 3.2.1).
type RequestURIResolverFunc ¶
type RequestURIResolverFunc func(ctx context.Context, requestURI, clientID string) (url.Values, error)
RequestURIResolverFunc returns the stored parameters of a pushed authorization request (RFC 9126), better-auth's requestUriResolver; nil values mean the request_uri is unknown or expired.
type RequireOptions ¶
type RequireOptions struct {
// Resource is the protected resource this route belongs to: only tokens
// issued for it (RFC 8707 resource, the JWT's aud) are accepted, and its
// metadata URL is named in every challenge (RFC 9728 section 5.1).
// Optional; without it any of this provider's access tokens is accepted.
Resource string
// Scopes must all be on the token; a missing one is 403
// insufficient_scope naming every missing scope.
Scopes []string
// ScopeSatisfied replaces the exact scope match, better-auth's
// isScopeSatisfied: given the token's scopes and one required scope, it
// says whether the requirement is met (a hierarchy, a wildcard).
ScopeSatisfied func(granted []string, required string) bool
// JSONRPC answers a refusal in the JSON-RPC 2.0 error envelope MCP
// clients expect, {"jsonrpc":"2.0","error":{"code":-32000,...},"id":null},
// with the same status and WWW-Authenticate challenge.
JSONRPC bool
// ChallengeScopes are the scopes a 401 challenge names, better-auth's
// challengeScopes; nil names Scopes. A 403 always names the missing
// ones.
ChallengeScopes []string
}
RequireOptions configures RequireToken.
type Resource ¶
type Resource struct {
// Identifier is the RFC 8707 resource: an absolute URI without a
// fragment (or what IdentifierValidator accepts). It never changes.
Identifier string `json:"identifier"`
Name string `json:"name,omitempty"`
AccessTTL *int64 `json:"access_token_ttl,omitempty"`
RefreshTTL *int64 `json:"refresh_token_ttl,omitempty"`
// AllowedScopes narrows the scopes a token for this resource gets (an
// intersection); nil allows any.
AllowedScopes []string `json:"allowed_scopes,omitempty"`
// CustomClaims are added to its JWT access tokens and introspection;
// reserved claims are ignored.
CustomClaims map[string]any `json:"custom_claims,omitempty"`
// DPoPRequired makes every token for it DPoP-bound.
DPoPRequired bool `json:"dpop_bound_access_tokens_required,omitempty"`
// Disabled refuses new tokens for it (invalid_target); issued ones keep
// working until they expire.
Disabled bool `json:"disabled,omitempty"`
// SigningAlgorithm pins the algorithm of its JWT access tokens (one the
// jwt plugin has a key for: its signing key's or an AlgorithmKeys one),
// better-auth's signingAlgorithm; "" is the signing key's.
SigningAlgorithm string `json:"signing_algorithm,omitempty"`
// SigningKeyID pins the key of its JWT access tokens by key id (the jwt
// plugin's signing key or an AlgorithmKeys key, of SigningAlgorithm when
// both are set), better-auth's signingKeyId; a request for several
// resources whose pins differ is invalid_request.
SigningKeyID string `json:"signing_key_id,omitempty"`
// PolicyVersion counts the policy's writes, better-auth's policyVersion:
// 1 at creation, one more at every update, so a cache keyed on it knows
// a policy changed. Set by the provider; a value passed in is ignored.
PolicyVersion int64 `json:"policy_version"`
Metadata json.RawMessage `json:"metadata,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Resource is a protected resource's token policy, better-auth's OAuthResourceInput. Nil fields fall back to the provider's defaults.
type ResourceGuard ¶
type ResourceGuard struct {
// Issuer and Audience are what a JWT access token must name: the
// provider and this resource. Both required.
Issuer string
Audience string
// Keys resolves key ids, usually jwt.NewRemoteKeys(<issuer>/oauth2/jwks,
// nil).Lookup.
Keys func(kid string) (crypto.PublicKey, bool)
// IntrospectURL, ClientID and ClientSecret verify opaque tokens by
// introspection (client_secret_basic); empty refuses opaque tokens.
IntrospectURL, ClientID, ClientSecret string
// Force introspects every token, JWTs too, better-auth's
// remoteVerify.force: a revoked JWT is refused at once rather than at
// its expiry. It needs IntrospectURL.
Force bool
// AllowMissingAudience accepts an introspected token that names no
// audience, better-auth's remoteVerify.allowMissingAudience: a token
// issued without a resource. Off, such a token is refused, since nothing
// says it was meant for this resource.
AllowMissingAudience bool
// ChallengeScopes are the scopes a 401 challenge names; nil names
// Scopes.
ChallengeScopes []string
// HTTPClient makes the introspection request; nil is a client with a
// 5-second timeout.
HTTPClient *http.Client
// Scopes must all be granted; ScopeSatisfied replaces the exact match,
// better-auth's isScopeSatisfied.
Scopes []string
ScopeSatisfied func(granted []string, required string) bool
// ResourceMetadataURL is named in challenges; JSONRPC answers in the
// MCP JSON-RPC error envelope.
ResourceMetadataURL string
JSONRPC bool
// Now is the clock; nil is time.Now.
Now func() time.Time
}
ResourceGuard protects a resource server that runs apart from the provider (an MCP server in another process), better-auth's createMcpProtectedRequestHandler: it needs no database and no Plugin. A JWT access token (typ at+jwt) is verified against the provider's keys, issuer and this resource's audience; an opaque one, when IntrospectURL is set, by RFC 7662 introspection (better-auth's remoteVerify). The required scopes are then checked, and refusals carry the RFC 6750 / RFC 9728 challenge.
func (ResourceGuard) Middleware ¶
func (g ResourceGuard) Middleware(next http.Handler) http.Handler
Middleware guards next.
type ResourceMetadata ¶
type ResourceMetadata struct {
Resource string `json:"resource"`
AuthorizationServers []string `json:"authorization_servers"`
BearerMethodsSupported []string `json:"bearer_methods_supported"`
ScopesSupported []string `json:"scopes_supported,omitempty"`
// DPoPSigningAlgValuesSupported are the DPoP proof algorithms the
// provider's resources accept (RFC 9728 section 2); a separate
// resource server sets its own.
DPoPSigningAlgValuesSupported []string `json:"dpop_signing_alg_values_supported,omitempty"`
}
ResourceMetadata is the RFC 9728 document.
func ProtectedResourceMetadata ¶
func ProtectedResourceMetadata(resource string, authorizationServers, scopes []string) (ResourceMetadata, error)
ProtectedResourceMetadata builds an RFC 9728 document for a resource served apart from the provider, better-auth's getProtectedResourceMetadata: the user scopes (openid, profile, email, offline_access) are not a resource's and are refused (ErrBadScopes).
type ResourcePatch ¶
type ResourcePatch struct {
Name *string
AccessTTL **int64
RefreshTTL **int64
AllowedScopes *[]string
CustomClaims *map[string]any
DPoPRequired *bool
Disabled *bool
SigningAlgorithm *string
SigningKeyID *string
Metadata *json.RawMessage
}
ResourcePatch is a partial update of a resource, better-auth's PATCH /admin/oauth2/resources/:identifier: a nil field keeps the stored value. The identifier is the key and cannot change. A pointer to a nil slice or map (AllowedScopes, CustomClaims) clears it; Metadata set to JSON null clears the metadata.
type ResourceSeed ¶
type ResourceSeed struct {
Identifier string
ResourcePatch
}
ResourceSeed is one resource SeedResources writes: its identifier and the policy fields the configuration names (a nil field is not named).
type RevokeRequest ¶
type RevokeRequest struct {
Token string `json:"token"`
TokenTypeHint string `json:"token_type_hint"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
ClientAuthMethod string `json:"client_auth_method"`
// ClientAssertion is a private_key_jwt assertion; Handler checks
// client_assertion_type.
ClientAssertion string `json:"client_assertion"`
// ClientAssertionType and ClientCertificates are for an extension
// ClientAuthMethod (an assertion of its type, mutual TLS).
ClientAssertionType string `json:"client_assertion_type,omitempty"`
ClientCertificates []*x509.Certificate `json:"-"`
}
RevokeRequest is RFC 7009 section 2.1, with the caller's client credentials.
type SecretEncryption ¶
type SecretEncryption struct {
Encrypt func(plaintext string) (string, error)
Decrypt func(ciphertext string) (string, error)
}
SecretEncryption seals and opens a value the provider must be able to read back: client secrets under EncryptClientSecrets, cached token responses under RefreshReplay. Encrypt must be authenticated encryption (an AEAD such as AES-GCM, under a key from the app's secret store) so a changed ciphertext fails Decrypt instead of opening to something else.
type SecretHasher ¶
type SecretHasher func(secret string, kind SecretKind) []byte
SecretHasher computes the stored form of a client secret, code, access token or refresh token, better-auth's storeClientSecret and storeTokens { hash }: an HMAC with a key from the app's secret store (a pepper), say, so a copy of the database alone cannot even confirm a guessed token. It must be deterministic, because tokens are looked up by their hash, and return exactly 32 bytes, the width of the hash columns on every engine (SHA-256's size). Changing it invalidates every stored secret.
type SecretKind ¶
type SecretKind string
SecretKind names what a stored secret is, for a SecretHasher that hashes them differently.
const ( SecretClient SecretKind = "client_secret" SecretCode SecretKind = "authorization_code" SecretAccess SecretKind = "access_token" SecretRefresh SecretKind = "refresh_token" )
The secrets the provider stores hashed.
type SeedMode ¶
type SeedMode string
SeedMode is what SeedResources does with a resource that already has a row, better-auth's resourceSeedMode.
const ( // SeedInsertOnly leaves an existing row alone, so an admin's edits // survive a restart. The default. SeedInsertOnly SeedMode = "insertOnly" // SeedMerge sets the fields the seed names and keeps the others. SeedMerge SeedMode = "merge" // SeedOverwrite replaces the whole policy with the seed's, unnamed // fields cleared: the configuration is the single source of truth. SeedOverwrite SeedMode = "overwrite" )
The seed modes. A missing row is inserted in every mode.
type SelectAccountGate ¶
type SelectAccountGate struct {
// Page is the picker, a same-site path or a URL on a trusted origin.
Page string
// ShouldRedirect, when set, sends the user to Page without
// prompt=select_account too.
ShouldRedirect func(ctx context.Context, info GateInfo) bool
}
SelectAccountGate is better-auth's selectAccount: the account picker a signed-in user is sent to for prompt=select_account, or whenever ShouldRedirect says so.
type Signer ¶
type Signer interface {
Sign(ctx context.Context, in jwt.SignRequest) (jwt.TokenResult, error)
PublicKeys() jwt.JWKS
}
Signer signs the provider's ID tokens and JWT access tokens and lists the public keys the provider publishes at <base>/jwks, so a relying party can verify what Sign produced. *jwt.Plugin is the default and satisfies it; a KMS, an HSM or a key service an app already runs can stand in, without registering the jwt plugin or serving its token route.
The contract a custom Signer must keep, because relying parties check every part of it: the token's iss is SignRequest's issuer, which New verifies at start-up by signing a probe and comparing it with Issuer; the claims are sub, aud, iat, exp and the request's Claims, with typ the request's Type ("" is "JWT"); the header's kid names a key PublicKeys lists, and the first listed key's alg is the one ID tokens use, because at_hash is computed with its hash. Sign is called from trusted code only.
type SignupRedirectFunc ¶
SignupRedirectFunc is better-auth's signup.shouldRedirect: true sends a signed-in user to more registration steps, at page or, when page is "", SignupPage.
type TokenFieldsInfo ¶
type TokenFieldsInfo struct {
GrantType string
ClientID string
Scopes []string
// UserID is "" for a client's own token.
UserID string
}
TokenFieldsInfo is what TokenResponseFields receives.
type TokenGenerator ¶
type TokenGenerator func() string
TokenGenerator returns the random part of a new token (after its prefix), better-auth's generateOpaqueAccessToken and generateRefreshToken: at least 22 characters from a cryptographic source, or the issuance fails with ErrShortGenerated.
type TokenRequest ¶
type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
RedirectURI string `json:"redirect_uri"`
CodeVerifier string `json:"code_verifier"`
RefreshToken string `json:"refresh_token"`
// DeviceCode is the RFC 8628 device_code, for the device grant served
// through WithGrant.
DeviceCode string `json:"device_code"`
// DPoPProof is the request's DPoP header; Handler fills it.
DPoPProof string `json:"dpop_proof"`
Scope string `json:"scope"`
// Resource is the RFC 8707 resource; at a code exchange or refresh it
// must be the one the grant was issued for.
Resource string `json:"resource"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
// ClientAuthMethod is how the client authenticated: AuthMethodBasic,
// AuthMethodPost or AuthMethodNone. It must be the client's registered
// method.
ClientAuthMethod string `json:"client_auth_method"`
// ClientAssertion is a private_key_jwt assertion; Handler checks
// client_assertion_type.
ClientAssertion string `json:"client_assertion"`
// ClientAssertionType and ClientCertificates are for an extension
// ClientAuthMethod (an assertion of its type, mutual TLS).
ClientAssertionType string `json:"client_assertion_type,omitempty"`
ClientCertificates []*x509.Certificate `json:"-"`
}
TokenRequest is a token request, RFC 6749 sections 4.1.3, 4.4.2 and 6. Handler fills the client credentials from the Authorization header or the form, and Method with how they came.
type TokenResponse ¶
type TokenResponse struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int64 `json:"expires_in"`
// ExpiresAt is when the access token expires, in Unix seconds,
// better-auth's expires_at.
ExpiresAt int64 `json:"expires_at,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
IDToken string `json:"id_token,omitempty"`
Scope string `json:"scope"`
// Extra are the TokenResponseFields additions; a standard member's
// name is ignored.
Extra map[string]any `json:"-"`
}
TokenResponse is the token endpoint's answer, RFC 6749 section 5.1. The tokens are plain strings: this is the one place they are meant to be seen.
func (TokenResponse) MarshalJSON ¶
func (r TokenResponse) MarshalJSON() ([]byte, error)
MarshalJSON writes the standard members and, beside them, Extra.
type TrustedClient ¶
type TrustedClient struct {
ClientSpec
// ClientID is the client_id; required.
ClientID string
// Secret is the client secret of a confidential client (held only as
// its hash); a public one (token_endpoint_auth_method none) has none.
Secret string
}
TrustedClient is a client defined in Go and held in memory, never stored, better-auth's cachedTrustedClients: a first-party app that ships with the server. The client operations cannot change or delete it (ErrForbidden from the admin ones, unknown to the owner ones), and it shadows a stored client with the same client_id.
type UpdateClientRequest ¶
type UpdateClientRequest struct {
ClientID string `json:"client_id"`
Metadata ClientMetadata `json:"metadata"`
// ClientCredentialsScopes, when set, replaces the client_credentials
// ceiling; it needs ActionConfigureCCScopes, like CredentialsScopes at
// creation.
ClientCredentialsScopes *[]string `json:"client_credentials_scopes,omitempty"`
}
UpdateClientRequest replaces an owned client's metadata. The authentication method cannot change, as in better-auth: a public client that became confidential would have no secret.
type UpdateConsentRequest ¶
UpdateConsentRequest replaces a consent's scopes. Every scope must be one the client may request; narrowing does not revoke tokens already issued.
type UserInfoRequest ¶
type UserInfoRequest struct {
AccessToken string `json:"access_token"`
// Scheme, DPoPProof, Method and URL describe how the token was
// presented, for a DPoP-bound one; "" Scheme is Bearer. Handler fills
// them.
Scheme string `json:"-"`
DPoPProof string `json:"-"`
Method string `json:"-"`
URL string `json:"-"`
// Certificate is the connection's TLS client certificate, for a token
// bound to one.
Certificate *x509.Certificate `json:"-"`
}
UserInfoRequest carries the access token, from the Authorization header or the form (Handler refuses both at once).
Source Files
¶
- api.go
- assertion.go
- authorize.go
- backchannel.go
- cimd.go
- cimdpolicy.go
- claimsreq.go
- client.go
- consent.go
- dpop.go
- extend.go
- extension.go
- gates.go
- guard.go
- handler.go
- hint.go
- jwksuri.go
- limits.go
- logout.go
- methods.go
- oauthprovider.go
- options.go
- pairwise.go
- parity.go
- privileges.go
- registry.go
- replay.go
- resource.go
- resourcecache.go
- resources.go
- schema.go
- signedquery.go
- storage.go
- token.go
- trusted.go