Documentation
¶
Overview ¶
Package oauthcore holds what SystemForge's two Fosite-based authorization servers, identity/systemauth and identity/oauth, share to issue and accept DPoP-bound tokens (RFC 9449) and to rotate signing keys N / N-1:
- KeyRing: a signing key plus the verification keys tokens are checked against by kid, its JWKS and its Fosite signer;
- ParseTokenRequest, VerifyTokenRequestProof, CheckCodeDPoPBinding, BindDPoPGrant, DPoPProofRequiredError and AuthorizationDPoPJKT for the token and authorization endpoints;
- AuthenticateAccessToken, AccessTokenCredentials, TokenAuthError and WriteTokenAuthError for resource endpoints;
- SetConfirmationClaim for the cnf claim of JWT access tokens.
The package is internal: it is not part of SystemForge's public API. It does not depend on either server's session type; callers pass the DPoP binding of their own sessions (a key thumbprint, or a function that reads it).
Index ¶
- Constants
- func AccessTokenCredentials(r *http.Request) (scheme, token string)
- func AuthorizationDPoPJKT(ar fosite.AuthorizeRequester) (string, error)
- func BearerChallenge(code, description, scope string) string
- func BindDPoPGrant(bound *string, proof *dpopproof.Proof, requireProof bool) error
- func CheckCodeDPoPBinding(ctx context.Context, codes CodeStorage, strategy oauth2.AuthorizeCodeStrategy, ...) error
- func DPoPChallenge(code, description string) string
- func DPoPProofRequiredError() *fosite.RFC6749Error
- func ParseTokenRequest(w http.ResponseWriter, r *http.Request) error
- func SetConfirmationClaim(jkt string, claims *jwt.JWTClaims)
- func VerifyTokenRequestProof(ctx context.Context, checker *dpopproof.Checker, r *http.Request, ...) (*dpopproof.Proof, error)
- func WriteChallengeError(w http.ResponseWriter, status int, challenges []string, ...)
- func WriteTokenAuthError(w http.ResponseWriter, e *TokenAuthError)
- type CodeStorage
- type KeyRing
- type SessionJKT
- type TokenAuthError
- type VerificationKey
Constants ¶
const DPoPNonceHeader = "DPoP-Nonce"
DPoPNonceHeader is the response header carrying a server nonce.
const MaxTokenRequestBody = 1 << 20
MaxTokenRequestBody is the largest token request body accepted, ample for any grant.
Variables ¶
This section is empty.
Functions ¶
func AccessTokenCredentials ¶
AccessTokenCredentials returns the scheme ("bearer" or "dpop") and the access token of a request: the Authorization header, or for Bearer the form body of a POST (RFC 6750 §2.2). Query parameters are not accepted.
func AuthorizationDPoPJKT ¶
func AuthorizationDPoPJKT(ar fosite.AuthorizeRequester) (string, error)
AuthorizationDPoPJKT returns the dpop_jkt of an authorization request (RFC 9449 §10), which binds the code to that key, or an error for a malformed one.
func BearerChallenge ¶
BearerChallenge is an RFC 6750 §3 Bearer challenge.
func BindDPoPGrant ¶
BindDPoPGrant applies the DPoP policy to a token request Fosite has parsed and binds the tokens it issues to the proof's key. bound is the DPoP binding of the request's session (its key thumbprint field), which is set to the proof's key:
- without a proof, the request is refused when requireProof is set (the client has no bearer-token exception);
- a code issued for a dpop_jkt, and a refresh token bound to a key, need a proof by that key (RFC 9449 §10, §5);
- with a proof, the access and refresh tokens are bound to its key.
func CheckCodeDPoPBinding ¶
func CheckCodeDPoPBinding(ctx context.Context, codes CodeStorage, strategy oauth2.AuthorizeCodeStrategy, r *http.Request, proof *dpopproof.Proof, session fosite.Session, jkt SessionJKT) error
CheckCodeDPoPBinding refuses an authorization_code token request whose code is bound to a DPoP key (dpop_jkt, RFC 9449 §10) when the request carries no proof by that key. Call it before Fosite handles the request: Fosite's grant handling has side effects even when the exchange is then refused (it deletes the code's PKCE request, which with some storage makes the code unusable), so a refused exchange must not reach it. The code is looked up by its signature under codes into session, an empty session of the server's type, and jkt reads its binding. A request for another grant, or a code that cannot be read, is left to Fosite.
func DPoPChallenge ¶
DPoPChallenge is the DPoP WWW-Authenticate challenge (RFC 9449 §7.1).
func DPoPProofRequiredError ¶
func DPoPProofRequiredError() *fosite.RFC6749Error
DPoPProofRequiredError is the token endpoint error for a client that must use DPoP and sent no proof.
func ParseTokenRequest ¶
func ParseTokenRequest(w http.ResponseWriter, r *http.Request) error
ParseTokenRequest limits the body of a token endpoint request to MaxTokenRequestBody and parses its form. Call it before anything reads the request (FormValue, PostFormValue or Fosite), so a large body cannot exhaust memory. The error is an RFC 6749 error to write with WriteAccessError.
func SetConfirmationClaim ¶
SetConfirmationClaim sets or clears the cnf claim of JWT access tokens to match jkt, the session's DPoP binding.
func VerifyTokenRequestProof ¶
func VerifyTokenRequestProof(ctx context.Context, checker *dpopproof.Checker, r *http.Request, tokenURL string) (*dpopproof.Proof, error)
VerifyTokenRequestProof verifies the DPoP proof of a token request to the token endpoint at tokenURL (its external URL, as discovery advertises it), if the request carries one. A request without one returns nil: whether a proof is required depends on the client. The error is an RFC 6749 error to write with WriteAccessError: invalid_dpop_proof, use_dpop_nonce or server_error.
func WriteChallengeError ¶
func WriteChallengeError(w http.ResponseWriter, status int, challenges []string, code, description string)
WriteChallengeError writes an error response with WWW-Authenticate challenges and, when code is set, an error body.
func WriteTokenAuthError ¶
func WriteTokenAuthError(w http.ResponseWriter, e *TokenAuthError)
WriteTokenAuthError writes the response for a request whose access token AuthenticateAccessToken did not accept: 401 with WWW-Authenticate challenges (RFC 6750 §3, RFC 9449 §7.1) and a DPoP-Nonce to retry with when one is needed, or 500 when the token could not be checked.
Types ¶
type CodeStorage ¶
type CodeStorage interface {
GetAuthorizeCodeSession(ctx context.Context, signature string, session fosite.Session) (fosite.Requester, error)
}
CodeStorage reads authorization codes for CheckCodeDPoPBinding.
type KeyRing ¶
type KeyRing struct {
// contains filtered or unexported fields
}
KeyRing holds a signing key and the keys tokens are verified with: the signing key and every verification key, by kid. A token's kid selects the key that verifies it, and a token with any other kid is refused.
func NewKeyRing ¶
func NewKeyRing(signing *rsa.PrivateKey, signingKID string, verification []VerificationKey) *KeyRing
NewKeyRing returns a key ring that signs with signing, published under signingKID, and verifies also with the verification keys. The caller has resolved and checked the keys: every kid is set, and no two keys share one.
func (*KeyRing) JWKS ¶
func (r *KeyRing) JWKS() jose.JSONWebKeySet
JWKS returns the published key set: the signing key first, then the verification keys.
func (*KeyRing) Signer ¶
Signer returns the Fosite JWT signer of the ring: it signs with the signing key and verifies with the key the token's kid selects.
func (*KeyRing) SigningKeyID ¶
SigningKeyID returns the kid of the signing key, to set in the header of every JWT signed with the ring.
type SessionJKT ¶
SessionJKT returns the RFC 7638 thumbprint of the DPoP key a Fosite session is bound to: "" for an unbound session, a nil one, or one of another type than the server's own.
type TokenAuthError ¶
type TokenAuthError struct {
// contains filtered or unexported fields
}
TokenAuthError is why a request's access token was not accepted.
func AuthenticateAccessToken ¶
func AuthenticateAccessToken(r *http.Request, checker *dpopproof.Checker, htu string, introspect func(ctx context.Context, token string) (fosite.AccessRequester, error), jkt SessionJKT) (fosite.AccessRequester, *TokenAuthError)
AuthenticateAccessToken validates the access token of a request to a resource at htu, the request's external URL without query. introspect returns the request of an active, acceptable access token, or an error to refuse it with invalid_token; jkt reads the DPoP binding of its session. A DPoP-bound token must come with the DPoP scheme and a fresh, unreplayed proof by its key (checked by checker) for the request's method and htu, carrying the token's hash; an unbound token must come with the Bearer scheme. A request without a token returns a *TokenAuthError for which NoToken reports true. Write a refusal with WriteTokenAuthError.
func UnauthorizedError ¶
func UnauthorizedError(code, description string) *TokenAuthError
UnauthorizedError is a 401 refusal with the RFC 6750 error code and description (both may be empty), challenging with both schemes.
func (*TokenAuthError) Error ¶
func (e *TokenAuthError) Error() string
func (*TokenAuthError) NoToken ¶
func (e *TokenAuthError) NoToken() bool
NoToken reports whether the request carried no access token.
func (*TokenAuthError) StatusCode ¶
func (e *TokenAuthError) StatusCode() int
StatusCode is the HTTP status of the refusal: 401, or 500 when the token could not be checked.
func (*TokenAuthError) Unwrap ¶
func (e *TokenAuthError) Unwrap() error
type VerificationKey ¶
VerificationKey is a key tokens are verified with besides the signing key: the previous signing key N-1 after a rotation, or a pre-staged next key.