@@ -22,7 +22,6 @@ import (
22
22
"github.com/lestrrat-go/jwx/v2/jwk"
23
23
"github.com/lestrrat-go/jwx/v2/jws"
24
24
"github.com/lestrrat-go/jwx/v2/jwt"
25
- "google.golang.org/grpc/metadata"
26
25
27
26
sdkAudit "github.com/opentdf/platform/sdk/audit"
28
27
"github.com/opentdf/platform/service/logger"
62
61
jwa .PS384 : true ,
63
62
jwa .PS512 : true ,
64
63
}
64
+
65
+ // Exported error variables for client ID processing
66
+ ErrClientIDClaimNotConfigured = errors .New ("no client ID claim configured" )
67
+ ErrClientIDClaimNotFound = errors .New ("client ID claim not found" )
68
+ ErrClientIDClaimNotString = errors .New ("client ID claim is not a string" )
65
69
)
66
70
67
71
const (
@@ -164,7 +168,7 @@ func NewAuthenticator(ctx context.Context, cfg Config, logger *logger.Logger, we
164
168
165
169
// Try an register oidc issuer to wellknown service but don't return an error if it fails
166
170
if err := wellknownRegistration ("platform_issuer" , cfg .Issuer ); err != nil {
167
- logger .Warn ("failed to register platform issuer" , slog .String ("error" , err . Error () ))
171
+ logger .Warn ("failed to register platform issuer" , slog .Any ("error" , err ))
168
172
}
169
173
170
174
var oidcConfigMap map [string ]any
@@ -180,7 +184,7 @@ func NewAuthenticator(ctx context.Context, cfg Config, logger *logger.Logger, we
180
184
}
181
185
182
186
if err := wellknownRegistration ("idp" , oidcConfigMap ); err != nil {
183
- logger .Warn ("failed to register platform idp information" , slog .String ("error" , err . Error () ))
187
+ logger .Warn ("failed to register platform idp information" , slog .Any ("error" , err ))
184
188
}
185
189
186
190
return a , nil
@@ -212,6 +216,7 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
212
216
}
213
217
214
218
dp := r .Header .Values ("Dpop" )
219
+ log := a .logger
215
220
216
221
// Verify the token
217
222
header := r .Header ["Authorization" ]
@@ -228,12 +233,12 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
228
233
origin = "http://" + strings .TrimSuffix (origin , ":80" )
229
234
}
230
235
}
231
- accessTok , ctxWithJWK , err := a .checkToken (r .Context (), header , receiverInfo {
236
+ accessTok , ctx , err := a .checkToken (r .Context (), header , receiverInfo {
232
237
u : []string {normalizeURL (origin , r .URL )},
233
238
m : []string {r .Method },
234
239
}, dp )
235
240
if err != nil {
236
- slog .WarnContext (r . Context () ,
241
+ log .WarnContext (ctx ,
237
242
"unauthenticated" ,
238
243
slog .Any ("error" , err ),
239
244
slog .Any ("dpop" , dp ),
@@ -242,12 +247,19 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
242
247
return
243
248
}
244
249
245
- md , ok := metadata .FromIncomingContext (ctxWithJWK )
246
- if ! ok {
247
- md = metadata .New (nil )
250
+ clientID , err := a .getClientIDFromToken (ctx , accessTok )
251
+ if err != nil {
252
+ log .WarnContext (
253
+ ctx ,
254
+ "could not determine client ID from token" ,
255
+ slog .Any ("err" , err ),
256
+ )
257
+ } else {
258
+ log = log .
259
+ With ("client_id" , clientID ).
260
+ With ("configured_client_id_claim_name" , a .oidcConfiguration .Policy .ClientIDClaim )
261
+ ctx = ctxAuth .ContextWithAuthnMetadata (ctx , clientID )
248
262
}
249
- md .Append ("access_token" , ctxAuth .GetRawAccessTokenFromContext (ctxWithJWK , nil ))
250
- ctxWithJWK = metadata .NewIncomingContext (ctxWithJWK , md )
251
263
252
264
// Check if the token is allowed to access the resource
253
265
var action string
@@ -263,7 +275,8 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
263
275
}
264
276
if allow , err := a .enforcer .Enforce (accessTok , r .URL .Path , action ); err != nil {
265
277
if err .Error () == "permission denied" {
266
- a .logger .WarnContext (r .Context (),
278
+ log .WarnContext (
279
+ ctx ,
267
280
"permission denied" ,
268
281
slog .String ("azp" , accessTok .Subject ()),
269
282
slog .Any ("error" , err ),
@@ -274,12 +287,16 @@ func (a Authentication) MuxHandler(handler http.Handler) http.Handler {
274
287
http .Error (w , "internal server error" , http .StatusInternalServerError )
275
288
return
276
289
} else if ! allow {
277
- a .logger .WarnContext (r .Context (), "permission denied" , slog .String ("azp" , accessTok .Subject ()))
290
+ log .WarnContext (
291
+ ctx ,
292
+ "permission denied" ,
293
+ slog .String ("azp" , accessTok .Subject ()),
294
+ )
278
295
http .Error (w , "permission denied" , http .StatusForbidden )
279
296
return
280
297
}
281
298
282
- r = r .WithContext (ctxWithJWK )
299
+ r = r .WithContext (ctx )
283
300
handler .ServeHTTP (w , r )
284
301
})
285
302
}
@@ -296,6 +313,8 @@ func (a Authentication) ConnectUnaryServerInterceptor() connect.UnaryInterceptor
296
313
return next (ctx , req )
297
314
}
298
315
316
+ log := a .logger
317
+
299
318
ri := receiverInfo {
300
319
u : []string {req .Spec ().Procedure },
301
320
m : []string {http .MethodPost },
@@ -319,7 +338,7 @@ func (a Authentication) ConnectUnaryServerInterceptor() connect.UnaryInterceptor
319
338
resource := p [1 ] + "/" + p [2 ]
320
339
action := getAction (p [2 ])
321
340
322
- token , newCtx , err := a .checkToken (
341
+ token , ctxWithJWK , err := a .checkToken (
323
342
ctx ,
324
343
header ,
325
344
ri ,
@@ -329,22 +348,38 @@ func (a Authentication) ConnectUnaryServerInterceptor() connect.UnaryInterceptor
329
348
return nil , connect .NewError (connect .CodeUnauthenticated , errors .New ("unauthenticated" ))
330
349
}
331
350
351
+ clientID , err := a .getClientIDFromToken (ctxWithJWK , token )
352
+ if err != nil {
353
+ log .WarnContext (
354
+ ctxWithJWK ,
355
+ "could not determine client ID from token" ,
356
+ slog .Any ("err" , err ),
357
+ )
358
+ } else {
359
+ log = log .
360
+ With ("client_id" , clientID ).
361
+ With ("configured_client_id_claim_name" , a .oidcConfiguration .Policy .ClientIDClaim )
362
+ ctxWithJWK = ctxAuth .ContextWithAuthnMetadata (ctxWithJWK , clientID )
363
+ }
364
+
332
365
// Check if the token is allowed to access the resource
333
366
if allowed , err := a .enforcer .Enforce (token , resource , action ); err != nil {
334
367
if err .Error () == "permission denied" {
335
- a .logger .Warn ("permission denied" ,
368
+ log .WarnContext (
369
+ ctxWithJWK ,
370
+ "permission denied" ,
336
371
slog .String ("azp" , token .Subject ()),
337
372
slog .Any ("error" , err ),
338
373
)
339
374
return nil , connect .NewError (connect .CodePermissionDenied , errors .New ("permission denied" ))
340
375
}
341
376
return nil , err
342
377
} else if ! allowed {
343
- a . logger . Warn ( "permission denied" , slog .String ("azp" , token .Subject ()))
378
+ log . WarnContext ( ctxWithJWK , "permission denied" , slog .String ("azp" , token .Subject ()))
344
379
return nil , connect .NewError (connect .CodePermissionDenied , errors .New ("permission denied" ))
345
380
}
346
381
347
- return next (newCtx , req )
382
+ return next (ctxWithJWK , req )
348
383
})
349
384
}
350
385
return connect .UnaryInterceptorFunc (interceptor )
@@ -399,7 +434,7 @@ func (a *Authentication) checkToken(ctx context.Context, authHeader []string, dp
399
434
case strings .HasPrefix (authHeader [0 ], "Bearer " ):
400
435
tokenRaw = strings .TrimPrefix (authHeader [0 ], "Bearer " )
401
436
default :
402
- a .logger .Warn ( "failed to validate authentication header: not of type bearer or dpop" , slog .String ("header" , authHeader [0 ]))
437
+ a .logger .WarnContext ( ctx , "failed to validate authentication header: not of type bearer or dpop" , slog .String ("header" , authHeader [0 ]))
403
438
return nil , nil , errors .New ("not of type bearer or dpop" )
404
439
}
405
440
@@ -431,12 +466,12 @@ func (a *Authentication) checkToken(ctx context.Context, authHeader []string, dp
431
466
ctx = ctxAuth .ContextWithAuthNInfo (ctx , nil , accessToken , tokenRaw )
432
467
return accessToken , ctx , nil
433
468
}
434
- key , err := a .validateDPoP (accessToken , tokenRaw , dpopInfo , dpopHeader )
469
+ dpopKey , err := a .validateDPoP (accessToken , tokenRaw , dpopInfo , dpopHeader )
435
470
if err != nil {
436
471
a .logger .Warn ("failed to validate dpop" , slog .Any ("err" , err ))
437
472
return nil , nil , err
438
473
}
439
- ctx = ctxAuth .ContextWithAuthNInfo (ctx , key , accessToken , tokenRaw )
474
+ ctx = ctxAuth .ContextWithAuthNInfo (ctx , dpopKey , accessToken , tokenRaw )
440
475
return accessToken , ctx , nil
441
476
}
442
477
@@ -668,7 +703,7 @@ func (a Authentication) ipcReauthCheck(ctx context.Context, path string, header
668
703
u = append (u , a .lookupGatewayPaths (ctx , path , header )... )
669
704
670
705
// Validate the token and create a JWT token
671
- _ , nextCtx , err := a .checkToken (ctx , authHeader , receiverInfo {
706
+ token , ctxWithJWK , err := a .checkToken (ctx , authHeader , receiverInfo {
672
707
u : u ,
673
708
m : []string {http .MethodPost },
674
709
}, header ["Dpop" ])
@@ -677,8 +712,33 @@ func (a Authentication) ipcReauthCheck(ctx context.Context, path string, header
677
712
}
678
713
679
714
// Return the next context with the token
680
- return nextCtx , nil
715
+ clientID , err := a .getClientIDFromToken (ctxWithJWK , token )
716
+ if err != nil {
717
+ return nil , connect .NewError (connect .CodeUnauthenticated , errors .New ("unauthenticated" ))
718
+ }
719
+ return ctxAuth .ContextWithAuthnMetadata (ctxWithJWK , clientID ), nil
681
720
}
682
721
}
683
722
return ctx , nil
684
723
}
724
+
725
+ // getClientIDFromToken returns the client ID from the token if found (dot notation)
726
+ func (a * Authentication ) getClientIDFromToken (ctx context.Context , tok jwt.Token ) (string , error ) {
727
+ clientIDClaim := a .oidcConfiguration .Policy .ClientIDClaim
728
+ if clientIDClaim == "" {
729
+ return "" , ErrClientIDClaimNotConfigured
730
+ }
731
+ claimsMap , err := tok .AsMap (ctx )
732
+ if err != nil {
733
+ return "" , fmt .Errorf ("failed to parse token as a map and find claim at [%s]: %w" , clientIDClaim , err )
734
+ }
735
+ found := dotNotation (claimsMap , clientIDClaim )
736
+ if found == nil {
737
+ return "" , fmt .Errorf ("%w at [%s]" , ErrClientIDClaimNotFound , clientIDClaim )
738
+ }
739
+ clientID , isString := found .(string )
740
+ if ! isString {
741
+ return "" , fmt .Errorf ("%w at [%s]" , ErrClientIDClaimNotString , clientIDClaim )
742
+ }
743
+ return clientID , nil
744
+ }
0 commit comments