Skip to content

Commit

Permalink
oidc make token age optional via config property
Browse files Browse the repository at this point in the history
update configuration exception and rename variable

Rewording documentation and always check id token iat

oidc rename variable name throw configuration property error
  • Loading branch information
wesleysalimansdvb committed Mar 8, 2024
1 parent e91bab4 commit 4d825af
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,16 @@ public static Token fromAudience(String... audience) {
@ConfigItem
public Optional<Duration> age = Optional.empty();

/**
* Require that the token includes a `iat` (issued at) claim
*
* Set this property to `false` if your JWT token does not contain an `iat` (issued at) claim.
* Note that ID token is always required to have an `iat` claim and therefore this property has no impact on the ID
* token verification process.
*/
@ConfigItem(defaultValue = "true")
public boolean issuedAtRequired = true;

/**
* Name of the claim which contains a principal name. By default, the `upn`, `preferred_username` and `sub`
* claims are
Expand Down Expand Up @@ -1769,6 +1779,14 @@ public void setAge(Duration age) {
this.age = Optional.of(age);
}

public boolean isIssuedAtRequired() {
return issuedAtRequired;
}

public void setIssuedAtRequired(boolean issuedAtRequired) {
this.issuedAtRequired = issuedAtRequired;
}

public Optional<String> getDecryptionKeyLocation() {
return decryptionKeyLocation;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,8 @@ private Uni<TokenVerificationResult> resolveJwksAndVerifyTokenUni(TenantConfigCo
TokenCredential tokenCred,
boolean enforceAudienceVerification, boolean subjectRequired, String nonce) {
return resolvedContext.provider
.getKeyResolverAndVerifyJwtToken(tokenCred, enforceAudienceVerification, subjectRequired, nonce)
.getKeyResolverAndVerifyJwtToken(tokenCred, enforceAudienceVerification, subjectRequired, nonce,
(tokenCred instanceof IdTokenCredential))
.onFailure(f -> fallbackToIntrospectionIfNoMatchingKey(f, resolvedContext))
.recoverWithUni(f -> introspectTokenUni(resolvedContext, tokenCred.getToken(), true));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,22 +158,22 @@ private Map<String, String> checkRequiredClaimsProp() {

public TokenVerificationResult verifySelfSignedJwtToken(String token) throws InvalidJwtException {
return verifyJwtTokenInternal(token, true, false, null, SYMMETRIC_ALGORITHM_CONSTRAINTS, new SymmetricKeyResolver(),
true);
true, oidcConfig.token.isIssuedAtRequired());
}

public TokenVerificationResult verifyJwtToken(String token, boolean enforceAudienceVerification, boolean subjectRequired,
String nonce)
throws InvalidJwtException {
return verifyJwtTokenInternal(customizeJwtToken(token), enforceAudienceVerification, subjectRequired, nonce,
(requiredAlgorithmConstraints != null ? requiredAlgorithmConstraints : ASYMMETRIC_ALGORITHM_CONSTRAINTS),
asymmetricKeyResolver, true);
asymmetricKeyResolver, true, oidcConfig.token.isIssuedAtRequired());
}

public TokenVerificationResult verifyLogoutJwtToken(String token) throws InvalidJwtException {
final boolean enforceExpReq = !oidcConfig.token.age.isPresent();
TokenVerificationResult result = verifyJwtTokenInternal(token, true, false, null, ASYMMETRIC_ALGORITHM_CONSTRAINTS,
asymmetricKeyResolver,
enforceExpReq);
enforceExpReq, oidcConfig.token.isIssuedAtRequired());
if (!enforceExpReq) {
// Expiry check was skipped during the initial verification but if the logout token contains the exp claim
// then it must be verified
Expand All @@ -191,7 +191,8 @@ private TokenVerificationResult verifyJwtTokenInternal(String token,
boolean subjectRequired,
String nonce,
AlgorithmConstraints algConstraints,
VerificationKeyResolver verificationKeyResolver, boolean enforceExpReq) throws InvalidJwtException {
VerificationKeyResolver verificationKeyResolver, boolean enforceExpReq, boolean issuedAtRequired)
throws InvalidJwtException {
JwtConsumerBuilder builder = new JwtConsumerBuilder();

builder.setVerificationKeyResolver(verificationKeyResolver);
Expand All @@ -209,7 +210,9 @@ private TokenVerificationResult verifyJwtTokenInternal(String token,
builder.registerValidator(new CustomClaimsValidator(Map.of(OidcConstants.NONCE, nonce)));
}

builder.setRequireIssuedAt();
if (issuedAtRequired) {
builder.setRequireIssuedAt();
}

if (issuer != null) {
builder.setExpectedIssuer(issuer);
Expand Down Expand Up @@ -308,7 +311,7 @@ public Uni<? extends TokenVerificationResult> apply(Void v) {

public Uni<TokenVerificationResult> getKeyResolverAndVerifyJwtToken(TokenCredential tokenCred,
boolean enforceAudienceVerification,
boolean subjectRequired, String nonce) {
boolean subjectRequired, String nonce, boolean issuedAtRequired) {
return keyResolverProvider.resolve(tokenCred).onItem()
.transformToUni(new Function<VerificationKeyResolver, Uni<? extends TokenVerificationResult>>() {

Expand All @@ -321,7 +324,7 @@ public Uni<? extends TokenVerificationResult> apply(VerificationKeyResolver reso
subjectRequired, nonce,
(requiredAlgorithmConstraints != null ? requiredAlgorithmConstraints
: ASYMMETRIC_ALGORITHM_CONSTRAINTS),
resolver, true));
resolver, true, issuedAtRequired));
} catch (Throwable t) {
return Uni.createFrom().failure(t);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,13 @@ private Uni<TenantConfigContext> createTenantContext(Vertx vertx, OidcTenantConf
}
}

if (!oidcConfig.token.isIssuedAtRequired() && oidcConfig.token.getAge().isPresent()) {
throw new ConfigurationException(
"The 'token.issued-at-required' can only be set to false if 'token.age' is not set." +
" Either set 'token.issued-at-required' to true or do not set 'token.age'.",
Set.of("quarkus.oidc.token.issued-at-required", "quarkus.oidc.token.age"));
}

return createOidcProvider(oidcConfig, tlsConfig, vertx)
.onItem().transform(new Function<OidcProvider, TenantConfigContext>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package io.quarkus.oidc.runtime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;

import jakarta.json.Json;
Expand All @@ -15,6 +17,8 @@
import org.jose4j.jwk.EllipticCurveJsonWebKey;
import org.jose4j.jwk.RsaJsonWebKey;
import org.jose4j.jwk.RsaJwkGenerator;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.jose4j.keys.EllipticCurves;
import org.jose4j.lang.UnresolvableKeyException;
Expand Down Expand Up @@ -106,7 +110,6 @@ private static String replaceAlgorithm(String token, String algorithm) {

@Test
public void testSubject() throws Exception {

RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
rsaJsonWebKey.setKeyId("k1");
JsonWebKeySet jwkSet = new JsonWebKeySet("{\"keys\": [" + rsaJsonWebKey.toJson() + "]}");
Expand Down Expand Up @@ -134,7 +137,6 @@ public void testSubject() throws Exception {

@Test
public void testNonce() throws Exception {

RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
rsaJsonWebKey.setKeyId("k1");
JsonWebKeySet jwkSet = new JsonWebKeySet("{\"keys\": [" + rsaJsonWebKey.toJson() + "]}");
Expand All @@ -159,4 +161,43 @@ public void testNonce() throws Exception {
}
}
}

@Test
public void testAge() throws Exception {
String tokenPayload = "{\n" +
" \"exp\": " + Instant.now().plusSeconds(1000).getEpochSecond() + "\n" +
"}";

JsonWebSignature jws = new JsonWebSignature();
jws.setPayload(tokenPayload);
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);

RsaJsonWebKey rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);

jws.setKey(rsaJsonWebKey.getPrivateKey());

String token = jws.getCompactSerialization();

JsonWebKeySet jwkSet = new JsonWebKeySet("{\"keys\": [" + rsaJsonWebKey.toJson() + "]}");

OidcTenantConfig oidcConfig = new OidcTenantConfig();
oidcConfig.token.issuedAtRequired = false;

try (OidcProvider provider = new OidcProvider(null, oidcConfig, jwkSet, null, null)) {
TokenVerificationResult result = provider.verifyJwtToken(token, false, false, null);
assertNull(result.localVerificationResult.getString(Claims.iat.name()));
}

OidcTenantConfig oidcConfigRequireAge = new OidcTenantConfig();
oidcConfigRequireAge.token.issuedAtRequired = true;

try (OidcProvider provider = new OidcProvider(null, oidcConfigRequireAge, jwkSet, null, null)) {
try {
provider.verifyJwtToken(token, false, false, null);
fail("InvalidJwtException expected");
} catch (InvalidJwtException ex) {
assertTrue(ex.getMessage().contains("No Issued At (iat) claim present."));
}
}
}
}

0 comments on commit 4d825af

Please sign in to comment.