oauth2

package
v1.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package oauth2 implements the client half of the OAuth 2.0 authorization-code flow with PKCE (RFC 7636) using only the standard library.

Scope

This package obtains tokens. It does not validate them — that is github.com/reliant-labs/forge/pkg/auth's job, and the two never talk to each other. A forge server validates bearer tokens statelessly; the flow implemented here runs in a browser, a CLI, or a test, against an identity provider's endpoints. The forge server is not a participant.

Nothing here is specific to any identity provider. Endpoints are supplied by the caller, either literally or via Discover.

The flow

A verifier is minted once per login attempt and must survive the redirect to the provider and back:

v, err := oauth2.NewVerifier()
state, err := oauth2.NewState()
// persist v.Secret() and state server-side (session, signed cookie, keyring)

u, err := oauth2.AuthRequest{
	Endpoint:    "https://idp.example.com/oidc/auth",
	ClientID:    clientID,
	RedirectURI: "https://app.example.com/callback",
	Scopes:      []string{"openid", "profile", "offline_access"},
	State:       state,
	Challenge:   v.Challenge(),
}.URL()
// redirect the user agent to u

On the way back, the state is checked before anything else is trusted, and the code is exchanged with the verifier that produced the challenge:

if err := oauth2.CompareState(state, r.URL.Query().Get("state")); err != nil {
	return err // errors.Is(err, oauth2.ErrStateMismatch) — treat as CSRF
}
tok, err := (&oauth2.Exchanger{}).Exchange(ctx, oauth2.TokenRequest{
	Endpoint:    "https://idp.example.com/oidc/token",
	ClientID:    clientID,
	RedirectURI: "https://app.example.com/callback",
	Code:        r.URL.Query().Get("code"),
	Verifier:    v,
})

Secrets

Verifier and Token deliberately resist accidental disclosure: they redact themselves under fmt, log/slog, and encoding/json rather than printing their contents. See Verifier for the full list of what is blocked and how to get the value out on purpose.

No global state

Every function takes what it needs. There are no package-level mutable variables and no implicit default client to reconfigure.

Index

Constants

View Source
const (
	ErrCodeInvalidRequest       = "invalid_request"
	ErrCodeInvalidClient        = "invalid_client"
	ErrCodeInvalidGrant         = "invalid_grant"
	ErrCodeUnauthorizedClient   = "unauthorized_client"
	ErrCodeUnsupportedGrantType = "unsupported_grant_type"
	ErrCodeInvalidScope         = "invalid_scope"
)

Standard OAuth 2.0 error codes (RFC 6749 §5.2), for comparison against Error.Code. Providers may return codes outside this set.

View Source
const (
	MinVerifierLength = 43
	MaxVerifierLength = 128
)

Verifier length bounds from RFC 7636 §4.1: the code verifier is a string of 43 to 128 characters drawn from the unreserved set.

View Source
const AuthorizationServerMetadataPath = "/.well-known/oauth-authorization-server"

AuthorizationServerMetadataPath is RFC 8414's well-known path.

View Source
const DefaultExchangeTimeout = 30 * time.Second

DefaultExchangeTimeout bounds a token exchange made by an Exchanger with no Client of its own.

Variables

View Source
var (
	// ErrStateMismatch reports that the state returned by the authorization
	// server did not match the state sent with the authorization request.
	// This is a CSRF signal: abandon the flow, do not exchange the code.
	ErrStateMismatch = errors.New("oauth2: state mismatch (possible CSRF)")

	// ErrEmptyState reports that one or both sides of a state comparison
	// were empty. Empty state is never a match — an authorization server
	// that echoes no state, or a session that stored none, must not be
	// allowed to satisfy the CSRF check by comparing "" against "".
	ErrEmptyState = errors.New("oauth2: state is empty")

	// ErrVerifierRedacted is returned by [Verifier]'s marshalling methods.
	// A code verifier must never end up in a log line, an error string, or
	// a JSON document written by accident; use [Verifier.Secret] to obtain
	// the value deliberately.
	ErrVerifierRedacted = errors.New("oauth2: refusing to marshal a code verifier; call Verifier.Secret() if you really mean to")
)
View Source
var ErrMetadataIssuerMismatch = errors.New("oauth2: discovery document issuer does not match the requested issuer")

ErrMetadataIssuerMismatch reports that a discovery document declared an issuer other than the one it was fetched from. OpenID Connect Discovery §4.3 requires these to match; a mismatch means the document cannot be trusted to describe the issuer the caller asked about.

View Source
var ErrNoAccessToken = errors.New("oauth2: token response contained no access_token")

ErrNoAccessToken reports a token endpoint that answered 200 with no access_token. Such a response is a failure, not an empty success: returning a blank Token would hand the caller a credential-shaped value that authenticates nothing, and the failure would surface later as an unexplained 401.

View Source
var ErrNoAuthorizationServer = errors.New("oauth2: the server publishes no authorization server metadata")

ErrNoAuthorizationServer reports a server that publishes no authorization server metadata: it issues no tokens through a browser login.

View Source
var ErrNoRefreshToken = errors.New("oauth2: refresh token is empty")

ErrNoRefreshToken reports a refresh token that is absent where one is required — either constructed from the empty string, or a RefreshRequest carrying the zero RefreshToken.

Functions

func CompareState

func CompareState(sent, received string) error

CompareState checks the state echoed by the authorization server against the state that was sent, in constant time, and returns nil only if they match.

A mismatch wraps ErrStateMismatch; an empty value on either side wraps ErrEmptyState. The empty case is rejected before the comparison on purpose: an authorization server that echoes no state at all, paired with a session that recorded none, would otherwise compare "" against "" and pass the CSRF check without either side having proved anything.

func NewState

func NewState() (string, error)

NewState generates an opaque CSRF token for the state parameter, 32 bytes of cryptographically secure randomness base64url-encoded without padding.

State travels in a URL, so it is returned as a plain string rather than a secret-bearing type. Bind it to the user agent (session or cookie) and check it with CompareState before trusting anything in the callback.

Types

type AuthRequest

type AuthRequest struct {
	// Endpoint is the provider's authorization endpoint, e.g.
	// https://idp.example.com/oidc/auth. Take it from
	// [ProviderMetadata.AuthorizationEndpoint] if you used [Discover].
	Endpoint string

	// ClientID identifies the client to the provider. Required.
	ClientID string

	// RedirectURI must match one registered with the provider. Required:
	// omitting it lets a provider fall back to a registered default, which
	// is how a callback ends up somewhere the caller did not intend.
	RedirectURI string

	// Scopes are joined with ScopeSeparator. For OIDC include "openid";
	// add "offline_access" to be issued a refresh token.
	Scopes []string

	// ScopeSeparator joins Scopes into the one scope parameter. Empty means
	// a space, which is RFC 6749 §3.3. Set "," only for a provider that
	// reads scope comma-delimited (Slack's OAuth v2 does); no other value
	// is accepted.
	ScopeSeparator string

	// State is the CSRF token from [NewState]. Required — a flow with no
	// state has no defence against a forged callback.
	State string

	// Challenge is the PKCE challenge from [Verifier.Challenge]. Required.
	Challenge Challenge

	// ResponseType defaults to "code". Set it only for a provider that
	// needs something else; this package can only complete "code".
	ResponseType string

	// Extra carries provider-specific parameters (prompt, audience,
	// resource, login_hint, nonce, ...). Keys that collide with a
	// parameter this package sets are rejected rather than silently
	// dropped or duplicated.
	Extra url.Values
}

AuthRequest describes the authorization request a user agent is redirected to. It is a plain struct rather than a constructor with options so that a caller can see, in one place, every parameter that ends up in the URL.

func (AuthRequest) URL

func (r AuthRequest) URL() (string, error)

URL renders the authorization request as an absolute URL, with every parameter percent-encoded by net/url.

It fails rather than emitting a half-built URL: a request missing a challenge, a state, or a redirect URI is a flow with a security property silently switched off, and the provider is not obliged to complain.

type Challenge

type Challenge struct {
	Value  string
	Method Method
}

Challenge is the public half of a PKCE pair: the value and method sent as code_challenge and code_challenge_method on the authorization request.

Unlike Verifier this carries no secret under S256 and is safe to log. Under MethodPlain it is the verifier verbatim, which is exactly why plain is discouraged.

type Error

type Error struct {
	// Code is the OAuth error identifier, e.g. "invalid_grant".
	Code string
	// Description is the provider's human-readable explanation, if any.
	Description string
	// URI optionally points at provider documentation.
	URI string
	// StatusCode is the HTTP status the error arrived with.
	StatusCode int
}

Error is an OAuth 2.0 error response (RFC 6749 §5.2) rendered as a Go error.

The provider's machine-readable code is preserved in Code so callers can branch on it — invalid_grant means the code is spent or expired and the user should start over, while invalid_client is a deployment problem.

func (*Error) Error

func (e *Error) Error() string

type Exchanger

type Exchanger struct {
	// Client performs the request. When nil, a client with
	// DefaultExchangeTimeout is used — never [http.DefaultClient], which
	// has no timeout at all and would let a wedged provider hang a login
	// forever.
	Client HTTPDoer

	// UserAgent is sent as the User-Agent header when non-empty.
	UserAgent string
}

Exchanger redeems authorization codes at a token endpoint.

The zero Exchanger is usable and exchanges over a client with a 30 second timeout. Set Client to control timeouts, transport, or proxying, or to point tests at an net/http/httptest server.

func (*Exchanger) Exchange

func (e *Exchanger) Exchange(ctx context.Context, req TokenRequest) (*Token, error)

Exchange redeems req.Code for a token, proving possession of the PKCE verifier.

A provider error response becomes an *Error (use errors.As); a 200 with no access token becomes ErrNoAccessToken. Neither is reported as success.

func (*Exchanger) Refresh

func (e *Exchanger) Refresh(ctx context.Context, req RefreshRequest) (*Refreshed, error)

Refresh redeems req.RefreshToken for a new access token (RFC 6749 §6).

Rotation

A provider MAY return a new refresh token, and RFC 6749 §6 says the client MUST then discard the old one. Many providers rotate on EVERY refresh and treat a re-presented old token as theft: they revoke the entire grant, which signs the user out completely rather than merely failing one call.

Verified against Zitadel v4.19.4 (the IdP forge scaffolds for local dev): it rotates on every refresh and rejects a replayed token IMMEDIATELY, with no grace window — though it revokes only that token, leaving the newest one usable. Providers built on node oidc-provider are harsher and kill the whole grant. Write the client for the harsh case; it is correct under both.

This function therefore reports what to present NEXT rather than leaving the caller to infer it. Refreshed.RefreshToken is ALWAYS the token to use for the following refresh: the rotated one when the provider sent one, otherwise the one just presented. A caller that stores it unconditionally is correct under both provider behaviours, and there is no branch to get wrong.

A provider error response becomes an *Error (use errors.As); a 200 with no access token becomes ErrNoAccessToken. Neither is reported as success.

type HTTPDoer

type HTTPDoer interface {
	Do(req *http.Request) (*http.Response, error)
}

HTTPDoer is the subset of *http.Client this package needs. Taking an interface keeps the token exchange testable without a network and lets callers supply a client with their own timeout, transport, or proxy.

*http.Client satisfies it.

type LoopbackLogin

type LoopbackLogin struct {
	// AuthorizeEndpoint and TokenEndpoint are the server's two URLs.
	AuthorizeEndpoint string
	TokenEndpoint     string
	// ClientID is the public client (no secret: a CLI cannot keep one).
	ClientID string
	// Scopes requested. The server may grant fewer, and Token.Scope reports
	// what it granted.
	Scopes []string
	// Extra carries additional authorization parameters (e.g. a device
	// label). Reserved names are refused, per AuthRequest.
	Extra url.Values
	// OpenURL launches the browser. When it fails the flow continues: the URL
	// has already been written to Out, so a headless user can open it by hand.
	OpenURL func(string) error
	// Out receives the "open this URL" notice. May be nil.
	Out io.Writer
	// Timeout bounds the wait for the browser to come back. Defaults to 5m.
	Timeout time.Duration
	// HTTPClient performs the token exchange. Nil uses a bounded default.
	HTTPClient HTTPDoer
	// SuccessHTML is the page shown in the browser after the callback. It is
	// shown whether or not the exchange then succeeds, because the exchange
	// happens after the page is served. A default is used when empty.
	SuccessHTML string
}

LoopbackLogin runs the whole authorization-code + PKCE flow for a native app, as RFC 8252 describes. It binds a loopback listener on an ephemeral port, sends the user's browser to the authorization endpoint with that listener as the redirect, receives the code, and redeems it at the token endpoint with the verifier.

This is the ONE implementation of a CLI's browser login: `forge login` and `reliant auth login` both run it, so the two cannot drift on the security properties that matter (state, S256, exact redirect).

WHY PORT 0. The OS picks a free port, so two logins on one machine (two developers, or two agents) never collide, which a fixed port cannot promise. RFC 8252 §7.3 requires the authorization server to accept any port on a loopback redirect.

func (LoopbackLogin) Run

func (l LoopbackLogin) Run(ctx context.Context) (*Token, error)

Run performs the flow and returns the token.

type Method

type Method string

Method is a PKCE code challenge method (RFC 7636 §4.2).

const (
	// MethodS256 derives the challenge as BASE64URL-ENCODE(SHA256(ASCII(verifier)))
	// with padding omitted. This is the method to use.
	MethodS256 Method = "S256"

	// MethodPlain sends the verifier itself as the challenge.
	//
	// Discouraged. RFC 7636 §7.2 permits plain only for clients that
	// cannot compute a SHA-256 hash, and warns that it offers no
	// protection against an attacker who can observe the authorization
	// request. Any Go program can compute SHA-256, so no Go client has
	// that excuse. It exists here only to interoperate with a provider
	// that rejects S256, and is never selected unless a caller names it
	// explicitly via [Verifier.ChallengeWithMethod].
	MethodPlain Method = "plain"
)

type ProviderMetadata

type ProviderMetadata struct {
	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,omitempty"`
	EndSessionEndpoint                string   `json:"end_session_endpoint,omitempty"`
	ScopesSupported                   []string `json:"scopes_supported,omitempty"`
	ResponseTypesSupported            []string `json:"response_types_supported,omitempty"`
	CodeChallengeMethodsSupported     []string `json:"code_challenge_methods_supported,omitempty"`
	TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported,omitempty"`

	Raw map[string]any `json:"-"`
}

ProviderMetadata is the subset of an OpenID Provider configuration (OpenID Connect Discovery 1.0 §3, RFC 8414) that a PKCE client needs.

Raw carries the whole document for callers who need a field this struct does not name.

func Discover

func Discover(ctx context.Context, client HTTPDoer, issuer string) (*ProviderMetadata, error)

Discover fetches and validates a provider's configuration from issuer + "/.well-known/openid-configuration".

client may be nil, in which case a client with DefaultExchangeTimeout is used. The returned metadata is checked for the issuer match required by the spec and for the presence of the endpoints this package needs, so a caller can use the endpoints without nil-checking strings.

func DiscoverAuthorizationServer

func DiscoverAuthorizationServer(ctx context.Context, client HTTPDoer, server string) (*ProviderMetadata, error)

DiscoverAuthorizationServer fetches RFC 8414 authorization-server metadata from server + AuthorizationServerMetadataPath.

Unlike Discover, the document may be published by a RESOURCE server on behalf of the authorization server that protects it (an API naming the control plane that issues its tokens), so the declared issuer need not equal server. What IS required: the issuer and both endpoints are absolute, the endpoints live on the issuer's origin, and every one is https or loopback. A document naming a token endpoint on some other origin is refused, since that is where the verifier and the code are about to be sent.

func (ProviderMetadata) SupportsS256

func (m ProviderMetadata) SupportsS256() bool

SupportsS256 reports whether the provider advertises the S256 code challenge method.

A provider that advertises no methods at all returns false rather than a hopeful true: the answer is "it did not say", and a caller checking this should treat silence as unknown instead of being told yes by a default. Note that many providers support S256 without advertising it, so a false here is a reason to check documentation, not to fall back to plain.

type RefreshRequest

type RefreshRequest struct {
	// Endpoint is the provider's token endpoint. Required.
	Endpoint string

	// ClientID must be the client the refresh token was issued to.
	// Required: RFC 6749 §6 requires it for a public client, and a
	// provider that cannot identify the client cannot check the binding.
	ClientID string

	// ClientSecret authenticates a confidential client. Leave it empty for
	// a public client (browser SPA, native app, CLI). When set it is sent
	// in the request body as client_secret.
	ClientSecret string

	// RefreshToken is the credential being redeemed. Required.
	RefreshToken RefreshToken

	// Scopes optionally narrows the granted scope. RFC 6749 §6 permits a
	// scope no broader than the original grant, and providers reject a
	// widening. Leave it nil to keep the original scope, which is what
	// almost every caller wants — omitting the parameter is not the same
	// as sending an empty one, and this package omits it when nil.
	Scopes []string

	// ScopeSeparator joins Scopes, exactly as [AuthRequest.ScopeSeparator]:
	// empty means a space (RFC 6749 §3.3), "," for a comma-delimited
	// provider, and nothing else.
	ScopeSeparator string

	// Extra carries provider-specific body parameters. Keys that collide
	// with a parameter this package sets are rejected.
	Extra url.Values
}

RefreshRequest describes a refresh-token redemption (RFC 6749 §6).

There is no Verifier field. PKCE binds the AUTHORIZATION CODE to the client instance that started the flow (RFC 7636 §4.5); the refresh grant presents no code, so there is no challenge to satisfy. The refresh token is itself the credential, which is why RefreshRequest.RefreshToken is a RefreshToken rather than a bare string — see that type for what it refuses to disclose.

type RefreshToken

type RefreshToken struct {
	// contains filtered or unexported fields
}

RefreshToken is a refresh token: a long-lived credential that mints access tokens without the user present.

It is a distinct type from a plain string, and holds its value in an unexported field, for the same reason Verifier does — it is strictly more dangerous than the access token it produces, because it outlives every token it mints. It redacts itself under fmt and log/slog.

Unlike Verifier, it does NOT fail to marshal: a caller legitimately needs to carry a refresh token in a session so that a refresh can happen later, and Verifier's "fail rather than emit a placeholder" rule exists precisely because a verifier is single-use within one login. See RefreshToken.Secret for taking the value out deliberately.

func NewRefreshToken

func NewRefreshToken(value string) (RefreshToken, error)

NewRefreshToken wraps a refresh token value received from a provider.

It rejects the empty string. A zero RefreshToken is not a token, and letting one through would produce a refresh request whose credential is absent — a call the provider answers with invalid_grant, far from the missing assignment that caused it.

func (RefreshToken) GoString

func (t RefreshToken) GoString() string

GoString implements fmt.GoStringer so that %#v does not reflect through the unexported field.

func (RefreshToken) IsZero

func (t RefreshToken) IsZero() bool

IsZero reports whether t holds no value, which is how a caller distinguishes "this session has no refresh token" from "this session has one" without reaching for RefreshToken.Secret and comparing against "".

func (RefreshToken) LogValue

func (t RefreshToken) LogValue() slog.Value

LogValue implements slog.LogValuer and returns a redacted placeholder.

func (RefreshToken) Secret

func (t RefreshToken) Secret() string

Secret returns the refresh token in the clear. Every call site is a place the credential can escape: send it to the token endpoint, or hand it to the session store that will present it later. Do not log it.

func (RefreshToken) String

func (t RefreshToken) String() string

String implements fmt.Stringer and returns a redacted placeholder.

type Refreshed

type Refreshed struct {
	// Token is the provider's response. Token.RefreshToken holds the raw
	// rotated value when there was one, and is empty when the provider sent
	// none — prefer the RefreshToken field below, which is never empty.
	Token *Token

	// RefreshToken is what to present on the NEXT refresh. It is always
	// populated: the rotated token when the provider issued one, otherwise
	// the token that was just presented. Store it unconditionally.
	RefreshToken RefreshToken

	// Rotated reports whether the provider issued a NEW refresh token and
	// therefore invalidated (or will invalidate) the previous one. It is
	// informational — RefreshToken is already correct either way — and is
	// exposed for callers that log or meter rotation.
	Rotated bool
}

Refreshed is the result of a successful Exchanger.Refresh.

It exists rather than returning a bare *Token so that the rotation rule is carried in the type: a caller cannot read the new access token without also being handed the refresh token to store, which is the step whose omission breaks the SECOND refresh against a rotating provider.

type Token

type Token struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type,omitempty"`
	ExpiresIn    int64  `json:"expires_in,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
	IDToken      string `json:"id_token,omitempty"`
	Scope        string `json:"scope,omitempty"`

	// Raw holds every field of the response, including provider-specific
	// ones this struct does not name.
	Raw map[string]any `json:"-"`
}

Token is a successful token endpoint response (RFC 6749 §5.1).

Like Verifier, Token redacts itself under fmt and log/slog so that logging a whole response does not spill the credentials. Unlike Verifier it *is* JSON-marshalable, because a caller storing a session legitimately needs to serialize it; the fields are exported and carry their wire names.

func (Token) Expiry

func (t Token) Expiry(now time.Time) time.Time

Expiry converts ExpiresIn to an absolute instant relative to now, or returns the zero Time when the provider sent no expiry.

func (Token) GoString

func (t Token) GoString() string

GoString implements fmt.GoStringer so %#v does not print the secrets.

func (Token) LogValue

func (t Token) LogValue() slog.Value

LogValue implements slog.LogValuer.

func (Token) String

func (t Token) String() string

String implements fmt.Stringer and reports the shape of the token without its contents, which is what is actually useful in a log line.

type TokenRequest

type TokenRequest struct {
	// Endpoint is the provider's token endpoint. Required.
	Endpoint string

	// ClientID must be the same client the code was issued to. Required.
	ClientID string

	// ClientSecret authenticates a confidential client. Leave it empty for
	// a public client (browser SPA, native app, CLI) — that is what PKCE
	// is for. When set it is sent in the request body as client_secret.
	ClientSecret string

	// RedirectURI must byte-for-byte match the one in the authorization
	// request. Required: providers verify it, and a mismatch is a common
	// cause of an otherwise unexplained invalid_grant.
	RedirectURI string

	// Code is the authorization code from the callback. Required.
	Code string

	// Verifier is the code verifier whose challenge was sent with the
	// authorization request. Required.
	Verifier Verifier

	// Extra carries provider-specific body parameters. Keys that collide
	// with a parameter this package sets are rejected.
	Extra url.Values
}

TokenRequest describes an authorization-code redemption (RFC 6749 §4.1.3 plus the code_verifier of RFC 7636 §4.5).

type Verifier

type Verifier struct {
	// contains filtered or unexported fields
}

Verifier is a PKCE code verifier: the high-entropy secret whose hash is published in the authorization request and whose plaintext is revealed only to the token endpoint.

The value is held in an unexported field, and the type actively refuses to disclose it:

  • fmt verbs (%v, %s, %d, %q) print [REDACTED] via Verifier.String.
  • %#v prints Verifier{[REDACTED]} via Verifier.GoString. Without this, fmt would reflect straight through the unexported field and print the secret.
  • log/slog logs verifier=[REDACTED] via Verifier.LogValue.
  • encoding/json and encoding/text based encoders fail with ErrVerifierRedacted rather than emitting the secret. Failing is deliberate: a silent [REDACTED] in a session cookie would be a login that mysteriously stops working, while an error names the problem at the call site.

A Verifier must outlive the redirect to the authorization server. Persist Verifier.Secret somewhere the user agent cannot read — a server-side session, an encrypted cookie, an OS keyring — and rebuild it with ParseVerifier.

The zero Verifier is invalid; Verifier.Challenge on it panics rather than producing the hash of the empty string, which would be a challenge no verifier can ever satisfy.

func NewVerifier

func NewVerifier() (Verifier, error)

NewVerifier generates a code verifier from 32 bytes of cryptographically secure randomness, base64url-encoded without padding into the 43 characters RFC 7636 §4.1 recommends.

func NewVerifierLength

func NewVerifierLength(n int) (Verifier, error)

NewVerifierLength generates a code verifier of exactly n characters drawn uniformly from the unreserved alphabet, for the rare provider that demands a specific length. n must be within [MinVerifierLength, MaxVerifierLength].

Prefer NewVerifier.

func ParseVerifier

func ParseVerifier(s string) (Verifier, error)

ParseVerifier rebuilds a Verifier from a previously generated string, rejecting anything RFC 7636 §4.1 would not accept. Use it to restore a verifier persisted across the authorization redirect.

func (Verifier) Challenge

func (v Verifier) Challenge() Challenge

Challenge derives the S256 code challenge for v: BASE64URL(SHA256(ASCII(verifier))) with padding omitted (RFC 7636 §4.2).

It panics on the zero Verifier. Hashing the empty string would yield a well-formed challenge that no verifier can redeem, turning a programming mistake into an authorization failure at the token endpoint, far from its cause.

func (Verifier) ChallengeWithMethod

func (v Verifier) ChallengeWithMethod(m Method) (Challenge, error)

ChallengeWithMethod derives the code challenge for v using an explicitly named method.

Pass MethodS256 unless a provider genuinely rejects it; see MethodPlain for why plain is a downgrade. There is no way to reach plain without naming it here.

func (Verifier) GoString

func (v Verifier) GoString() string

GoString implements fmt.GoStringer so that %#v does not reflect through the unexported field.

func (Verifier) LogValue

func (v Verifier) LogValue() slog.Value

LogValue implements slog.LogValuer and returns a redacted placeholder.

func (Verifier) MarshalJSON

func (v Verifier) MarshalJSON() ([]byte, error)

MarshalJSON always fails with ErrVerifierRedacted. See Verifier.

func (Verifier) MarshalText

func (v Verifier) MarshalText() ([]byte, error)

MarshalText always fails with ErrVerifierRedacted. See Verifier.

func (Verifier) Secret

func (v Verifier) Secret() string

Secret returns the code verifier in the clear. Every call site is a place the secret can escape: send it to the token endpoint, or persist it for the duration of one login. Do not log it.

func (Verifier) String

func (v Verifier) String() string

String implements fmt.Stringer and returns a redacted placeholder.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL