oauth

package
v0.3.2 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 33 Imported by: 0

Documentation

Overview

Package oauth is an OAuth 2.0 authorization-code client with PKCE and OpenID Connect ID-token verification for social login. It returns a typed Profile; linking or creating the application's own user is the application's decision. Provider requests use an SSRF-restricted httpclient.Client. No dependency beyond the standard library cryptography is used.

Index

Constants

View Source
const DefaultPendingLifetime = 10 * time.Minute

DefaultPendingLifetime bounds the time between redirect and callback.

View Source
const Denied failureCode = "oauth authorization was denied at the provider"

Denied is the cause of an auth.Unauthenticated callback that carried a provider error such as access_denied (the user declined).

Variables

This section is empty.

Functions

This section is empty.

Types

type Authorization

type Authorization struct {
	URL     string
	Pending Pending
}

Authorization is a started request: redirect the browser to URL and keep Pending until the callback.

func (Authorization) Format

func (Authorization) Format(s fmt.State, _ rune)

func (Authorization) LogValue

func (Authorization) LogValue() slog.Value

type Callback

type Callback struct {
	Code  secret.String
	State string
	// Error is the provider's error code, for example access_denied.
	Error string
}

Callback is the provider's redirect query.

func ParseCallback

func ParseCallback(query url.Values) (Callback, error)

ParseCallback reads code, state and error from a callback query, rejecting repeated or oversized parameters.

type Client

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

Client runs one provider's authorization-code flow. It is immutable and safe for concurrent use; the JWKS cache is shared by its calls.

func NewClient

func NewClient(provider Provider, outbound *httpclient.Client, source clock.Clock) (*Client, error)

NewClient validates the provider and borrows an outbound client that must restrict destinations (see Provider.Destinations), so provider URLs cannot be used to reach internal services. The client must have no BaseURL.

func (*Client) Begin

func (c *Client) Begin(ctx context.Context) (Authorization, error)

Begin creates fresh state, nonce and PKCE verifier and the provider URL with response_type=code, the exact redirect URI and the S256 code challenge.

func (*Client) Complete

func (c *Client) Complete(ctx context.Context, pending Pending, callback Callback) (Profile, error)

Complete validates a callback against its pending request (provider, expiry and state), exchanges the code with the PKCE verifier and reads the profile. OpenID Connect ID tokens are verified (signature, issuer, audience, expiry, issue time and nonce). Every rejected callback returns auth.Unauthenticated; provider and network failures remain operational errors. The pending request is single use: discard it after this call, whatever the result.

func (*Client) Provider

func (c *Client) Provider() Provider

Provider returns the immutable provider declaration.

func (*Client) Validate

func (c *Client) Validate() error

type Credentials

type Credentials struct {
	ClientID     string
	ClientSecret secret.String
	RedirectURL  string
}

Credentials are the application's registration at a provider. RedirectURL is the exact registered callback URI; it is sent verbatim in the authorization and token requests and is never taken from an incoming request.

type Flow

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

Flow keeps the pending request in an encrypted, short-lived cookie between the redirect and the callback, so no server-side state is needed. The cookie is authenticated and scoped by the application key ring (http.CookieEncrypter), HttpOnly, Secure and SameSite=Lax (sent on the provider's top-level redirect back), and expires with the pending request.

func NewFlow

func NewFlow(client *Client, encrypter http.CookieEncrypter, name http.CookieName) (*Flow, error)

NewFlow binds a client to a pending-request cookie. Use one cookie name per provider, for example "__Host-foundry_oauth_google".

func (*Flow) Finish

func (f *Flow) Finish(w stdhttp.ResponseWriter, r *stdhttp.Request) (Profile, error)

Finish handles the provider's redirect to the registered callback: it reads and always clears the pending cookie (it is single use), validates the query and completes the flow. A missing, expired or tampered cookie, a callback on another path, a state mismatch or a provider denial return auth.Unauthenticated.

func (*Flow) Start

func (f *Flow) Start(ctx context.Context, w stdhttp.ResponseWriter) (string, error)

Start begins an authorization request, stores its pending half in the cookie and returns the provider URL. Respond with a redirect to it (for example http.Redirect with 302) or return it to a single-page client.

type Pending

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

Pending is the server-held half of one authorization request: the state that binds the callback to this browser, the OpenID nonce bound into the ID token and the PKCE code verifier. Keep it server-side or in an encrypted, short-lived cookie (see Flow); never in a URL or plain cookie. Routine formatting, logging and JSON redact it.

func (Pending) ExpiresAt

func (p Pending) ExpiresAt() time.Time

func (Pending) Format

func (Pending) Format(s fmt.State, _ rune)

func (Pending) LogValue

func (Pending) LogValue() slog.Value

func (Pending) MarshalJSON

func (Pending) MarshalJSON() ([]byte, error)

func (Pending) Provider

func (p Pending) Provider() ProviderName

Provider and ExpiresAt describe the pending request without its secrets.

type Profile

type Profile struct {
	Provider      ProviderName
	Subject       string
	Email         string
	EmailVerified bool
	Name          string
	// Username is the provider handle when it has one (GitHub login).
	Username  string
	AvatarURL string
	// Token is the provider access for further API calls; it is redacted.
	Token Token
}

Profile is the verified identity a provider asserted. Subject is the stable account identifier at the provider (the OpenID sub claim or GitHub's numeric account ID) and is the key to link on; email addresses can change and are only trustworthy when EmailVerified. Mapping a profile to the application's own user, including creating or linking one, is the application's decision.

type ProfileSource

type ProfileSource uint8

ProfileSource selects how a provider's user profile is read.

const (
	// OpenIDConnect verifies the ID token returned by the token endpoint
	// (signature against JWKSURL, issuer, audience, expiry and nonce) and reads
	// its standard claims.
	OpenIDConnect ProfileSource = iota + 1
	// GitHubUser reads GitHub's REST user API with the access token, plus the
	// verified primary address from its emails API when that scope is granted.
	GitHubUser
)

type Provider

type Provider struct {
	Name             ProviderName
	Credentials      Credentials
	Source           ProfileSource
	AuthorizationURL string
	TokenURL         string
	Scopes           []string
	// Issuers and JWKSURL are required for OpenIDConnect.
	Issuers []string
	JWKSURL string
	// UserURL and EmailsURL are used by GitHubUser.
	UserURL   string
	EmailsURL string
}

Provider is an immutable provider declaration. Use a preset such as Google or GitHub, or fill the fields for another OpenID Connect provider.

func GitHub

func GitHub(credentials Credentials) Provider

GitHub is the OAuth 2.0 preset using GitHub's user API (scopes read:user, user:email). GitHub issues no ID token; the numeric account ID is the Subject.

func Google

func Google(credentials Credentials) Provider

Google is the OpenID Connect preset (scopes openid, email, profile).

func (Provider) Destinations

func (p Provider) Destinations() (httpclient.DestinationPolicy, error)

Destinations returns an SSRF-restricted outbound policy admitting only this provider's HTTPS hosts on port 443, for the httpclient.Client passed to NewClient.

func (Provider) Validate

func (p Provider) Validate() error

Validate checks the declaration without I/O. Provider endpoints and the redirect URI must use HTTPS; plain HTTP is accepted only for loopback hosts (local development and tests).

type ProviderName

type ProviderName string

ProviderName identifies a configured provider, for example "google".

type Token

type Token struct {
	Access    secret.String
	Refresh   value.Optional[secret.String]
	ExpiresAt value.Optional[time.Time]
	Scopes    []string
}

Token is the provider's credential for calling its APIs on the user's behalf. Formatting, logging and JSON redact it; Access and Refresh are explicit disclosure boundaries.

func (Token) Format

func (Token) Format(s fmt.State, _ rune)

func (Token) HasScope

func (t Token) HasScope(scope string) bool

HasScope reports whether the provider reported granting scope.

func (Token) LogValue

func (Token) LogValue() slog.Value

func (Token) MarshalJSON

func (Token) MarshalJSON() ([]byte, error)

Jump to

Keyboard shortcuts

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