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 ¶
- Variables
- 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 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 ¶
This section is empty.
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 ¶
This section is empty.
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"`
}
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 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.