dpop

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

Documentation

Overview

Package dpop implements Demonstrating Proof of Possession (DPoP) for OAuth 2.0 per RFC 9449. DPoP binds access and refresh tokens to a client's ephemeral key pair, preventing bearer-token theft from being useful without the corresponding private key.

Flow:

  1. Client generates an ephemeral EC/RSA key pair.
  2. On every token request, the client creates a DPoP proof JWT signed by the private key and sends it in the DPoP header.
  3. This server validates the proof, extracts the public key thumbprint (JWK SHA-256 thumbprint, RFC 7638), and embeds it as cnf.jkt in the issued access token.
  4. On every resource request the client sends a fresh DPoP proof. Resource servers verify both the access token cnf.jkt and the proof signature.

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidNonce = errors.New("dpop: invalid or expired nonce")

ErrInvalidNonce is returned by ConsumeNonce when the presented nonce is unknown, already used, or expired.

Functions

func AccessTokenHash

func AccessTokenHash(accessToken string) string

AccessTokenHash computes the base64url-encoded SHA-256 hash of a raw access token string, as required by the DPoP ath claim (RFC 9449 §4.2).

func ExtractProofNonce

func ExtractProofNonce(proofHeader string) string

ExtractProofNonce reads the `nonce` claim from a DPoP proof JWT without verifying its signature. The nonce's security derives from being server-issued and single-use (validated against NonceStore), so reading it unverified here is safe — the proof signature is validated separately. Returns "" when the proof is absent or has no nonce claim.

func Middleware

func Middleware(store JTIStore, getRequestURL func(r *http.Request) string) func(http.Handler) http.Handler

Middleware returns an HTTP middleware that optionally validates a DPoP proof when present. When the DPoP header is absent the request is treated as a standard Bearer request. When the header is present but invalid, the request is rejected with 401.

The validated *Claims are stored in the request context under the key exported by ClaimsFromContext.

Types

type Claims

type Claims struct {
	// JTI is the unique identifier of this proof (replay prevention).
	JTI string
	// HTTPMethod is the HTTP method the proof was created for.
	HTTPMethod string
	// HTTPURL is the full HTTP URL the proof was created for.
	HTTPURL string
	// IssuedAt is when the proof was signed.
	IssuedAt time.Time
	// Thumbprint is the SHA-256 JWK thumbprint of the proof's public key.
	Thumbprint string
}

Claims holds the validated contents of a DPoP proof JWT.

func ClaimsFromContext

func ClaimsFromContext(ctx context.Context) *Claims

ClaimsFromContext retrieves the DPoP *Claims stored by Middleware. Returns nil when the request did not carry a DPoP proof.

func ValidateProof

func ValidateProof(
	ctx context.Context,
	proofHeader string,
	method string,
	requestURL string,
	accessTokenHash string,
	store JTIStore,
) (*Claims, error)

ValidateProof validates a DPoP proof JWT and returns the extracted claims.

Parameters:

  • proofHeader: the raw value of the DPoP HTTP header.
  • method: expected HTTP method (e.g. "POST").
  • requestURL: expected full request URL (e.g. "https://auth.example.com/oauth/token").
  • accessTokenHash: SHA-256 hash of the access token encoded as base64url (ath claim). Pass "" to skip ath validation (during token issuance there is no access token yet).
  • store: JTI denylist for replay prevention. May be nil (disables replay prevention — only safe in tests).

func ValidateResourceRequest

func ValidateResourceRequest(
	ctx context.Context,
	proofHeader string,
	method string,
	requestURL string,
	accessToken string,
	cnfJKT string,
	store JTIStore,
) (*Claims, error)

ValidateResourceRequest performs full DPoP validation for a resource endpoint. It validates the proof and additionally verifies the proof's JWK thumbprint matches the access token's cnf.jkt claim (RFC 9449 §7).

type JTIStore

type JTIStore interface {
	DenyJTI(ctx context.Context, jti string, ttl time.Duration) error
	IsJTIDenied(ctx context.Context, jti string) (bool, error)
}

JTIStore is the interface used to check and record DPoP proof JTIs for replay prevention. Implemented by the Redis cache.

type NonceManager

type NonceManager struct {
	// contains filtered or unexported fields
}

func NewNonceManager

func NewNonceManager() *NonceManager

func (*NonceManager) Generate

func (nm *NonceManager) Generate() string

func (*NonceManager) SetNonceHeader

func (nm *NonceManager) SetNonceHeader(w http.ResponseWriter)

func (*NonceManager) Stop

func (nm *NonceManager) Stop()

func (*NonceManager) Validate

func (nm *NonceManager) Validate(nonce string) bool

type NonceStore

type NonceStore interface {
	// SaveNonce persists a freshly issued nonce for a client with a TTL.
	SaveNonce(tenantID, clientID int64, nonce string, expiresAt time.Time) error
	// ConsumeNonce atomically validates and marks a nonce used. Returns ok=false
	// when the nonce is unknown, already used, or expired.
	ConsumeNonce(nonce string) (ok bool, err error)
}

NonceStore persists server-issued, single-use DPoP nonces. It is implemented by the oauth_dpop_nonces repository and injected at the composition root, so this platform package stays domain-agnostic. The method signatures match the repository exactly, so the repository satisfies this interface directly.

type StoreNonceManager

type StoreNonceManager struct {
	// contains filtered or unexported fields
}

StoreNonceManager issues and consumes DB-backed, single-use, per-client DPoP server nonces (RFC 9449 §8), unlike the in-memory NonceManager. It is used by the token endpoint's nonce gate for DPoP-required clients.

func NewStoreNonceManager

func NewStoreNonceManager(store NonceStore) *StoreNonceManager

NewStoreNonceManager creates a store-backed nonce manager.

func (*StoreNonceManager) ConsumeNonce

func (m *StoreNonceManager) ConsumeNonce(_ context.Context, nonce string) error

ConsumeNonce validates and single-use-consumes a nonce. Returns ErrInvalidNonce when the nonce is unknown, already used, or expired.

func (*StoreNonceManager) IssueNonce

func (m *StoreNonceManager) IssueNonce(_ context.Context, tenantID, clientID int64) (string, error)

IssueNonce generates a fresh 32-byte base64url nonce, persists it for the client with a 5-minute TTL, and returns it for the DPoP-Nonce response header.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL