Documentation
¶
Overview ¶
Package auth provides authentication primitives for FlowCatalyst SDK applications: JWT validation (RS256 via JWKS, or HS256 via a shared secret), the OAuth2 authorization-code flow with PKCE, and a client_credentials TokenProvider that plugs into client.WithTokenProvider.
A token minted by FlowCatalyst validates identically from any of the SDKs (TypeScript, Laravel, Go).
Resource server (token validation) ¶
v := auth.NewTokenValidator(auth.TokenValidatorConfig{
IssuerURL: "https://auth.flowcatalyst.io",
Audience: "my-app",
})
ctx, err := v.ValidateBearer(r.Context(), r.Header.Get("Authorization"))
if err != nil { /* 401 */ }
if ctx.HasRole("admin") && ctx.HasClientAccess("clt_123") { /* allow */ }
OAuth2 authorization code flow (web app) ¶
oauth := auth.NewOAuthClient(auth.OAuthConfig{
IssuerURL: "https://auth.flowcatalyst.io",
ClientID: "my-app",
ClientSecret: "secret",
RedirectURI: "https://myapp.example.com/callback",
})
url, params := oauth.AuthorizeURL()
// ... redirect, store params in session, then on callback:
tokens, err := oauth.ExchangeCode(ctx, code, params.PKCE.CodeVerifier)
Service-to-service (client_credentials) ¶
tp := auth.NewClientCredentialsProvider(auth.ClientCredentialsConfig{
IssuerURL: "https://auth.flowcatalyst.io",
ClientID: "svc-app",
ClientSecret: "...",
})
c := client.New("https://api.flowcatalyst.io", client.WithTokenProvider(tp.Token))
Index ¶
- Constants
- Variables
- func FieldMiddleware(v *FieldTokenVerifier) func(http.Handler) http.Handler
- type AccessTokenClaims
- type Audience
- type AuthContext
- func (a *AuthContext) BearerToken() string
- func (a *AuthContext) ClientIDs() []string
- func (a *AuthContext) Email() string
- func (a *AuthContext) HasClientAccess(clientID string) bool
- func (a *AuthContext) HasRole(role string) bool
- func (a *AuthContext) IsAnchor() bool
- func (a *AuthContext) IsService() bool
- func (a *AuthContext) Name() string
- func (a *AuthContext) PrincipalID() string
- func (a *AuthContext) Roles() []string
- type AuthorizeParams
- type ClientCredentialsConfig
- type ClientCredentialsProvider
- type Error
- type ErrorKind
- type FieldError
- type FieldPrincipal
- type FieldTokenVerifier
- type FieldTokenVerifierConfig
- type HmacTokenValidator
- type IntrospectionResponse
- type JwksCache
- type OAuthClient
- func (c *OAuthClient) AuthorizeURL() (string, AuthorizeParams)
- func (c *OAuthClient) ExchangeCode(ctx context.Context, code, codeVerifier string) (*TokenResponse, error)
- func (c *OAuthClient) IntrospectToken(ctx context.Context, token string) (*IntrospectionResponse, error)
- func (c *OAuthClient) LogoutURL(postLogoutRedirectURI, idTokenHint, state string) string
- func (c *OAuthClient) RefreshToken(ctx context.Context, refreshToken string) (*TokenResponse, error)
- func (c *OAuthClient) RevokeToken(ctx context.Context, token string) error
- func (c *OAuthClient) UserInfo(ctx context.Context, accessToken string) (*UserInfoResponse, error)
- type OAuthConfig
- type PkceChallenge
- type TokenResponse
- type TokenValidator
- type TokenValidatorConfig
- type UserInfoResponse
Constants ¶
const ( FieldReasonMalformed = "malformed" FieldReasonWrongTyp = "wrong_typ" FieldReasonWrongTokenUse = "wrong_token_use" FieldReasonWrongAud = "wrong_aud" FieldReasonExpired = "expired" FieldReasonInvalidToken = "invalid_token" FieldReasonPCR = "pcr" FieldReasonMissingProof = "missing_proof" FieldReasonInvalidProof = "invalid_proof" FieldReasonJKTMismatch = "jkt_mismatch" FieldReasonHTMMismatch = "htm_mismatch" FieldReasonHTUMismatch = "htu_mismatch" FieldReasonATHMismatch = "ath_mismatch" FieldReasonStaleProof = "stale_proof" )
Reasons a field credential is refused. They are the stable strings the shared fixtures (api/field-fixtures) expect.
Variables ¶
var ( ErrTokenExpired = &Error{Kind: KindTokenExpired} ErrInvalidToken = &Error{Kind: KindInvalidToken} ErrDiscovery = &Error{Kind: KindDiscovery} ErrTokenExchange = &Error{Kind: KindTokenExchange} ErrConfig = &Error{Kind: KindConfig} ErrCrypto = &Error{Kind: KindCrypto} )
Sentinel errors. Use errors.Is to branch on them.
Functions ¶
func FieldMiddleware ¶ added in v0.0.12
func FieldMiddleware(v *FieldTokenVerifier) func(http.Handler) http.Handler
FieldMiddleware verifies every request with v and puts the FieldPrincipal in the request context (FieldPrincipalFrom). A refused request is a 401 {"error": <reason>} with WWW-Authenticate: DPoP; a key-fetch failure is 503. The request URL is v's BaseURL plus the request path when BaseURL is set, else scheme (X-Forwarded-Proto or TLS) and Host from the request.
Types ¶
type AccessTokenClaims ¶
type AccessTokenClaims struct {
// Sub is the principal id, e.g. "prn_0HZXEQ5Y8JY5Z".
Sub string `json:"sub"`
// Iss is the issuer URL.
Iss string `json:"iss"`
// Aud is the audience. Accepts string or []string on the wire.
Aud Audience `json:"aud"`
// Exp is the expiration time (Unix seconds).
Exp int64 `json:"exp"`
// Iat is the issued-at time (Unix seconds).
Iat int64 `json:"iat"`
// Nbf is the not-before time (Unix seconds).
Nbf int64 `json:"nbf"`
// Jti is the JWT id.
Jti string `json:"jti"`
// PrincipalType is "USER" or "SERVICE". The wire field name is
// "type".
PrincipalType string `json:"type"`
// Scope is "ANCHOR", "PARTNER", or "CLIENT".
Scope string `json:"scope"`
// Email is present for USER principals, absent for SERVICE.
Email string `json:"email,omitempty"`
// Name is the display name.
Name string `json:"name"`
// Clients are the client ids this principal can access. ["*"] for
// anchor users.
Clients []string `json:"clients"`
// Roles are the role codes assigned to this principal.
Roles []string `json:"roles,omitempty"`
// Applications are the application codes derived from roles, e.g.
// "operant" from "operant:admin". Always present on tokens issued
// by FC but absent on older tokens.
Applications []string `json:"applications,omitempty"`
// TokenUse is the token class: "api" for a credential an application
// backend may accept, absent on older tokens. [TokenValidator.Validate]
// refuses every other value ("identity", "field", ...).
TokenUse string `json:"token_use,omitempty"`
}
AccessTokenClaims is the JWT payload shape issued by FlowCatalyst's /oauth/token endpoint. JSON tags match the wire contract byte-for-byte so the same token deserialises identically across SDKs.
func (*AccessTokenClaims) HasClientAccess ¶
func (c *AccessTokenClaims) HasClientAccess(clientID string) bool
HasClientAccess reports whether the principal can access the given client. Anchor principals (clients == ["*"]) always return true.
func (*AccessTokenClaims) HasRole ¶
func (c *AccessTokenClaims) HasRole(role string) bool
HasRole reports whether the principal has the given role code.
func (*AccessTokenClaims) IsAnchor ¶
func (c *AccessTokenClaims) IsAnchor() bool
IsAnchor reports whether this is an anchor (platform-wide) principal.
func (*AccessTokenClaims) IsService ¶
func (c *AccessTokenClaims) IsService() bool
IsService reports whether this is a service account.
func (*AccessTokenClaims) PrincipalID ¶
func (c *AccessTokenClaims) PrincipalID() string
PrincipalID returns the Sub claim.
type Audience ¶
type Audience string
Audience is a JWT aud claim. RFC 7519 allows aud to be either a string or an array of strings. FlowCatalyst-minted tokens emit a single string, but tokens minted with other tooling (e.g. jwx itself) emit an array. We accept both forms on unmarshal and always marshal as a single string for wire parity.
func (*Audience) UnmarshalJSON ¶
UnmarshalJSON accepts either a string or a one-element string array. Multi-element arrays take the first entry.
type AuthContext ¶
type AuthContext struct {
Claims AccessTokenClaims
Token string
}
AuthContext is a validated token plus the raw JWT, ready for authorization checks and forwarding to downstream services.
func NewAuthContext ¶
func NewAuthContext(claims AccessTokenClaims, token string) *AuthContext
NewAuthContext constructs an AuthContext.
func (*AuthContext) BearerToken ¶
func (a *AuthContext) BearerToken() string
BearerToken returns the raw JWT for forwarding to downstream services.
func (*AuthContext) ClientIDs ¶
func (a *AuthContext) ClientIDs() []string
func (*AuthContext) Email ¶
func (a *AuthContext) Email() string
func (*AuthContext) HasClientAccess ¶
func (a *AuthContext) HasClientAccess(clientID string) bool
func (*AuthContext) HasRole ¶
func (a *AuthContext) HasRole(role string) bool
func (*AuthContext) IsAnchor ¶
func (a *AuthContext) IsAnchor() bool
func (*AuthContext) IsService ¶
func (a *AuthContext) IsService() bool
func (*AuthContext) Name ¶
func (a *AuthContext) Name() string
func (*AuthContext) PrincipalID ¶
func (a *AuthContext) PrincipalID() string
func (*AuthContext) Roles ¶
func (a *AuthContext) Roles() []string
type AuthorizeParams ¶
type AuthorizeParams struct {
PKCE PkceChallenge
State string
Nonce string
}
AuthorizeParams is the session-stored side of an authorize-URL call.
type ClientCredentialsConfig ¶
type ClientCredentialsConfig struct {
// IssuerURL is the FlowCatalyst OIDC server.
IssuerURL string
// ClientID is the service-account / confidential client id.
ClientID string
// ClientSecret is the matching secret.
ClientSecret string
// Scopes requested. May be empty.
Scopes []string
// Audience requested. May be empty.
Audience string
// SafetyWindow is how long before expiry to refresh proactively.
// Defaults to 60s.
SafetyWindow time.Duration
// HTTPClient overrides the default transport. Optional.
HTTPClient *http.Client
}
ClientCredentialsConfig configures a ClientCredentialsProvider.
type ClientCredentialsProvider ¶
type ClientCredentialsProvider struct {
// contains filtered or unexported fields
}
ClientCredentialsProvider obtains and refreshes service-account access tokens via the OAuth2 client_credentials grant. Plug it into client.WithTokenProvider:
cc := auth.NewClientCredentialsProvider(...) c := client.New(base, client.WithTokenProvider(cc.Token))
func NewClientCredentialsProvider ¶
func NewClientCredentialsProvider(cfg ClientCredentialsConfig) *ClientCredentialsProvider
NewClientCredentialsProvider builds a provider with sensible defaults.
func (*ClientCredentialsProvider) Invalidate ¶
func (p *ClientCredentialsProvider) Invalidate()
Invalidate clears the cached token. The next Token call will fetch.
type ErrorKind ¶
type ErrorKind int
ErrorKind enumerates auth error categories.
const ( // KindTokenExpired — JWT exp has passed. KindTokenExpired ErrorKind = iota // KindInvalidToken — bad signature, wrong issuer/audience, malformed. KindInvalidToken // KindDiscovery — OIDC discovery or JWKS fetch failed. KindDiscovery // KindTokenExchange — /oauth/token or related endpoint failed. KindTokenExchange // KindConfig — bad configuration (missing required field, bad key length). KindConfig // KindCrypto — cryptographic operation failed. KindCrypto )
type FieldError ¶ added in v0.0.12
FieldError is a refused field credential; Reason is one of the FieldReason* constants.
func (*FieldError) Error ¶ added in v0.0.12
func (e *FieldError) Error() string
func (*FieldError) Is ¶ added in v0.0.12
func (e *FieldError) Is(target error) bool
Is lets errors.Is(err, ErrInvalidToken) match any refused field credential.
type FieldPrincipal ¶ added in v0.0.12
type FieldPrincipal struct {
WorkerID string
ClientID string
ClientIdentifier string
WorkerCode string
Name string
AppCode string
DeviceID string
DeviceMode string
// SessionID is the login family (sid).
SessionID string
AMR []string
AuthTime time.Time
ExpiresAt time.Time
// JKT is the thumbprint of the device key the request was proven with.
JKT string
TokenID string
}
FieldPrincipal is a verified field worker on a verified device.
func FieldPrincipalFrom ¶ added in v0.0.12
func FieldPrincipalFrom(ctx context.Context) (*FieldPrincipal, bool)
FieldPrincipalFrom returns the principal FieldMiddleware stored.
type FieldTokenVerifier ¶ added in v0.0.12
type FieldTokenVerifier struct {
// contains filtered or unexported fields
}
FieldTokenVerifier verifies field access tokens and their DPoP proofs.
func NewFieldTokenVerifier ¶ added in v0.0.12
func NewFieldTokenVerifier(cfg FieldTokenVerifierConfig) *FieldTokenVerifier
NewFieldTokenVerifier builds a verifier.
func (*FieldTokenVerifier) Verify ¶ added in v0.0.12
func (v *FieldTokenVerifier) Verify(ctx context.Context, authorizationHeader, dpopHeader, method, requestURL string) (*FieldPrincipal, error)
Verify checks a request's credentials: the Authorization header ("DPoP <token>"), the DPoP proof header, and the request's method and full URL (scheme, host and path; any query is ignored). It returns the verified principal or a *FieldError (also an ErrInvalidToken) naming the reason; discovery and key-fetch failures are ErrDiscovery.
type FieldTokenVerifierConfig ¶ added in v0.0.12
type FieldTokenVerifierConfig struct {
// Issuer is the FlowCatalyst base URL and the tokens' iss claim.
Issuer string
// AppCode is this application's code; tokens must be addressed to it (aud).
AppCode string
// JWKSURL skips OIDC discovery and fetches keys from this URL.
JWKSURL string
// HTTPClient is the transport for discovery and JWKS (default 15 s timeout).
HTTPClient *http.Client
// JWKSRefreshInterval defaults to 1 hour.
JWKSRefreshInterval time.Duration
// ClockSkew bounds exp/nbf checks (default 60 s).
ClockSkew time.Duration
// ProofMaxAge is how far a proof's iat may be from now, either way
// (default 5 minutes). Widen it for devices with bad clocks.
ProofMaxAge time.Duration
// BaseURL is this service's public base URL, used by [FieldMiddleware] to
// rebuild the request URL behind a proxy (BaseURL + request path).
BaseURL string
// Now overrides the clock (tests, fixtures).
Now func() time.Time
}
FieldTokenVerifierConfig configures a FieldTokenVerifier. Issuer and AppCode are required.
type HmacTokenValidator ¶
type HmacTokenValidator struct {
// contains filtered or unexported fields
}
HmacTokenValidator validates HS256-signed JWTs using a shared secret. Use for development or when your app shares a signing secret with FlowCatalyst. For production OIDC integration, prefer TokenValidator (RS256 via JWKS).
func NewHmacTokenValidator ¶
func NewHmacTokenValidator(secret, issuer, audience string) *HmacTokenValidator
NewHmacTokenValidator builds an HS256 validator.
func (*HmacTokenValidator) Validate ¶
func (v *HmacTokenValidator) Validate(token string) (*AuthContext, error)
Validate verifies the HS256 signature, issuer, audience, and exp/nbf.
func (*HmacTokenValidator) ValidateBearer ¶
func (v *HmacTokenValidator) ValidateBearer(authHeader string) (*AuthContext, error)
ValidateBearer strips the "Bearer " prefix and calls Validate.
type IntrospectionResponse ¶
type IntrospectionResponse struct {
Active bool `json:"active"`
Scope string `json:"scope,omitempty"`
ClientID string `json:"client_id,omitempty"`
Username string `json:"username,omitempty"`
TokenType string `json:"token_type,omitempty"`
Exp int64 `json:"exp,omitempty"`
Iat int64 `json:"iat,omitempty"`
Sub string `json:"sub,omitempty"`
}
IntrospectionResponse is the body of /oauth/introspect (RFC 7662).
type JwksCache ¶
type JwksCache struct {
// contains filtered or unexported fields
}
JwksCache wraps jwx's jwk.Cache so callers don't have to deal with the underlying lifetime context.
func NewJwksCache ¶
NewJwksCache builds a cache with the given refresh interval.
type OAuthClient ¶
type OAuthClient struct {
// contains filtered or unexported fields
}
OAuthClient wraps the OAuth2 authorization-code flow + the OIDC session-end (RP-initiated logout) endpoint.
func NewOAuthClient ¶
func NewOAuthClient(cfg OAuthConfig) *OAuthClient
NewOAuthClient builds an OAuthClient. Scopes default to openid/profile/email.
func (*OAuthClient) AuthorizeURL ¶
func (c *OAuthClient) AuthorizeURL() (string, AuthorizeParams)
AuthorizeURL builds the URL to redirect users to for login, plus the session-stored verifier/state/nonce to validate the callback.
func (*OAuthClient) ExchangeCode ¶
func (c *OAuthClient) ExchangeCode(ctx context.Context, code, codeVerifier string) (*TokenResponse, error)
ExchangeCode exchanges an authorization code for tokens. Call this from your callback handler after validating state and PKCE.
func (*OAuthClient) IntrospectToken ¶
func (c *OAuthClient) IntrospectToken(ctx context.Context, token string) (*IntrospectionResponse, error)
IntrospectToken posts to /oauth/introspect and returns the response.
func (*OAuthClient) LogoutURL ¶
func (c *OAuthClient) LogoutURL(postLogoutRedirectURI, idTokenHint, state string) string
LogoutURL builds the RP-Initiated Logout URL.
When postLogoutRedirectURI is non-empty, idTokenHint must also be set — FlowCatalyst uses the hint's aud claim to verify the redirect URI against the client's registered postLogoutRedirectUris (OIDC RP- Initiated Logout 1.0 §2). Omitting the hint causes the OP to refuse the redirect.
func (*OAuthClient) RefreshToken ¶
func (c *OAuthClient) RefreshToken(ctx context.Context, refreshToken string) (*TokenResponse, error)
RefreshToken exchanges a refresh token for a fresh access token.
func (*OAuthClient) RevokeToken ¶
func (c *OAuthClient) RevokeToken(ctx context.Context, token string) error
RevokeToken revokes an access or refresh token per RFC 7009. A 200 response is required; anything else returns an Error.
func (*OAuthClient) UserInfo ¶
func (c *OAuthClient) UserInfo(ctx context.Context, accessToken string) (*UserInfoResponse, error)
UserInfo fetches /oauth/userinfo using the access token.
type OAuthConfig ¶
type OAuthConfig struct {
// IssuerURL is the FlowCatalyst OIDC server base URL.
IssuerURL string
// ClientID is the registered OAuth client id.
ClientID string
// ClientSecret is the OAuth client secret (omit for public clients).
ClientSecret string
// RedirectURI is the application's callback URL.
RedirectURI string
// Scopes requested. Defaults to ["openid", "profile", "email"].
Scopes []string
// HTTPClient overrides the default transport. Optional.
HTTPClient *http.Client
}
OAuthConfig configures an OAuthClient for the authorization-code flow.
type PkceChallenge ¶
type PkceChallenge struct {
// CodeVerifier is the secret. Store in session; send on token exchange.
CodeVerifier string
// CodeChallenge is sent in the authorize URL.
CodeChallenge string
// CodeChallengeMethod is always "S256".
CodeChallengeMethod string
}
PkceChallenge is a one-shot verifier/challenge pair for the OAuth2 authorization-code flow with PKCE (RFC 7636).
func NewPkceChallenge ¶
func NewPkceChallenge() PkceChallenge
NewPkceChallenge generates a fresh PKCE pair using S256.
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,omitempty"`
IDToken string `json:"id_token,omitempty"`
Scope string `json:"scope,omitempty"`
}
TokenResponse is the body of /oauth/token.
type TokenValidator ¶
type TokenValidator struct {
// contains filtered or unexported fields
}
TokenValidator verifies RS256 JWTs against a JWKS fetched via OIDC discovery.
func NewTokenValidator ¶
func NewTokenValidator(cfg TokenValidatorConfig) *TokenValidator
NewTokenValidator builds a TokenValidator. The first call to Validate performs OIDC discovery to find the JWKS URL.
func (*TokenValidator) RefreshJWKS ¶
func (v *TokenValidator) RefreshJWKS(ctx context.Context) error
RefreshJWKS forces an immediate re-fetch of the JWKS, useful after a known key rotation. Subsequent Validate calls will see the new keys.
func (*TokenValidator) Validate ¶
func (v *TokenValidator) Validate(ctx context.Context, token string) (*AuthContext, error)
Validate verifies signature, issuer, audience, and exp/nbf claims.
func (*TokenValidator) ValidateBearer ¶
func (v *TokenValidator) ValidateBearer(ctx context.Context, authHeader string) (*AuthContext, error)
ValidateBearer strips the "Bearer " prefix and calls Validate.
type TokenValidatorConfig ¶
type TokenValidatorConfig struct {
// IssuerURL is the FlowCatalyst OIDC server (e.g. https://auth.flowcatalyst.io).
IssuerURL string
// Audience is your application's expected aud claim. Defaults to "flowcatalyst".
Audience string
// JWKSRefreshInterval is how often jwx checks for key rotation.
// Defaults to 1 hour.
JWKSRefreshInterval time.Duration
// ClockSkew is the acceptable difference for exp/nbf checks.
// Defaults to 60s.
ClockSkew time.Duration
// HTTPClient is the transport used for discovery + JWKS fetch.
// Defaults to a 15-second-timeout client.
HTTPClient *http.Client
}
TokenValidatorConfig configures a TokenValidator. IssuerURL and Audience are required; everything else has a sensible default.
type UserInfoResponse ¶
type UserInfoResponse struct {
Sub string `json:"sub"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
EmailVerified *bool `json:"email_verified,omitempty"`
Extra map[string]any `json:"-"`
}
UserInfoResponse is the body of /oauth/userinfo. Extra carries any custom claims the platform adds.
func (*UserInfoResponse) UnmarshalJSON ¶
func (u *UserInfoResponse) UnmarshalJSON(b []byte) error
UnmarshalJSON keeps standard fields typed and bucketing extras.