From d154f9e07114064718a638372afbd26fa07405a6 Mon Sep 17 00:00:00 2001
From: rmartinc
Date: Tue, 2 Dec 2025 08:45:37 +0100
Subject: [PATCH] Improve Public Key Management for JWTAuthorizationGrant
identity provider Closes #44243
Signed-off-by: rmartinc
---
.github/workflows/js-ci.yml | 2 +-
.../idm/CertificateRepresentation.java | 9 ++
.../admin/messages/messages_en.properties | 3 +-
.../src/clients/keys/ImportKeyDialog.tsx | 8 +-
.../add/DiscoverySettings.tsx | 44 +-----
.../add/JWTAuthorizationGrantSettings.tsx | 12 +-
.../identity-providers/add/JwksSettings.tsx | 129 ++++++++++++++++
.../jwt-authorization-grant.spec.ts | 138 ++++++++++++++++++
.../admin-ui/test/identity-providers/main.ts | 32 ++++
.../test/identity-providers/oidc.spec.ts | 7 +-
js/apps/admin-ui/test/utils/file-chooser.ts | 13 +-
js/apps/admin-ui/test/utils/files/key.jwks | 9 ++
js/apps/admin-ui/test/utils/files/key.pem | 3 +
.../src/defs/certificateRepresentation.ts | 1 +
.../src/resources/identityProviders.ts | 10 ++
.../JWTAuthorizationGrantConfig.java | 47 ++++++
...JWTAuthorizationGrantIdentityProvider.java | 13 +-
...TAuthorizationGrantJWKSEndpointLoader.java | 28 ----
.../oidc/OIDCIdentityProviderConfig.java | 49 +------
.../keys/loader/HardcodedPublicKeyLoader.java | 5 +-
.../OIDCIdentityProviderPublicKeyLoader.java | 51 ++-----
.../keys/loader/PublicKeyStorageManager.java | 23 ++-
.../ClientAttributeCertificateResource.java | 117 ++-------------
.../admin/IdentityProvidersResource.java | 33 +++++
.../services/util/CertificateInfoHelper.java | 106 ++++++++++++++
.../AbstractJWTAuthorizationGrantTest.java | 44 ++++++
.../oauth/JWTAuthorizationGrantTest.java | 1 +
.../broker/KcOIDCBrokerWithSignatureTest.java | 124 +++++++++++++++-
.../broker/KcOidcBrokerConfiguration.java | 6 +-
.../broker/KcOidcBrokerLogoutTest.java | 8 +-
.../AbstractClientAuthSignedJWTTest.java | 10 +-
.../oauth/ClientAuthSignedJWTTest.java | 3 +-
32 files changed, 775 insertions(+), 313 deletions(-)
create mode 100644 js/apps/admin-ui/src/identity-providers/add/JwksSettings.tsx
create mode 100644 js/apps/admin-ui/test/identity-providers/jwt-authorization-grant.spec.ts
create mode 100644 js/apps/admin-ui/test/utils/files/key.jwks
create mode 100644 js/apps/admin-ui/test/utils/files/key.pem
delete mode 100644 services/src/main/java/org/keycloak/broker/jwtauthorizationgrant/JWTAuthorizationGrantJWKSEndpointLoader.java
diff --git a/.github/workflows/js-ci.yml b/.github/workflows/js-ci.yml
index b5b76870e1c0..7aadf938645c 100644
--- a/.github/workflows/js-ci.yml
+++ b/.github/workflows/js-ci.yml
@@ -230,7 +230,7 @@ jobs:
- name: Start Keycloak server
run: |
tar xfvz keycloak-999.0.0-SNAPSHOT.tar.gz
- keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v2,transient-users,spiffe,oid4vc-vci,kubernetes-service-accounts &> ~/server.log &
+ keycloak-999.0.0-SNAPSHOT/bin/kc.sh start-dev --features=admin-fine-grained-authz:v2,transient-users,spiffe,oid4vc-vci,kubernetes-service-accounts,jwt-authorization-grant &> ~/server.log &
env:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: admin
diff --git a/core/src/main/java/org/keycloak/representations/idm/CertificateRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/CertificateRepresentation.java
index d10616caaf1e..0b328a0240b0 100755
--- a/core/src/main/java/org/keycloak/representations/idm/CertificateRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/CertificateRepresentation.java
@@ -28,6 +28,7 @@ public class CertificateRepresentation {
protected String publicKey;
protected String certificate;
protected String kid;
+ protected String jwks;
public String getPrivateKey() {
return privateKey;
@@ -60,4 +61,12 @@ public String getKid() {
public void setKid(String kid) {
this.kid = kid;
}
+
+ public String getJwks() {
+ return jwks;
+ }
+
+ public void setJwks(String jwks) {
+ this.jwks = jwks;
+ }
}
diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
index 30bb956c520d..eb96da062c91 100644
--- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
+++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
@@ -805,6 +805,7 @@ deleteMappingTitle=Delete mapping?
profile=Profile
active=Active
generateKeysDescription=If you generate new keys, you can download the keystore with the private key automatically and save it on your client's side. Keycloak server will save just the certificate and public key, but not the private key.
+importKeysDescription=Import a public key using different file formats. Please select the type of archive you want to import.
addSubFlowTitle=Add a sub-flow
useTruststoreSpiHelp=Specifies whether LDAP connection will use the Truststore SPI with the truststore configured in command-line options. 'Always' means that it will always use it. 'Never' means that it will not use it. Note that even if Keycloak truststore is not configured, the default java cacerts or certificate specified by 'javax.net.ssl.trustStore' property will be used.
forcePostBindingHelp=Always use POST binding for responses.
@@ -1974,7 +1975,7 @@ scopeParameter=Scope parameter
unsigned=Unsigned
userGroupsRetrieveStrategy=User groups retrieve strategy
addSubFlow=Add sub-flow
-validatingPublicKeyHelp=The public key in PEM format that must be used to verify external IDP signatures.
+validatingPublicKeyHelp=The public key in PEM or JWKS format that must be used to verify external IDP signatures. The button below can be used to import an external file with different key and certificate formats. The provider needs to be saved after the import to store the changes.
client-uris-must-match.label=Client URIs Must Match
webAuthnPolicyAcceptableAaguids=Acceptable AAGUIDs
noRoles-roles=No roles in this realm
diff --git a/js/apps/admin-ui/src/clients/keys/ImportKeyDialog.tsx b/js/apps/admin-ui/src/clients/keys/ImportKeyDialog.tsx
index 75869c5d8da0..12b82d9ec84b 100644
--- a/js/apps/admin-ui/src/clients/keys/ImportKeyDialog.tsx
+++ b/js/apps/admin-ui/src/clients/keys/ImportKeyDialog.tsx
@@ -16,6 +16,8 @@ import { StoreSettings } from "./StoreSettings";
type ImportKeyDialogProps = {
toggleDialog: () => void;
save: (importFile: ImportFile) => void;
+ title?: string;
+ description?: string;
};
export type ImportFile = {
@@ -28,6 +30,8 @@ export type ImportFile = {
export const ImportKeyDialog = ({
save,
toggleDialog,
+ title = "generateKeys",
+ description = "generateKeysDescription",
}: ImportKeyDialogProps) => {
const { t } = useTranslation();
const form = useForm();
@@ -50,7 +54,7 @@ export const ImportKeyDialog = ({
return (
- {t("generateKeysDescription")}
+ {t(description)}