-
Notifications
You must be signed in to change notification settings - Fork 208
Expand file tree
/
Copy pathoption.go
More file actions
243 lines (222 loc) · 6.83 KB
/
option.go
File metadata and controls
243 lines (222 loc) · 6.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
package jwtmiddleware
import (
"context"
"errors"
"net/http"
"time"
"github.com/auth0/go-jwt-middleware/v3/core"
"github.com/auth0/go-jwt-middleware/v3/validator"
)
// Option configures the JWTMiddleware.
// Returns error for validation failures.
type Option func(*JWTMiddleware) error
// validatorAdapter adapts the validator.Validator to the core.Validator interface
type validatorAdapter struct {
validator *validator.Validator
}
func (v *validatorAdapter) ValidateToken(ctx context.Context, token string) (any, error) {
return v.validator.ValidateToken(ctx, token)
}
func (v *validatorAdapter) ValidateDPoPProof(ctx context.Context, proofString string) (core.DPoPProofClaims, error) {
return v.validator.ValidateDPoPProof(ctx, proofString)
}
// WithValidator configures the middleware with a JWT validator.
// This is the REQUIRED way to configure the middleware.
//
// The validator must implement ValidateToken, and optionally ValidateDPoPProof
// for DPoP support. The Auth0 validator package provides both methods automatically.
//
// Example:
//
// validator, _ := validator.New(...) // Supports both JWT and DPoP
// middleware, err := jwtmiddleware.New(
// jwtmiddleware.WithValidator(validator),
// )
func WithValidator(v *validator.Validator) Option {
return func(m *JWTMiddleware) error {
if v == nil {
return ErrValidatorNil
}
// Store the validator instance
m.validator = v
return nil
}
}
// WithCredentialsOptional sets whether credentials are optional.
// If set to true, an empty token will be considered valid.
//
// Default: false (credentials required)
func WithCredentialsOptional(value bool) Option {
return func(m *JWTMiddleware) error {
m.credentialsOptional = value
return nil
}
}
// WithValidateOnOptions sets whether OPTIONS requests should have their JWT validated.
//
// Default: true (OPTIONS requests are validated)
func WithValidateOnOptions(value bool) Option {
return func(m *JWTMiddleware) error {
m.validateOnOptions = value
return nil
}
}
// WithErrorHandler sets the handler called when errors occur during JWT validation.
// See the ErrorHandler type for more information.
//
// Default: DefaultErrorHandler
func WithErrorHandler(h ErrorHandler) Option {
return func(m *JWTMiddleware) error {
if h == nil {
return ErrErrorHandlerNil
}
m.errorHandler = h
return nil
}
}
// WithTokenExtractor sets the function to extract the JWT from the request.
//
// Default: AuthHeaderTokenExtractor
func WithTokenExtractor(e TokenExtractor) Option {
return func(m *JWTMiddleware) error {
if e == nil {
return ErrTokenExtractorNil
}
m.tokenExtractor = e
return nil
}
}
// WithExclusionUrls configures URL patterns to exclude from JWT validation.
// URLs can be full URLs or just paths.
func WithExclusionUrls(exclusions []string) Option {
return func(m *JWTMiddleware) error {
if len(exclusions) == 0 {
return ErrExclusionUrlsEmpty
}
m.exclusionURLHandler = func(r *http.Request) bool {
requestFullURL := r.URL.String()
requestPath := r.URL.Path
for _, exclusion := range exclusions {
if requestFullURL == exclusion || requestPath == exclusion {
return true
}
}
return false
}
return nil
}
}
// WithLogger sets an optional logger for the middleware.
// The logger will be used throughout the validation flow in both middleware and core.
//
// The logger interface is compatible with log/slog.Logger and similar loggers.
//
// Example:
//
// middleware, err := jwtmiddleware.New(
// jwtmiddleware.WithValidateToken(validator.ValidateToken),
// jwtmiddleware.WithLogger(slog.Default()),
// )
func WithLogger(logger Logger) Option {
return func(m *JWTMiddleware) error {
if logger == nil {
return ErrLoggerNil
}
m.logger = logger
return nil
}
}
// WithDPoPHeaderExtractor sets a custom DPoP header extractor.
// Optional - defaults to extracting from the "DPoP" HTTP header per RFC 9449.
//
// Use this for non-standard scenarios:
// - Custom header names (e.g., "X-DPoP-Proof")
// - Header transformations (e.g., base64 decoding)
// - Alternative sources (e.g., query parameters)
// - Testing/mocking
//
// Example (custom header name):
//
// middleware, err := jwtmiddleware.New(
// jwtmiddleware.WithValidator(validator),
// jwtmiddleware.WithDPoPHeaderExtractor(func(r *http.Request) (string, error) {
// return r.Header.Get("X-DPoP-Proof"), nil
// }),
// )
func WithDPoPHeaderExtractor(extractor func(*http.Request) (string, error)) Option {
return func(m *JWTMiddleware) error {
if extractor == nil {
return ErrDPoPHeaderExtractorNil
}
m.dpopHeaderExtractor = extractor
return nil
}
}
// WithDPoPMode sets the DPoP operational mode.
//
// Modes:
// - core.DPoPAllowed (default): Accept both Bearer and DPoP tokens
// - core.DPoPRequired: Only accept DPoP tokens, reject Bearer tokens
// - core.DPoPDisabled: Only accept Bearer tokens, ignore DPoP headers
//
// Example:
//
// middleware, err := jwtmiddleware.New(
// jwtmiddleware.WithValidator(validator),
// jwtmiddleware.WithDPoPMode(core.DPoPRequired), // Require DPoP
// )
func WithDPoPMode(mode core.DPoPMode) Option {
return func(m *JWTMiddleware) error {
m.dpopMode = &mode
return nil
}
}
// WithDPoPProofOffset sets the maximum age for DPoP proofs.
// This determines how far in the past a DPoP proof's iat timestamp can be.
//
// Default: 300 seconds (5 minutes)
//
// Example:
//
// middleware, err := jwtmiddleware.New(
// jwtmiddleware.WithValidator(validator),
// jwtmiddleware.WithDPoPProofOffset(60 * time.Second), // Stricter: 60s
// )
func WithDPoPProofOffset(offset time.Duration) Option {
return func(m *JWTMiddleware) error {
if offset < 0 {
return errors.New("DPoP proof offset cannot be negative")
}
m.dpopProofOffset = &offset
return nil
}
}
// WithDPoPIATLeeway sets the clock skew allowance for DPoP proof iat claims.
// This allows DPoP proofs with iat timestamps slightly in the future due to clock drift.
//
// Default: 5 seconds
//
// Example:
//
// middleware, err := jwtmiddleware.New(
// jwtmiddleware.WithValidator(validator),
// jwtmiddleware.WithDPoPIATLeeway(30 * time.Second), // More lenient: 30s
// )
func WithDPoPIATLeeway(leeway time.Duration) Option {
return func(m *JWTMiddleware) error {
if leeway < 0 {
return errors.New("DPoP IAT leeway cannot be negative")
}
m.dpopIATLeeway = &leeway
return nil
}
}
// Sentinel errors for configuration validation
var (
ErrValidatorNil = errors.New("validator cannot be nil (use WithValidator)")
ErrErrorHandlerNil = errors.New("errorHandler cannot be nil")
ErrTokenExtractorNil = errors.New("tokenExtractor cannot be nil")
ErrExclusionUrlsEmpty = errors.New("exclusion URLs list cannot be empty")
ErrLoggerNil = errors.New("logger cannot be nil")
ErrDPoPHeaderExtractorNil = errors.New("DPoP header extractor cannot be nil")
)