Skip to content

Commit 8143f64

Browse files
committed
docfix and extra debug around oauth2.jwk_set.ul
1 parent 1b5a36e commit 8143f64

File tree

2 files changed

+150
-4
lines changed

2 files changed

+150
-4
lines changed

README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,59 @@ allow_oauth2_login=true
666666
oauth2.jwk_set.url=https://www.googleapis.com/oauth2/v3/certs
667667
```
668668

669+
### OAuth2 JWKS URI Configuration
670+
671+
The `oauth2.jwk_set.url` property is critical for OAuth2 JWT token validation. OBP-API uses this to verify the authenticity of JWT tokens by fetching the JSON Web Key Set (JWKS) from the specified URI(s).
672+
673+
#### Configuration Methods
674+
675+
The `oauth2.jwk_set.url` property is resolved in the following order of priority:
676+
677+
1. **Environment Variable**
678+
679+
```bash
680+
export OBP_OAUTH2_JWK_SET_URL="https://your-oidc-server.com/jwks"
681+
```
682+
683+
2. **Properties Files** (located in `obp-api/src/main/resources/props/`)
684+
- `production.default.props` (for production deployments)
685+
- `default.props` (for development)
686+
- `test.default.props` (for testing)
687+
688+
#### Supported Formats
689+
690+
- **Single URL**: `oauth2.jwk_set.url=http://localhost:9000/obp-oidc/jwks`
691+
- **Multiple URLs**: `oauth2.jwk_set.url=http://localhost:8080/jwk.json,https://www.googleapis.com/oauth2/v3/certs`
692+
693+
#### Common OAuth2 Provider Examples
694+
695+
- **Google**: `https://www.googleapis.com/oauth2/v3/certs`
696+
- **OBP-OIDC**: `http://localhost:9000/obp-oidc/jwks`
697+
- **Keycloak**: `http://localhost:7070/realms/master/protocol/openid-connect/certs`
698+
- **Azure AD**: `https://login.microsoftonline.com/common/discovery/v2.0/keys`
699+
700+
#### Troubleshooting OBP-20208 Error
701+
702+
If you encounter the error "OBP-20208: Cannot match the issuer and JWKS URI at this server instance", check the following:
703+
704+
1. **Verify JWT Issuer Claim**: The JWT token's `iss` (issuer) claim must match one of the configured identity providers
705+
2. **Check JWKS URL Configuration**: Ensure `oauth2.jwk_set.url` contains URLs that correspond to your JWT issuer
706+
3. **Case-Insensitive Matching**: OBP-API performs case-insensitive substring matching between the issuer and JWKS URLs
707+
4. **URL Format Consistency**: Check for trailing slashes or URL formatting differences
708+
709+
**Debug Logging**: Enable debug logging to see detailed information about the matching process:
710+
711+
```properties
712+
# Add to your logging configuration
713+
logger.code.api.OAuth2=DEBUG
714+
```
715+
716+
The debug logs will show:
717+
718+
- Expected identity provider vs actual JWT issuer claim
719+
- Available JWKS URIs from configuration
720+
- Matching logic results
721+
669722
---
670723

671724
## Frozen APIs

obp-api/src/main/scala/code/api/OAuth2.scala

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,14 +231,65 @@ object OAuth2Login extends RestHelper with MdcLoggable {
231231
def checkUrlOfJwkSets(identityProvider: String) = {
232232
val url: List[String] = Constant.oauth2JwkSetUrl.toList
233233
val jwksUris: List[String] = url.map(_.toLowerCase()).map(_.split(",").toList).flatten
234-
234+
235+
logger.debug(s"checkUrlOfJwkSets - identityProvider: '$identityProvider'")
236+
logger.debug(s"checkUrlOfJwkSets - oauth2.jwk_set.url raw value: '${Constant.oauth2JwkSetUrl}'")
237+
logger.debug(s"checkUrlOfJwkSets - parsed jwksUris: $jwksUris")
238+
235239
// Enhanced matching for both URL-based and semantic identifiers
236240
val identityProviderLower = identityProvider.toLowerCase()
237241
val jwksUri = jwksUris.filter(_.contains(identityProviderLower))
238-
242+
243+
logger.debug(s"checkUrlOfJwkSets - identityProviderLower: '$identityProviderLower'")
244+
logger.debug(s"checkUrlOfJwkSets - filtered jwksUri: $jwksUri")
245+
239246
jwksUri match {
240-
case x :: _ => Full(x)
241-
case Nil => Failure(Oauth2CannotMatchIssuerAndJwksUriException)
247+
case x :: _ =>
248+
logger.debug(s"checkUrlOfJwkSets - SUCCESS: Found matching JWKS URI: '$x'")
249+
Full(x)
250+
case Nil =>
251+
logger.debug(s"checkUrlOfJwkSets - FAILURE: Cannot match issuer '$identityProvider' with any JWKS URI")
252+
logger.debug(s"checkUrlOfJwkSets - Expected issuer pattern: '$identityProvider' (case-insensitive contains match)")
253+
logger.debug(s"checkUrlOfJwkSets - Available JWKS URIs: $jwksUris")
254+
logger.debug(s"checkUrlOfJwkSets - Identity provider (lowercase): '$identityProviderLower'")
255+
logger.debug(s"checkUrlOfJwkSets - Matching logic: Looking for JWKS URIs containing '$identityProviderLower'")
256+
Failure(Oauth2CannotMatchIssuerAndJwksUriException)
257+
}
258+
}
259+
260+
def checkUrlOfJwkSetsWithToken(identityProvider: String, jwtToken: String) = {
261+
val actualIssuer = JwtUtil.getIssuer(jwtToken).getOrElse("NO_ISSUER_CLAIM")
262+
val url: List[String] = Constant.oauth2JwkSetUrl.toList
263+
val jwksUris: List[String] = url.map(_.toLowerCase()).map(_.split(",").toList).flatten
264+
265+
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected identity provider: '$identityProvider'")
266+
logger.debug(s"checkUrlOfJwkSetsWithToken - Actual JWT issuer claim: '$actualIssuer'")
267+
logger.debug(s"checkUrlOfJwkSetsWithToken - oauth2.jwk_set.url raw value: '${Constant.oauth2JwkSetUrl}'")
268+
logger.debug(s"checkUrlOfJwkSetsWithToken - parsed jwksUris: $jwksUris")
269+
270+
// Enhanced matching for both URL-based and semantic identifiers
271+
val identityProviderLower = identityProvider.toLowerCase()
272+
val jwksUri = jwksUris.filter(_.contains(identityProviderLower))
273+
274+
logger.debug(s"checkUrlOfJwkSetsWithToken - identityProviderLower: '$identityProviderLower'")
275+
logger.debug(s"checkUrlOfJwkSetsWithToken - filtered jwksUri: $jwksUri")
276+
277+
jwksUri match {
278+
case x :: _ =>
279+
logger.debug(s"checkUrlOfJwkSetsWithToken - SUCCESS: Found matching JWKS URI: '$x'")
280+
Full(x)
281+
case Nil =>
282+
logger.debug(s"checkUrlOfJwkSetsWithToken - FAILURE: Cannot match issuer with any JWKS URI")
283+
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected identity provider: '$identityProvider'")
284+
logger.debug(s"checkUrlOfJwkSetsWithToken - Actual JWT issuer claim: '$actualIssuer'")
285+
logger.debug(s"checkUrlOfJwkSetsWithToken - Available JWKS URIs: $jwksUris")
286+
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected pattern (lowercase): '$identityProviderLower'")
287+
logger.debug(s"checkUrlOfJwkSetsWithToken - Matching logic: Looking for JWKS URIs containing '$identityProviderLower'")
288+
logger.debug(s"checkUrlOfJwkSetsWithToken - TROUBLESHOOTING:")
289+
logger.debug(s"checkUrlOfJwkSetsWithToken - 1. Verify oauth2.jwk_set.url contains URL matching '$identityProvider'")
290+
logger.debug(s"checkUrlOfJwkSetsWithToken - 2. Check if JWT issuer '$actualIssuer' should match identity provider '$identityProvider'")
291+
logger.debug(s"checkUrlOfJwkSetsWithToken - 3. Ensure case-insensitive substring matching works: does any JWKS URI contain '$identityProviderLower'?")
292+
Failure(Oauth2CannotMatchIssuerAndJwksUriException)
242293
}
243294
}
244295

@@ -259,14 +310,33 @@ object OAuth2Login extends RestHelper with MdcLoggable {
259310
}.getOrElse(false)
260311
}
261312
def validateIdToken(idToken: String): Box[IDTokenClaimsSet] = {
313+
logger.debug(s"validateIdToken - attempting to validate ID token")
314+
315+
// Extract issuer for better error reporting
316+
val actualIssuer = JwtUtil.getIssuer(idToken).getOrElse("NO_ISSUER_CLAIM")
317+
logger.debug(s"validateIdToken - JWT issuer claim: '$actualIssuer'")
318+
262319
urlOfJwkSets match {
263320
case Full(url) =>
321+
logger.debug(s"validateIdToken - using JWKS URL: '$url'")
264322
JwtUtil.validateIdToken(idToken, url)
265323
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
324+
logger.debug(s"validateIdToken - ParamFailure: $a, $b, $c, $apiFailure")
325+
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
266326
ParamFailure(a, b, c, apiFailure : APIFailure)
267327
case Failure(msg, t, c) =>
328+
logger.debug(s"validateIdToken - Failure getting JWKS URL: $msg")
329+
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
330+
if (msg.contains("OBP-20208")) {
331+
logger.debug("validateIdToken - OBP-20208 Error Details:")
332+
logger.debug(s"validateIdToken - JWT issuer claim: '$actualIssuer'")
333+
logger.debug(s"validateIdToken - oauth2.jwk_set.url value: '${Constant.oauth2JwkSetUrl}'")
334+
logger.debug("validateIdToken - Check that the JWKS URL configuration matches the JWT issuer")
335+
}
268336
Failure(msg, t, c)
269337
case _ =>
338+
logger.debug("validateIdToken - No JWKS URL available")
339+
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
270340
Failure(Oauth2ThereIsNoUrlOfJwkSet)
271341
}
272342
}
@@ -414,19 +484,42 @@ object OAuth2Login extends RestHelper with MdcLoggable {
414484
}
415485

416486
def applyIdTokenRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
487+
logger.debug("applyIdTokenRules - starting ID token validation")
488+
489+
// Extract issuer from token for debugging
490+
val actualIssuer = JwtUtil.getIssuer(token).getOrElse("NO_ISSUER_CLAIM")
491+
logger.debug(s"applyIdTokenRules - JWT issuer claim: '$actualIssuer'")
492+
417493
validateIdToken(token) match {
418494
case Full(_) =>
495+
logger.debug("applyIdTokenRules - ID token validation successful")
419496
val user = getOrCreateResourceUser(token)
420497
val consumer = getOrCreateConsumer(token, user.map(_.userId), Some(OpenIdConnect.openIdConnect))
421498
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
422499
case true => ((Failure(UsernameHasBeenLocked), Some(cc.copy(consumer = consumer))))
423500
case false => (user, Some(cc.copy(consumer = consumer)))
424501
}
425502
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
503+
logger.debug(s"applyIdTokenRules - ParamFailure during token validation: $a")
504+
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
426505
(ParamFailure(a, b, c, apiFailure : APIFailure), Some(cc))
427506
case Failure(msg, t, c) =>
507+
logger.debug(s"applyIdTokenRules - Failure during token validation: $msg")
508+
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
509+
if (msg.contains("OBP-20208")) {
510+
logger.debug("applyIdTokenRules - OBP-20208: JWKS URI matching failed. Diagnostic info:")
511+
logger.debug(s"applyIdTokenRules - Actual JWT issuer: '$actualIssuer'")
512+
logger.debug(s"applyIdTokenRules - oauth2.jwk_set.url config: '${Constant.oauth2JwkSetUrl}'")
513+
logger.debug("applyIdTokenRules - Resolution steps:")
514+
logger.debug("1. Verify oauth2.jwk_set.url contains URLs that match the JWT issuer")
515+
logger.debug("2. Check if JWT issuer claim matches expected identity provider")
516+
logger.debug("3. Ensure case-insensitive substring matching works between issuer and JWKS URLs")
517+
logger.debug("4. Consider if trailing slashes or URL formatting might be causing mismatch")
518+
}
428519
(Failure(msg, t, c), Some(cc))
429520
case _ =>
521+
logger.debug("applyIdTokenRules - Unknown failure during token validation")
522+
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
430523
(Failure(Oauth2IJwtCannotBeVerified), Some(cc))
431524
}
432525
}

0 commit comments

Comments
 (0)