Documentation
¶
Overview ¶
Package oauth implements provider-side OAuth code-exchange flows for authenticating end users into the identity service.
Identity does OAuth FOR LOGIN ONLY: it accepts an authorization code from the frontend, swaps it with the provider for an ID token (or userinfo response), verifies the user's identity, and then mints OUR own JWT. Provider access/refresh tokens are NEVER stored — that's a separate "connections" service concern.
The Exchanger interface decouples the auth flow from any specific provider; a Registry holds the per-provider implementations the service layer dispatches to via the "provider" string in the RPC.
Currently supported providers:
- "google": OIDC. ID token verified via JWKS (RS256).
- "microsoft": OIDC via Azure AD common endpoint. ID token verified via JWKS; per-tenant issuer accepted.
- "github": NOT OIDC. We exchange the code, then call /user and /user/emails to discover the canonical (verified, primary) email.
Index ¶
- Variables
- func CodeChallengeS256(verifier string) string
- func GenerateCodeVerifier() (string, error)
- func GenerateState() (string, error)
- func IssueHostedStateToken(ctx context.Context, signer identityjwt.Signer, ...) (string, error)
- func IssueStateToken(ctx context.Context, signer identityjwt.Signer, ...) (string, error)
- type AppleConfig
- type Authorizer
- type ExchangeParams
- type Exchanger
- type GitHubConfig
- type GoogleConfig
- type HostedStateClaims
- type Identity
- type MicrosoftConfig
- type Registry
- type StateClaims
Constants ¶
This section is empty.
Variables ¶
var ( // ErrCodeExchangeFailed indicates the provider rejected our // token-endpoint POST or returned an unparseable response. ErrCodeExchangeFailed = errors.New("oauth: code exchange failed") // ErrIdentityVerification indicates the ID token signature, issuer, // audience, or expiry could not be validated. ErrIdentityVerification = errors.New("oauth: identity verification failed") // ErrEmailNotVerified indicates the provider returned an unverified // email; we refuse to log such users in. ErrEmailNotVerified = errors.New("oauth: provider reported email is not verified") // ErrStateValidation indicates the OAuth callback state could not be // validated against the server-minted state token. ErrStateValidation = errors.New("oauth: state validation failed") )
Common error sentinels. Callers (e.g. the service layer) can errors.Is against these to map to RPC error codes.
Functions ¶
func CodeChallengeS256 ¶
CodeChallengeS256 returns the PKCE S256 code challenge for verifier.
func GenerateCodeVerifier ¶
GenerateCodeVerifier returns a PKCE code verifier.
func GenerateState ¶
GenerateState returns a high-entropy OAuth state string.
func IssueHostedStateToken ¶ added in v0.9.0
func IssueHostedStateToken( ctx context.Context, signer identityjwt.Signer, provider, redirectURI, returnTo, state, codeVerifier, csrfToken string, expiry time.Duration, now time.Time, ) (string, error)
IssueHostedStateToken signs a hosted-flow state token. redirectURI is the identity-owned callback URL registered with the provider; returnTo is the validated app URL the callback redirects to.
Types ¶
type AppleConfig ¶ added in v1.5.0
type AppleConfig struct {
ClientID string
TeamID string
KeyID string
PrivateKey string
TokenURL string
JWKSURL string
Issuer string
HTTPClient *http.Client
JWKSCacheTTL time.Duration
Now func() time.Time
}
AppleConfig configures an Apple Exchanger. ClientID, TeamID, KeyID, and PrivateKey are required.
type Authorizer ¶
type Authorizer interface {
AuthorizationURL(ctx context.Context, redirectURI, state, codeChallenge string) (string, error)
}
Authorizer builds the provider authorization URL for the first half of the OAuth authorization-code flow.
type ExchangeParams ¶ added in v1.5.0
type ExchangeParams struct {
Code string
RedirectURI string
CodeVerifier string // Optional PKCE code_verifier
AppleUserPayload string // Optional form-post payload for Apple first-time login
}
ExchangeParams contains the arguments for the OAuth token exchange.
type Exchanger ¶
type Exchanger interface {
Exchange(ctx context.Context, params ExchangeParams) (*Identity, error)
}
Exchanger swaps an OAuth authorization code for a verified user identity. Implementations are responsible for:
- POSTing to the provider's token endpoint with client_id / client_secret / code / redirect_uri.
- Verifying the resulting ID token (OIDC providers) OR fetching userinfo (non-OIDC providers like GitHub).
- Returning a canonical Identity with EmailVerified guaranteed true.
Errors returned to callers are intentionally generic — they do not leak provider response bodies. Callers that need provider-specific debugging should inspect logs.
func NewApple ¶ added in v1.5.0
func NewApple(cfg AppleConfig) Exchanger
NewApple returns an Exchanger that implements Sign-In with Apple.
func NewGitHub ¶
func NewGitHub(cfg GitHubConfig) Exchanger
NewGitHub returns an Exchanger for GitHub. Note that GitHub does not implement OIDC; this exchanger calls the user/userEmails APIs directly using the access token returned by the token endpoint.
func NewGoogle ¶
func NewGoogle(cfg GoogleConfig) Exchanger
NewGoogle returns an Exchanger for Google OIDC.
func NewMicrosoft ¶
func NewMicrosoft(cfg MicrosoftConfig) Exchanger
NewMicrosoft returns an Exchanger for Microsoft Azure AD.
type GitHubConfig ¶
type GitHubConfig struct {
ClientID string
ClientSecret string
HTTPClient *http.Client
AuthorizationURL string
TokenURL string
UserURL string
UserMailURL string
}
GitHubConfig configures a GitHub Exchanger.
type GoogleConfig ¶
type GoogleConfig struct {
ClientID string
ClientSecret string
// HTTPClient overrides the http.Client used for token + JWKS
// requests. Optional; defaults to a 10s-timeout client.
HTTPClient *http.Client
// TokenURL overrides the token endpoint. Optional; defaults to
// googleTokenURL.
TokenURL string
// AuthorizationURL overrides the provider authorization endpoint.
// Optional; defaults to googleAuthorizationURL or the discovery
// document's authorization_endpoint when DiscoveryURL is set.
AuthorizationURL string
// JWKSURL overrides the JWKS endpoint. Optional; defaults to
// googleJWKSURL.
JWKSURL string
// DiscoveryURL overrides the OIDC discovery endpoint. When set,
// Exchange resolves token / JWKS / userinfo endpoints from it.
DiscoveryURL string
// UserinfoURL overrides the OIDC userinfo endpoint. Optional.
UserinfoURL string
// Issuer overrides the expected `iss` claim. Optional; defaults
// to googleIssuer.
Issuer string
// JWKSCacheTTL overrides the JWKS cache TTL. Optional; defaults
// to 1h.
JWKSCacheTTL time.Duration
// Now overrides the clock used for ID token expiry validation.
// Optional; defaults to time.Now.
Now func() time.Time
}
GoogleConfig configures a Google Exchanger. ClientID and ClientSecret are required; the rest default to the live Google endpoints and a 1h JWKS cache.
type HostedStateClaims ¶ added in v0.9.0
type HostedStateClaims struct {
Provider string
RedirectURI string
ReturnTo string
State string
CodeVerifier string
CSRFToken string
IssuedAt int64
ExpiresAt int64
}
HostedStateClaims are the tamper-proof claims the hosted OAuth flow binds into the state token it carries through the provider redirect. It is a superset of the headless StateClaims with the app's return_to added: the provider sends only `state` + `code` back to the single callback URL, so everything else (the PKCE verifier, the provider name, and where to hand the user back) must round-trip inside this signed artifact.
This is a SEPARATE artifact from the headless state token (state.go). The headless flow's IssueStateToken / VerifyStateToken shape is intentionally left untouched — the SPA still round-trips the verifier itself there — so the hosted return_to binding does not become a breaking change for native/mobile callers (#126 stop condition).
func VerifyHostedStateToken ¶ added in v0.9.0
func VerifyHostedStateToken( token string, kp identityjwt.KeyProvider, now time.Time, ) (*HostedStateClaims, error)
VerifyHostedStateToken validates the signature and expiry of the hosted state token (which IS the OAuth `state` parameter the provider echoed back), then returns the recovered claims. The provider, return target, and PKCE verifier are recovered from the token — the callback has no other source for them. The token's signature is the CSRF binding: an attacker cannot forge a state the server will accept.
type Identity ¶
type Identity struct {
// ProviderUserID is the stable per-provider user identifier.
// For OIDC providers this is the "sub" claim. For GitHub this is
// the numeric user id (rendered as a string).
ProviderUserID string
// Email is the user's primary email address. Always lowercased.
Email string
// EmailVerified is true if the provider asserts the email is
// verified. Implementations MUST refuse to return an Identity if
// the provider says the email is not verified.
EmailVerified bool
// Name is the user's display name. May be empty.
Name string
// AvatarURL is a URL to the user's profile picture. May be empty.
AvatarURL string
// Provider is the provider key — "google", "microsoft", "github", "apple".
Provider string
}
Identity is the canonical, verified user identity returned by an Exchanger after a successful code exchange. Implementations MUST only return an Identity for verified users — i.e. the caller can rely on Email/ProviderUserID being authoritative.
type MicrosoftConfig ¶
type MicrosoftConfig struct {
ClientID string
ClientSecret string
HTTPClient *http.Client
// AuthorizationURL overrides the authorization endpoint. Optional.
AuthorizationURL string
// TokenURL overrides the token endpoint. Optional.
TokenURL string
// JWKSURL overrides the JWKS endpoint. Optional.
JWKSURL string
// TenantID controls the default tenant segment in the authorization
// endpoint when AuthorizationURL is not set. Optional; defaults to
// "common".
TenantID string
// IssuerFormat is a fmt.Sprintf format string into which the
// token's `tid` (tenant id) claim is interpolated to derive the
// expected issuer. Optional; defaults to the Microsoft format.
// In tests, set this to e.g. "%s" plus a fixed test issuer.
IssuerFormat string
JWKSCacheTTL time.Duration
Now func() time.Time
}
MicrosoftConfig configures a Microsoft Azure AD Exchanger.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps provider keys ("google", "microsoft", "github", "apple") to their Exchanger implementations. The service layer looks up the Exchanger for the provider named in the OAuthLoginRequest.
A nil *Registry is valid and reports every provider as missing. This lets the service treat "OAuth login disabled" as a registry without any registered providers (or no registry at all).
func NewRegistry ¶
func NewRegistry() *Registry
NewRegistry returns an empty registry. Callers populate it with Register.
func (*Registry) Get ¶
Get returns the Exchanger for the given provider key. The second return value is false if no Exchanger is registered.
func (*Registry) Providers ¶
Providers returns the sorted list of currently-registered provider keys. Useful for startup logging.
type StateClaims ¶
type StateClaims struct {
Provider string
RedirectURI string
State string
CodeVerifier string
IssuedAt int64
ExpiresAt int64
}
func VerifyStateToken ¶
func VerifyStateToken( token string, kp identityjwt.KeyProvider, expectedProvider, expectedRedirectURI, returnedState, explicitCodeVerifier string, now time.Time, ) (*StateClaims, error)