Documentation
¶
Overview ¶
Package registration provides OAuth client types and utilities, including RFC 8252 compliant loopback redirect URI support for native OAuth clients.
Package registration provides OAuth 2.0 Dynamic Client Registration (DCR) functionality per RFC 7591, including request validation and secure redirect URI handling for public native clients.
Index ¶
- Constants
- Variables
- func New(cfg Config) (fosite.Client, error)
- func UnionScopes(requested, baseline []string) []string
- func ValidateScopeSubset(subset, superset []string, fieldName string) error
- type Config
- type DCRError
- func ValidateDCRRequest(req *oauthproto.DynamicClientRegistrationRequest) (*oauthproto.DynamicClientRegistrationRequest, *DCRError)
- func ValidatePublicGrantTypes(grantTypes []string) ([]string, *DCRError)
- func ValidatePublicResponseTypes(responseTypes []string) ([]string, *DCRError)
- func ValidateRedirectURI(uri string) *DCRError
- func ValidateScopes(requestedScopes, allowedScopes []string) ([]string, *DCRError)
- type LoopbackClient
Constants ¶
const ( // DCRErrorInvalidRedirectURI indicates that the value of one or more // redirect_uris is invalid. DCRErrorInvalidRedirectURI = "invalid_redirect_uri" // DCRErrorInvalidClientMetadata indicates that the value of one of the // client metadata fields is invalid and the server has rejected this request. DCRErrorInvalidClientMetadata = "invalid_client_metadata" )
DCR error codes per RFC 7591 Section 3.2.2
const ( // MaxRedirectURICount is the maximum number of redirect URIs allowed per client. MaxRedirectURICount = 10 // MaxClientNameLength is the maximum allowed length for a client name. MaxClientNameLength = 256 // MaxSoftwareIDLength is the maximum allowed length for a software_id // value. RFC 7591 does not mandate an upper bound, so we reuse the // client_name cap for consistency — a software_id is a similar-purpose // human-oriented identifier and the same ballpark DoS concerns apply. MaxSoftwareIDLength = 256 )
Validation limits to prevent DoS attacks via excessively large requests.
Variables ¶
var DefaultScopes = []string{"openid", "profile", "email", "offline_access"}
DefaultScopes are the default OAuth 2.0 scopes for registered clients. Includes offline_access to enable refresh token issuance.
Functions ¶
func New ¶
New creates a fosite.Client from the given configuration. Public clients are wrapped in LoopbackClient to support RFC 8252 Section 7.3 compliant loopback redirect URI matching for native OAuth clients. Confidential clients with secrets have their Secret field bcrypt-hashed as required by fosite for credential validation.
func UnionScopes ¶ added in v0.29.0
UnionScopes returns the union of requested and baseline scopes, preserving the order of requested first, then appending any baseline scopes not already present. Duplicates are removed. Returns nil when the result is empty.
Both inputs must already be validated by the caller. UnionScopes does not filter empty strings or validate scope syntax — it only deduplicates and merges in stable order.
func ValidateScopeSubset ¶ added in v0.27.2
ValidateScopeSubset checks that every scope in subset is also present in superset, returning an error that names fieldName and the offending scope.
Shared across the layers that validate baseline-scope configuration so the error message format is identical wherever the violation is caught (a caller using YAML-loaded config and a caller constructing config programmatically both see the same wording).
fieldName should be the wire-format or display name of the field being validated (e.g. "baseline_client_scopes"). It is embedded verbatim in the returned error.
Types ¶
type Config ¶
type Config struct {
// ID is the unique client identifier.
ID string
// Secret is the client secret for confidential clients.
// Empty for public clients.
Secret string //nolint:gosec // G117: field legitimately holds sensitive data
// RedirectURIs is the list of allowed redirect URIs.
RedirectURIs []string
// Public indicates whether this is a public client (no secret).
Public bool
// GrantTypes overrides the default grant types.
// If nil or empty, defaultGrantTypes is used.
GrantTypes []string
// ResponseTypes overrides the default response types.
// If nil or empty, defaultResponseTypes is used.
ResponseTypes []string
// Scopes overrides the default scopes.
// If nil or empty, DefaultScopes is used.
Scopes []string
// Audience is the list of allowed audience values for this client.
// Per RFC 8707, the "resource" parameter in token requests is validated
// against this list. If nil, audience validation will reject all values.
Audience []string
}
Config holds configuration for creating a new OAuth client.
type DCRError ¶
type DCRError struct {
// Error is a single ASCII error code from the defined set.
Error string `json:"error"`
// ErrorDescription is a human-readable text providing additional information.
ErrorDescription string `json:"error_description,omitempty"`
}
DCRError represents an OAuth 2.0 Dynamic Client Registration error response per RFC 7591 Section 3.2.2.
func ValidateDCRRequest ¶
func ValidateDCRRequest( req *oauthproto.DynamicClientRegistrationRequest, ) (*oauthproto.DynamicClientRegistrationRequest, *DCRError)
ValidateDCRRequest validates a DCR request according to RFC 7591 and the server's security policy (loopback-only public clients). Returns the validated request with defaults applied, or an error.
The validated request does NOT carry the requested scopes — scope validation against the server's supported set is a separate step, handled by ValidateScopes using the caller's policy inputs.
func ValidatePublicGrantTypes ¶ added in v0.29.0
ValidatePublicGrantTypes validates the grant_types for a public OAuth client, applying the same rules as DCR: authorization_code must be present, and all declared values must be in the allowed set. Returns the validated slice (with defaults applied when nil/empty) or a *DCRError on violation.
func ValidatePublicResponseTypes ¶ added in v0.29.0
ValidatePublicResponseTypes validates the response_types for a public OAuth client, applying the same rules as DCR: code must be present and all declared values must be in the allowed set. Returns the validated slice (with defaults applied when nil/empty) or a *DCRError on violation.
func ValidateRedirectURI ¶
ValidateRedirectURI validates a redirect URI per RFC 8252: - HTTPS is allowed for any address (web-based redirects) - HTTP is only allowed for loopback addresses (127.0.0.1, [::1], localhost) - Private-use URI schemes (e.g., cursor://, vscode://) are allowed for native apps
func ValidateScopes ¶ added in v0.10.0
ValidateScopes validates a slice of already-parsed scope tokens against the server's allowed set per RFC 7591 §2.
- Empty/nil input falls back to DefaultScopes (which must itself be a subset of allowedScopes; otherwise the call returns an error).
- Each requested scope must appear in allowedScopes; otherwise returns invalid_client_metadata.
- Duplicates in the input are tolerated and deduplicated per RFC 6749 §3.3 (scope is a set of case-sensitive strings).
type LoopbackClient ¶
type LoopbackClient struct {
*fosite.DefaultOpenIDConnectClient
}
LoopbackClient is a fosite.Client implementation that supports RFC 8252 Section 7.3 compliant loopback redirect URI matching for native OAuth clients.
RFC 8252 Section 7.3 specifies that:
- Loopback redirect URIs use "http" (not "https")
- The host must be "127.0.0.1", "[::1]", or "localhost"
- The authorization server MUST allow any port
- The path and query components must match exactly
This client extends fosite's built-in loopback support to also handle "localhost" as a loopback address. Fosite's isMatchingAsLoopback uses isLoopbackAddress() which only supports IP addresses (net.ParseIP().IsLoopback()), not the "localhost" hostname. This is needed for DCR with clients like VS Code, Claude Code, and other native apps that register redirect URIs like "http://localhost/callback" and then request authorization with dynamic ports like "http://localhost:57403/callback".
func NewLoopbackClient ¶
func NewLoopbackClient(client *fosite.DefaultOpenIDConnectClient) *LoopbackClient
NewLoopbackClient creates a new LoopbackClient wrapping the provided client. The wrapper preserves all OIDC fields (including TokenEndpointAuthMethod) while adding RFC 8252 §7.3 dynamic port matching for loopback redirect URIs.
func (*LoopbackClient) GetMatchingRedirectURI ¶
func (c *LoopbackClient) GetMatchingRedirectURI(requestedURI string) string
GetMatchingRedirectURI returns the matching redirect URI if found, or an empty string. For loopback URIs, returns the requested URI (with its port) if it matches a registered loopback pattern.
func (*LoopbackClient) MatchRedirectURI ¶
func (c *LoopbackClient) MatchRedirectURI(requestedURI string) bool
MatchRedirectURI checks if the given redirect URI matches one of the client's registered redirect URIs, with RFC 8252 Section 7.3 loopback support.
For loopback URIs (127.0.0.1, [::1], or localhost), the port is allowed to vary while the scheme, host, path, and query must match exactly.