docfix and extra debug around oauth2.jwk_set.ul

This commit is contained in:
simonredfern 2025-09-09 18:04:08 +02:00
parent 1b5a36e1e8
commit 8143f6449a
2 changed files with 150 additions and 4 deletions

View File

@ -666,6 +666,59 @@ allow_oauth2_login=true
oauth2.jwk_set.url=https://www.googleapis.com/oauth2/v3/certs
```
### OAuth2 JWKS URI Configuration
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).
#### Configuration Methods
The `oauth2.jwk_set.url` property is resolved in the following order of priority:
1. **Environment Variable**
```bash
export OBP_OAUTH2_JWK_SET_URL="https://your-oidc-server.com/jwks"
```
2. **Properties Files** (located in `obp-api/src/main/resources/props/`)
- `production.default.props` (for production deployments)
- `default.props` (for development)
- `test.default.props` (for testing)
#### Supported Formats
- **Single URL**: `oauth2.jwk_set.url=http://localhost:9000/obp-oidc/jwks`
- **Multiple URLs**: `oauth2.jwk_set.url=http://localhost:8080/jwk.json,https://www.googleapis.com/oauth2/v3/certs`
#### Common OAuth2 Provider Examples
- **Google**: `https://www.googleapis.com/oauth2/v3/certs`
- **OBP-OIDC**: `http://localhost:9000/obp-oidc/jwks`
- **Keycloak**: `http://localhost:7070/realms/master/protocol/openid-connect/certs`
- **Azure AD**: `https://login.microsoftonline.com/common/discovery/v2.0/keys`
#### Troubleshooting OBP-20208 Error
If you encounter the error "OBP-20208: Cannot match the issuer and JWKS URI at this server instance", check the following:
1. **Verify JWT Issuer Claim**: The JWT token's `iss` (issuer) claim must match one of the configured identity providers
2. **Check JWKS URL Configuration**: Ensure `oauth2.jwk_set.url` contains URLs that correspond to your JWT issuer
3. **Case-Insensitive Matching**: OBP-API performs case-insensitive substring matching between the issuer and JWKS URLs
4. **URL Format Consistency**: Check for trailing slashes or URL formatting differences
**Debug Logging**: Enable debug logging to see detailed information about the matching process:
```properties
# Add to your logging configuration
logger.code.api.OAuth2=DEBUG
```
The debug logs will show:
- Expected identity provider vs actual JWT issuer claim
- Available JWKS URIs from configuration
- Matching logic results
---
## Frozen APIs

View File

@ -231,14 +231,65 @@ object OAuth2Login extends RestHelper with MdcLoggable {
def checkUrlOfJwkSets(identityProvider: String) = {
val url: List[String] = Constant.oauth2JwkSetUrl.toList
val jwksUris: List[String] = url.map(_.toLowerCase()).map(_.split(",").toList).flatten
logger.debug(s"checkUrlOfJwkSets - identityProvider: '$identityProvider'")
logger.debug(s"checkUrlOfJwkSets - oauth2.jwk_set.url raw value: '${Constant.oauth2JwkSetUrl}'")
logger.debug(s"checkUrlOfJwkSets - parsed jwksUris: $jwksUris")
// Enhanced matching for both URL-based and semantic identifiers
val identityProviderLower = identityProvider.toLowerCase()
val jwksUri = jwksUris.filter(_.contains(identityProviderLower))
logger.debug(s"checkUrlOfJwkSets - identityProviderLower: '$identityProviderLower'")
logger.debug(s"checkUrlOfJwkSets - filtered jwksUri: $jwksUri")
jwksUri match {
case x :: _ => Full(x)
case Nil => Failure(Oauth2CannotMatchIssuerAndJwksUriException)
case x :: _ =>
logger.debug(s"checkUrlOfJwkSets - SUCCESS: Found matching JWKS URI: '$x'")
Full(x)
case Nil =>
logger.debug(s"checkUrlOfJwkSets - FAILURE: Cannot match issuer '$identityProvider' with any JWKS URI")
logger.debug(s"checkUrlOfJwkSets - Expected issuer pattern: '$identityProvider' (case-insensitive contains match)")
logger.debug(s"checkUrlOfJwkSets - Available JWKS URIs: $jwksUris")
logger.debug(s"checkUrlOfJwkSets - Identity provider (lowercase): '$identityProviderLower'")
logger.debug(s"checkUrlOfJwkSets - Matching logic: Looking for JWKS URIs containing '$identityProviderLower'")
Failure(Oauth2CannotMatchIssuerAndJwksUriException)
}
}
def checkUrlOfJwkSetsWithToken(identityProvider: String, jwtToken: String) = {
val actualIssuer = JwtUtil.getIssuer(jwtToken).getOrElse("NO_ISSUER_CLAIM")
val url: List[String] = Constant.oauth2JwkSetUrl.toList
val jwksUris: List[String] = url.map(_.toLowerCase()).map(_.split(",").toList).flatten
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected identity provider: '$identityProvider'")
logger.debug(s"checkUrlOfJwkSetsWithToken - Actual JWT issuer claim: '$actualIssuer'")
logger.debug(s"checkUrlOfJwkSetsWithToken - oauth2.jwk_set.url raw value: '${Constant.oauth2JwkSetUrl}'")
logger.debug(s"checkUrlOfJwkSetsWithToken - parsed jwksUris: $jwksUris")
// Enhanced matching for both URL-based and semantic identifiers
val identityProviderLower = identityProvider.toLowerCase()
val jwksUri = jwksUris.filter(_.contains(identityProviderLower))
logger.debug(s"checkUrlOfJwkSetsWithToken - identityProviderLower: '$identityProviderLower'")
logger.debug(s"checkUrlOfJwkSetsWithToken - filtered jwksUri: $jwksUri")
jwksUri match {
case x :: _ =>
logger.debug(s"checkUrlOfJwkSetsWithToken - SUCCESS: Found matching JWKS URI: '$x'")
Full(x)
case Nil =>
logger.debug(s"checkUrlOfJwkSetsWithToken - FAILURE: Cannot match issuer with any JWKS URI")
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected identity provider: '$identityProvider'")
logger.debug(s"checkUrlOfJwkSetsWithToken - Actual JWT issuer claim: '$actualIssuer'")
logger.debug(s"checkUrlOfJwkSetsWithToken - Available JWKS URIs: $jwksUris")
logger.debug(s"checkUrlOfJwkSetsWithToken - Expected pattern (lowercase): '$identityProviderLower'")
logger.debug(s"checkUrlOfJwkSetsWithToken - Matching logic: Looking for JWKS URIs containing '$identityProviderLower'")
logger.debug(s"checkUrlOfJwkSetsWithToken - TROUBLESHOOTING:")
logger.debug(s"checkUrlOfJwkSetsWithToken - 1. Verify oauth2.jwk_set.url contains URL matching '$identityProvider'")
logger.debug(s"checkUrlOfJwkSetsWithToken - 2. Check if JWT issuer '$actualIssuer' should match identity provider '$identityProvider'")
logger.debug(s"checkUrlOfJwkSetsWithToken - 3. Ensure case-insensitive substring matching works: does any JWKS URI contain '$identityProviderLower'?")
Failure(Oauth2CannotMatchIssuerAndJwksUriException)
}
}
@ -259,14 +310,33 @@ object OAuth2Login extends RestHelper with MdcLoggable {
}.getOrElse(false)
}
def validateIdToken(idToken: String): Box[IDTokenClaimsSet] = {
logger.debug(s"validateIdToken - attempting to validate ID token")
// Extract issuer for better error reporting
val actualIssuer = JwtUtil.getIssuer(idToken).getOrElse("NO_ISSUER_CLAIM")
logger.debug(s"validateIdToken - JWT issuer claim: '$actualIssuer'")
urlOfJwkSets match {
case Full(url) =>
logger.debug(s"validateIdToken - using JWKS URL: '$url'")
JwtUtil.validateIdToken(idToken, url)
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
logger.debug(s"validateIdToken - ParamFailure: $a, $b, $c, $apiFailure")
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
ParamFailure(a, b, c, apiFailure : APIFailure)
case Failure(msg, t, c) =>
logger.debug(s"validateIdToken - Failure getting JWKS URL: $msg")
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
if (msg.contains("OBP-20208")) {
logger.debug("validateIdToken - OBP-20208 Error Details:")
logger.debug(s"validateIdToken - JWT issuer claim: '$actualIssuer'")
logger.debug(s"validateIdToken - oauth2.jwk_set.url value: '${Constant.oauth2JwkSetUrl}'")
logger.debug("validateIdToken - Check that the JWKS URL configuration matches the JWT issuer")
}
Failure(msg, t, c)
case _ =>
logger.debug("validateIdToken - No JWKS URL available")
logger.debug(s"validateIdToken - JWT issuer was: '$actualIssuer'")
Failure(Oauth2ThereIsNoUrlOfJwkSet)
}
}
@ -414,8 +484,15 @@ object OAuth2Login extends RestHelper with MdcLoggable {
}
def applyIdTokenRules(token: String, cc: CallContext): (Box[User], Some[CallContext]) = {
logger.debug("applyIdTokenRules - starting ID token validation")
// Extract issuer from token for debugging
val actualIssuer = JwtUtil.getIssuer(token).getOrElse("NO_ISSUER_CLAIM")
logger.debug(s"applyIdTokenRules - JWT issuer claim: '$actualIssuer'")
validateIdToken(token) match {
case Full(_) =>
logger.debug("applyIdTokenRules - ID token validation successful")
val user = getOrCreateResourceUser(token)
val consumer = getOrCreateConsumer(token, user.map(_.userId), Some(OpenIdConnect.openIdConnect))
LoginAttempt.userIsLocked(user.map(_.provider).getOrElse(""), user.map(_.name).getOrElse("")) match {
@ -423,10 +500,26 @@ object OAuth2Login extends RestHelper with MdcLoggable {
case false => (user, Some(cc.copy(consumer = consumer)))
}
case ParamFailure(a, b, c, apiFailure : APIFailure) =>
logger.debug(s"applyIdTokenRules - ParamFailure during token validation: $a")
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
(ParamFailure(a, b, c, apiFailure : APIFailure), Some(cc))
case Failure(msg, t, c) =>
logger.debug(s"applyIdTokenRules - Failure during token validation: $msg")
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
if (msg.contains("OBP-20208")) {
logger.debug("applyIdTokenRules - OBP-20208: JWKS URI matching failed. Diagnostic info:")
logger.debug(s"applyIdTokenRules - Actual JWT issuer: '$actualIssuer'")
logger.debug(s"applyIdTokenRules - oauth2.jwk_set.url config: '${Constant.oauth2JwkSetUrl}'")
logger.debug("applyIdTokenRules - Resolution steps:")
logger.debug("1. Verify oauth2.jwk_set.url contains URLs that match the JWT issuer")
logger.debug("2. Check if JWT issuer claim matches expected identity provider")
logger.debug("3. Ensure case-insensitive substring matching works between issuer and JWKS URLs")
logger.debug("4. Consider if trailing slashes or URL formatting might be causing mismatch")
}
(Failure(msg, t, c), Some(cc))
case _ =>
logger.debug("applyIdTokenRules - Unknown failure during token validation")
logger.debug(s"applyIdTokenRules - JWT issuer was: '$actualIssuer'")
(Failure(Oauth2IJwtCannotBeVerified), Some(cc))
}
}