Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token Propagation and OpenID "azp" claim validation issues #1543

Open
ArnauAregall opened this issue Dec 26, 2023 · 3 comments
Open

Token Propagation and OpenID "azp" claim validation issues #1543

ArnauAregall opened this issue Dec 26, 2023 · 3 comments
Assignees
Labels
status: under consideration The issue is being considered, but has not been accepted yet

Comments

@ArnauAregall
Copy link

ArnauAregall commented Dec 26, 2023

Issue description

Hello,

I have a use case where two Micronaut services are secured using OpenID (idtoken) with an OAuth2 issuer (Keycloak) within the same realm.

Each service is configured to use it's own realm client.

At application level, one service calls the other using HTTP client interfaces, using JWT Token Propagation feature.

identity-service:

micronaut:
  application:
    name: identity-service
  security:
    intercept-url-map:
      - pattern: /api/**
        access:
          - isAuthenticated()
    authentication: idtoken
    oauth2:
      clients:
        keycloak:
          client-id: '${micronaut.application.name}'
          client-secret: '${OAUTH2_IDENTITY_CLIENT_SECRET}'
          issuer: 'http://localhost:8082/realms/petclinic'

pet-service (calls identity-service):

micronaut:
  application:
    name: pet-service
  http:
    services:
      identity-service:
        url: "http://identity-service"
  security:
    intercept-url-map:
      - pattern: /api/**
        access:
          - isAuthenticated()
    authentication: idtoken
    oauth2:
      clients:
        keycloak:
          client-id: '${micronaut.application.name}'
          client-secret: '${OAUTH2_PET_CLIENT_SECRET}'
          issuer: 'http://localhost:8082/realms/petclinic'
    token:
      propagation:
        enabled: true
        service-id-regex: "identity-service"
@Client(id = "identity-service")
internal fun interface IdentityServiceHttpClient {

    @Get("/api/identities/{identityId}")
    @SingleResult
    fun getIdentity(@PathVariable identityId: UUID): Mono<HttpResponse<GetIdentityResponse>>

}

The OAuth clients have configured scopes so the aud claims of the JWT token contains the two client-ids.

Example decoded JWT payload:

{
  "exp": 1703581274,
  "iat": 1703580974,
  "auth_time": 1703580974,
  "jti": "98e88f17-683f-4ecc-8e70-b29ed6a604ab",
  "iss": "http://localhost:8082/realms/petclinic",
  "aud": [
    "pet-service",
    "identity-service"
  ],
  "sub": "b40af7e9-392d-40e9-9eb7-55993c9d2a8e",
  "typ": "ID",
  "azp": "pet-service",
  "nonce": "5a647d92-97e0-4bec-ba17-d8a116e93494",
  "session_state": "7c02f73a-e92b-4187-a70a-b49464e1c4fb",
  "at_hash": "5LMA6RsrsdBKtNk21bMfMA",
  "acr": "1",
  "sid": "7c02f73a-e92b-4187-a70a-b49464e1c4fb",
  "email_verified": false,
  "preferred_username": "system_test_user"
}

The issue I'm experiencing is the following:

The Authorized Party claim (azp) of the token is the pet-service, and when pet-service performs the authenticated HTTP call to identity-service endpoints using the Token Propagation feature, identity-service runs the io.micronaut.security.oauth2.client.IdTokenClaimsValidator and fails at the validateAzp step, even though the audiences validation is successful.

I've verified that disabling openid claims validation on identity-service via configuration is a bypass to the issue.

micronaut:
  security:
    token:
      jwt:
        claims-validators:
          openid-idtoken: false

I have also noticed recent clarifications in regards to azp were added to OpenID specs, and if I got it right the azp should only be validated when using extensions beyond the scope of the spec.

As framework core committers, which approach would you recommend to solve this issue?

Do you believe IdTokenClaimsValidator#validateAzp should be revisited after OpenID spec clarifications perhaps?

Thanks a lot in advance.
Arnau.

@sdelamo sdelamo added the status: under consideration The issue is being considered, but has not been accepted yet label Jan 4, 2024
@sdelamo sdelamo self-assigned this Jan 4, 2024
@sdelamo
Copy link
Contributor

sdelamo commented Jan 4, 2024

Thanks for the detailed issue report. I need to check it further. However, I recommend you not turn off the whole idtoken validator.

Instead, you can do a Bean Replacement and override the method causing issues.

@Singleton
@Replaces(IdTokenClaimsValidator.class)
public class IdTokenClaimsValidatorReplacement extends  IdTokenClaimsValidator {

@Override
 protected boolean validateAzp(@NonNull Claims claims,
                                  @NonNull String clientId,
                                  @NonNull List<String> audiences) {
// do custom logic
}

}

@ArnauAregall
Copy link
Author

Thanks for your answer @sdelamo, I'll try the custom bean replacement approach and get back with feedback.

ArnauAregall added a commit to ArnauAregall/micronaut-kotlin-petclinic-clean-architecture that referenced this issue Jan 5, 2024
ArnauAregall added a commit to ArnauAregall/micronaut-kotlin-petclinic-clean-architecture that referenced this issue Jan 5, 2024
@ArnauAregall
Copy link
Author

Hello @sdelamo, I confirm the suggested bean replacement approach suits as temporal workaround for my issue.

Here is the aforementioned example but with Kotlin that worked fine for me with the custom azp claim validation.

@Singleton
@Replaces(IdTokenClaimsValidator::class)
class CustomIdTokenClaimsValidator<T>(oauthClientConfigurations: Collection<OauthClientConfiguration>): IdTokenClaimsValidator<T>(oauthClientConfigurations) {

    override fun validateAzp(claims: Claims, clientId: String, audiences: MutableList<String>): Boolean {
        if (audiences.size < 2) {
            return true
        }
        return parseAzpClaim(claims)
            .filter { clientId.equals(it, ignoreCase = true) || audiences.containsIgnoreCase(it) }
            .isPresent
    }

}

private fun List<String>.containsIgnoreCase(element: String): Boolean {
    return this.any { it.equals(element, ignoreCase = true) }
}

Thanks again for your answer!

ArnauAregall added a commit to ArnauAregall/micronaut-kotlin-petclinic-clean-architecture that referenced this issue Mar 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: under consideration The issue is being considered, but has not been accepted yet
Projects
No open projects
Status: No status
Development

No branches or pull requests

2 participants