Created
February 3, 2026 17:22
-
-
Save thomasdarimont/921213eebbf388856f0fa6ebc680d3e1 to your computer and use it in GitHub Desktop.
Custom CustomAzureOidcIdentityProvider to support client assertions with managed identities
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package com.github.thomasdarimont.keycloak.custom.idp.azure; | |
| import com.google.auto.service.AutoService; | |
| import lombok.val; | |
| import org.keycloak.authentication.ClientAuthenticationFlowContext; | |
| import org.keycloak.authentication.authenticators.client.FederatedJWTClientValidator; | |
| import org.keycloak.broker.oidc.OIDCIdentityProvider; | |
| import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; | |
| import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; | |
| import org.keycloak.broker.provider.IdentityProviderFactory; | |
| import org.keycloak.common.Profile; | |
| import org.keycloak.models.IdentityProviderModel; | |
| import org.keycloak.models.KeycloakSession; | |
| import java.time.Duration; | |
| import java.util.ArrayList; | |
| import java.util.List; | |
| public class CustomAzureOidcIdentityProvider extends OIDCIdentityProvider { | |
| public static final String PROVIDER_ID = "acme-oidc"; | |
| public CustomAzureOidcIdentityProvider(KeycloakSession session, OIDCIdentityProviderConfig config) { | |
| super(session, config); | |
| } | |
| @Override | |
| public boolean verifyClientAssertion(ClientAuthenticationFlowContext context) throws Exception { | |
| OIDCIdentityProviderConfig config = getConfig(); | |
| FederatedJWTClientValidator validator = new CustomFederatedJWTClientValidator(context, v -> verifySignature(v.getJws()), | |
| config.getIssuer(), config.getAllowedClockSkew(), config.isSupportsClientAssertionReuse()); | |
| // Azure Managed Identity Access Token are valid for 55min, default in Keycloak is 5mins | |
| validator.setMaximumExpirationTime((int)Duration.ofMinutes(120).toSeconds()); | |
| if (!Profile.isFeatureEnabled(Profile.Feature.CLIENT_AUTH_FEDERATED)) { | |
| return false; | |
| } | |
| if (!config.isSupportsClientAssertions()) { | |
| throw new RuntimeException("Issuer does not support client assertions"); | |
| } | |
| return validator.validate(); | |
| } | |
| public static class CustomFederatedJWTClientValidator extends FederatedJWTClientValidator { | |
| public CustomFederatedJWTClientValidator(ClientAuthenticationFlowContext context, SignatureValidator signatureValidator, String expectedTokenIssuer, int allowedClockSkew, boolean reusePermitted) throws Exception { | |
| super(context, signatureValidator, expectedTokenIssuer, allowedClockSkew, reusePermitted); | |
| } | |
| @Override | |
| protected List<String> getExpectedAudiences() { | |
| List<String> audiences = new ArrayList<>(super.getExpectedAudiences()); | |
| // TODO make additional allowed audiences configurable | |
| audiences.add("api://someuuid"); | |
| audiences.add("https://somesubdomain.sometenanttrusted.custom.domain.com"); | |
| return audiences; | |
| } | |
| } | |
| @AutoService(IdentityProviderFactory.class) | |
| public static class Factory extends OIDCIdentityProviderFactory { | |
| @Override | |
| public String getId() { | |
| return CustomAzureOidcIdentityProvider.PROVIDER_ID; | |
| } | |
| @Override | |
| public String getName() { | |
| return "Acme: Azure OpenID Connect v1.0"; | |
| } | |
| @Override | |
| public OIDCIdentityProvider create(KeycloakSession session, IdentityProviderModel model) { | |
| return new CustomAzureOidcIdentityProvider(session, new OIDCIdentityProviderConfig(model)); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment