oidc

package
v1.0.2 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 23 Imported by: 0

Documentation

Overview

Package oidc verifies Google and Apple ID tokens for social sign-in and talks to the providers' OAuth token endpoints. It is deliberately not a general OIDC client: no discovery, no provider SDKs — just RS256 JWS verification against a cached remote JWKS plus the two token-endpoint calls moth needs (Apple code exchange/revoke, Google web code exchange).

moth's own tokens are ES256 and live in internal/jwt; this package only ever verifies third-party provider tokens, which are RS256.

Index

Constants

View Source
const (
	GoogleIssuer   = "https://accounts.google.com"
	GoogleJWKSURL  = "https://www.googleapis.com/oauth2/v3/certs"
	GoogleTokenURL = "https://oauth2.googleapis.com/token"
	AppleBaseURL   = "https://appleid.apple.com"
)

Default endpoint locations, overridable on the clients for tests.

Variables

View Source
var (
	ErrMalformed        = errors.New("malformed token")
	ErrInvalidSignature = errors.New("invalid signature")
	ErrExpired          = errors.New("token expired")
	ErrIssuerMismatch   = errors.New("issuer mismatch")
	ErrAudienceMismatch = errors.New("audience mismatch")
	ErrNonceMismatch    = errors.New("nonce mismatch")
	ErrUnknownKey       = errors.New("unknown key id")
)

Verification errors.

Functions

func ParseP8

func ParseP8(data []byte) (*ecdsa.PrivateKey, error)

ParseP8 parses an Apple developer private key (.p8 file: PEM-wrapped PKCS#8 EC P-256).

Types

type AppleClient

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

AppleClient talks to Apple's OAuth token endpoints.

func NewAppleClient

func NewAppleClient(baseURL, clientID string, secrets *AppleSecrets, httpc Doer) *AppleClient

NewAppleClient returns a client for Apple's token endpoints under baseURL (defaults to AppleBaseURL when empty; overridable for tests). httpc defaults to a timeout-bounded client when nil.

func (*AppleClient) ExchangeCode

func (c *AppleClient) ExchangeCode(ctx context.Context, code, redirectURI string) (TokenResponse, error)

ExchangeCode trades an authorization code for tokens at {base}/auth/token. redirectURI must match the one used to obtain the code; native-app codes have none, so it may be empty.

func (*AppleClient) Revoke

func (c *AppleClient) Revoke(ctx context.Context, refreshToken string) error

Revoke invalidates a refresh token at {base}/auth/token/revoke, as App Store review requires on account deletion.

type AppleSecretConfig

type AppleSecretConfig struct {
	TeamID   string // iss
	KeyID    string // JWS kid header (Apple Key ID, from the developer portal)
	ClientID string // sub: the Services ID (web) or bundle ID (native)
	Key      *ecdsa.PrivateKey
	// Audience defaults to AppleBaseURL; overridable for tests.
	Audience string
	// Lifetime defaults to defaultAppleSecretLifetime.
	Lifetime time.Duration
}

AppleSecretConfig identifies the Apple developer key that signs client secrets and the client they authenticate.

type AppleSecrets

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

AppleSecrets generates Apple client secrets (ES256 JWTs signed with the developer .p8) and caches each one until ~80% of its lifetime has elapsed.

func NewAppleSecrets

func NewAppleSecrets(cfg AppleSecretConfig, now func() time.Time) *AppleSecrets

NewAppleSecrets returns a cached client-secret generator. now defaults to time.Now when nil.

func (*AppleSecrets) ClientSecret

func (s *AppleSecrets) ClientSecret() (string, error)

ClientSecret returns a currently valid client secret, minting a fresh one when the cached secret is past 80% of its lifetime.

type Doer

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

Doer is the subset of *http.Client the package needs; injectable so tests and the server package can point it at doubles.

type GoogleClient

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

GoogleClient exchanges web-redirect-flow authorization codes at Google's token endpoint.

func NewGoogleClient

func NewGoogleClient(tokenURL, clientID, clientSecret string, httpc Doer) *GoogleClient

NewGoogleClient returns a client for Google's token endpoint at tokenURL (defaults to GoogleTokenURL when empty; overridable for tests). httpc defaults to a timeout-bounded client when nil.

func (*GoogleClient) ExchangeCode

func (c *GoogleClient) ExchangeCode(ctx context.Context, code, redirectURI string) (TokenResponse, error)

ExchangeCode trades an authorization code for tokens (including the id_token the caller then verifies). redirectURI must match the one used in the authorization request.

type Identity

type Identity struct {
	Issuer  string
	Subject string
	Email   string
	// EmailVerified is true only when the provider asserts it in the token
	// (Google sends a bool, Apple a bool or the string "true").
	EmailVerified bool
	Name          string
	GivenName     string
	FamilyName    string
	// ExpiresAt is the token's exp claim; callers use it to bound how long
	// a consumed token's hash must be remembered for replay rejection.
	ExpiresAt time.Time
}

Identity is the normalized result of a verified ID token. All fields come from the verified token, never from client-asserted request fields.

type KeySet

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

KeySet fetches and caches a provider's RSA JWKS, resolving kids to public keys. Refreshes are single-flight: concurrent lookups share one fetch.

func NewKeySet

func NewKeySet(url string, httpc Doer, now func() time.Time) *KeySet

NewKeySet returns a cache over the JWKS at url. httpc and now default to a timeout-bounded client and time.Now when nil.

func (*KeySet) Key

func (k *KeySet) Key(ctx context.Context, kid string) (*rsa.PublicKey, error)

Key resolves kid to an RSA public key, refetching the JWKS when the cache has expired or the kid is unknown (rate-limited so garbage kids cannot hammer the provider).

type NonceMode

type NonceMode int

NonceMode selects how a token's nonce claim relates to the raw per-attempt nonce the client sent in the RPC.

const (
	// NonceRaw: the token's nonce claim is the raw nonce itself. Google
	// echoes the nonce request parameter verbatim.
	NonceRaw NonceMode = iota
	// NonceSHA256Hex: the token's nonce claim is hex(SHA-256(raw)). Apple
	// echoes whatever the client sent it, and the SDKs send Apple the
	// SHA-256 hex digest (per Apple's scheme) while passing the raw value
	// in the RPC, so the server hashes before comparing.
	NonceSHA256Hex
)

type Provider

type Provider struct {
	Name      string
	Issuers   []string // exact-match set for the iss claim
	JWKSURL   string
	NonceMode NonceMode
}

Provider describes one identity provider: which issuer values its tokens carry, where its JWKS lives, and its nonce convention. Fields are plain so tests and the server package can point them at test doubles.

func Apple

func Apple() Provider

Apple returns the Apple preset.

func Google

func Google() Provider

Google returns the Google preset. Both issuer spellings are accepted: Google historically emitted "accounts.google.com" without a scheme.

type TokenError

type TokenError struct {
	StatusCode  int
	Code        string
	Description string
}

TokenError is a non-2xx token-endpoint response, carrying the OAuth error code (e.g. "invalid_grant") when the provider sent one.

func (*TokenError) Error

func (e *TokenError) Error() string

type TokenResponse

type TokenResponse struct {
	AccessToken  string `json:"access_token"`
	TokenType    string `json:"token_type"`
	ExpiresIn    int64  `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	IDToken      string `json:"id_token"`
}

TokenResponse is the successful body of an OAuth token-endpoint call.

type Verifier

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

Verifier checks provider ID tokens for one provider against its JWKS.

func NewVerifier

func NewVerifier(p Provider, httpc Doer, now func() time.Time) *Verifier

NewVerifier returns a verifier for p. httpc and now default to a timeout-bounded client and time.Now when nil.

func (*Verifier) Verify

func (v *Verifier) Verify(ctx context.Context, idToken string, audiences []string, rawNonce string) (Identity, error)

Verify checks an ID token's signature (RS256 only — any other alg, including "none" and HS256, is rejected before key material is touched), issuer, audience membership, exp/iat and nonce, and returns the normalized identity. rawNonce is the per-attempt nonce the client sent in the RPC; the comparison against the token's nonce claim follows the provider's NonceMode. An empty rawNonce skips the nonce check — only the web-redirect flow, where the signed state parameter binds the attempt, may pass it empty.

Jump to

Keyboard shortcuts

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