diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index d60c4f8f95579..1d2692ff68939 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -2,6 +2,8 @@ import java.nio.file.Path; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; import java.util.OptionalInt; @@ -299,6 +301,12 @@ public static class Jwt { @ConfigItem public Optional subject = Optional.empty(); + /** + * Additional claims. + */ + @ConfigItem + public Map claims = new HashMap<>(); + /** * Signature algorithm, also used for the {@link #keyFile} property. * Supported values: RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, HS256, HS384, HS512. @@ -368,6 +376,14 @@ public void setKeyFile(String keyFile) { this.keyFile = Optional.of(keyFile); } + public Map getClaims() { + return claims; + } + + public void setClaims(Map claims) { + this.claims = claims; + } + } /** diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 7ddcf22aee201..30e29928faa1b 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -345,6 +345,7 @@ public static Key clientJwtKey(Credentials creds) { public static String signJwtWithKey(OidcCommonConfig oidcConfig, String tokenRequestUri, Key key) { // 'jti' and 'iat' claims are created by default, 'iat' - is set to the current time JwtSignatureBuilder builder = Jwt + .claims(additionalClaims(oidcConfig.credentials.jwt.getClaims())) .issuer(oidcConfig.credentials.jwt.issuer.orElse(oidcConfig.clientId.get())) .subject(oidcConfig.credentials.jwt.subject.orElse(oidcConfig.clientId.get())) .audience(oidcConfig.credentials.jwt.getAudience().isPresent() @@ -367,6 +368,11 @@ public static String signJwtWithKey(OidcCommonConfig oidcConfig, String tokenReq } } + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Map additionalClaims(Map claims) { + return (Map) claims; + } + private static SignatureAlgorithm getSignatureAlgorithm(Credentials credentials, SignatureAlgorithm defaultAlgorithm) { if (credentials.jwt.getSignatureAlgorithm().isPresent()) { try { diff --git a/extensions/oidc-common/runtime/src/test/java/io/quarkus/oidc/common/runtime/OidcCommonUtilsTest.java b/extensions/oidc-common/runtime/src/test/java/io/quarkus/oidc/common/runtime/OidcCommonUtilsTest.java index c4ccd112ba24f..ae280acf797f4 100644 --- a/extensions/oidc-common/runtime/src/test/java/io/quarkus/oidc/common/runtime/OidcCommonUtilsTest.java +++ b/extensions/oidc-common/runtime/src/test/java/io/quarkus/oidc/common/runtime/OidcCommonUtilsTest.java @@ -3,10 +3,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.util.Base64; import java.util.Optional; +import java.util.StringTokenizer; import org.junit.jupiter.api.Test; +import io.vertx.core.json.JsonObject; import io.vertx.core.net.ProxyOptions; public class OidcCommonUtilsTest { @@ -42,4 +48,53 @@ public void testProxyOptionsWithHostWithScheme() throws Exception { assertEquals("user", options.getUsername()); assertEquals("password", options.getPassword()); } + + @Test + public void testJwtTokenWithScope() throws Exception { + OidcCommonConfig cfg = new OidcCommonConfig(); + cfg.setClientId("client"); + cfg.credentials.jwt.claims.put("scope", "read,write"); + PrivateKey key = KeyPairGenerator.getInstance("RSA").generateKeyPair().getPrivate(); + String jwt = OidcCommonUtils.signJwtWithKey(cfg, "http://localhost", key); + JsonObject json = decodeJwtContent(jwt); + String scope = json.getString("scope"); + assertEquals("read,write", scope); + } + + public static JsonObject decodeJwtContent(String jwt) { + String encodedContent = getJwtContentPart(jwt); + if (encodedContent == null) { + return null; + } + return decodeAsJsonObject(encodedContent); + } + + public static String getJwtContentPart(String jwt) { + StringTokenizer tokens = new StringTokenizer(jwt, "."); + // part 1: skip the token headers + tokens.nextToken(); + if (!tokens.hasMoreTokens()) { + return null; + } + // part 2: token content + String encodedContent = tokens.nextToken(); + + // let's check only 1 more signature part is available + if (tokens.countTokens() != 1) { + return null; + } + return encodedContent; + } + + private static JsonObject decodeAsJsonObject(String encodedContent) { + try { + return new JsonObject(base64UrlDecode(encodedContent)); + } catch (IllegalArgumentException ex) { + return null; + } + } + + private static String base64UrlDecode(String encodedContent) { + return new String(Base64.getUrlDecoder().decode(encodedContent), StandardCharsets.UTF_8); + } }