@@ -58,6 +58,7 @@ const refreshInterval = 15 * time.Minute
5858
5959// Authentication holds a jwks cache and information about the openid configuration
6060type Authentication struct {
61+ enforceDPoP bool
6162 // cache holds the jwks cache
6263 cache * jwk.Cache
6364 // openidConfigurations holds the openid configuration for each issuer
@@ -70,7 +71,9 @@ type Authentication struct {
7071
7172// Creates new authN which is used to verify tokens for a set of given issuers
7273func NewAuthenticator (ctx context.Context , cfg Config , d * db.Client ) (* Authentication , error ) {
73- a := & Authentication {}
74+ a := & Authentication {
75+ enforceDPoP : cfg .EnforceDPoP ,
76+ }
7477 a .oidcConfigurations = make (map [string ]AuthNConfig )
7578
7679 // validate the configuration
@@ -146,7 +149,7 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
146149 http .Error (w , "missing authorization header" , http .StatusUnauthorized )
147150 return
148151 }
149- tok , dpopKey , err := a .checkToken (r .Context (), header , dpopInfo {
152+ tok , newCtx , err := a .checkToken (r .Context (), header , dpopInfo {
150153 headers : r .Header ["Dpop" ],
151154 path : r .URL .Path ,
152155 method : r .Method ,
@@ -184,7 +187,7 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
184187 return
185188 }
186189
187- handler .ServeHTTP (w , r .WithContext (ContextWithJWK ( r . Context (), dpopKey ) ))
190+ handler .ServeHTTP (w , r .WithContext (newCtx ))
188191 })
189192}
190193
@@ -225,7 +228,7 @@ func (a Authentication) UnaryServerInterceptor(ctx context.Context, req any, inf
225228 action = "other"
226229 }
227230
228- token , dpopJWK , err := a .checkToken (
231+ token , newCtx , err := a .checkToken (
229232 ctx ,
230233 header ,
231234 dpopInfo {
@@ -251,26 +254,20 @@ func (a Authentication) UnaryServerInterceptor(ctx context.Context, req any, inf
251254 return nil , status .Errorf (codes .PermissionDenied , "permission denied" )
252255 }
253256
254- // add dpop key to context
255- ctx = ContextWithJWK (ctx , dpopJWK )
256-
257- return handler (ctx , req )
257+ return handler (newCtx , req )
258258}
259259
260260// checkToken is a helper function to verify the token.
261- func (a Authentication ) checkToken (ctx context.Context , authHeader []string , dpopInfo dpopInfo ) (jwt.Token , jwk. Key , error ) {
261+ func (a Authentication ) checkToken (ctx context.Context , authHeader []string , dpopInfo dpopInfo ) (jwt.Token , context. Context , error ) {
262262 var (
263- tokenRaw string
264- tokenType string
263+ tokenRaw string
265264 )
266265
267266 // If we don't get a DPoP/Bearer token type, we can't proceed
268267 switch {
269268 case strings .HasPrefix (authHeader [0 ], "DPoP " ):
270- tokenType = "DPoP"
271269 tokenRaw = strings .TrimPrefix (authHeader [0 ], "DPoP " )
272270 case strings .HasPrefix (authHeader [0 ], "Bearer " ):
273- tokenType = "Bearer"
274271 tokenRaw = strings .TrimPrefix (authHeader [0 ], "Bearer " )
275272 default :
276273 return nil , nil , fmt .Errorf ("not of type bearer or dpop" )
@@ -314,16 +311,17 @@ func (a Authentication) checkToken(ctx context.Context, authHeader []string, dpo
314311 return nil , nil , err
315312 }
316313
317- if tokenType == "Bearer" {
318- slog .Warn ("Presented bearer token. validating as DPoP" )
314+ _ , tokenHasCNF := accessToken .Get ("cnf" )
315+ if ! tokenHasCNF && a .enforceDPoP {
316+ // this condition is not quite tight because it's possible that the `cnf` claim may
317+ // come from token introspection
318+ return accessToken , ctx , nil
319319 }
320-
321320 key , err := validateDPoP (accessToken , tokenRaw , dpopInfo )
322321 if err != nil {
323322 return nil , nil , err
324323 }
325-
326- return accessToken , * key , nil
324+ return accessToken , ContextWithJWK (ctx , key ), nil
327325}
328326
329327func ContextWithJWK (ctx context.Context , key jwk.Key ) context.Context {
@@ -339,10 +337,10 @@ func GetJWKFromContext(ctx context.Context) jwk.Key {
339337 return jwk
340338 }
341339
342- return nil
340+ panic ( "got something that is not a jwk.Key from the JWK context" )
343341}
344342
345- func validateDPoP (accessToken jwt.Token , acessTokenRaw string , dpopInfo dpopInfo ) (* jwk.Key , error ) {
343+ func validateDPoP (accessToken jwt.Token , acessTokenRaw string , dpopInfo dpopInfo ) (jwk.Key , error ) {
346344 if len (dpopInfo .headers ) != 1 {
347345 return nil , fmt .Errorf ("got %d dpop headers, should have 1" , len (dpopInfo .headers ))
348346 }
@@ -457,7 +455,7 @@ func validateDPoP(accessToken jwt.Token, acessTokenRaw string, dpopInfo dpopInfo
457455 if ath != base64 .URLEncoding .WithPadding (base64 .NoPadding ).EncodeToString (h .Sum (nil )) {
458456 return nil , fmt .Errorf ("incorrect `ath` claim in DPoP JWT" )
459457 }
460- return & dpopKey , nil
458+ return dpopKey , nil
461459}
462460
463461func (a Authentication ) isPublicRoute (path string ) func (string ) bool {
0 commit comments