[java-idp-oidc] branch main updated: JOIDC-235 - AdministrativeLogoutConfiguration for OIDC

Henri Mikkonen henri.mikkonen at iki.fi
Wed Jun 11 14:57:28 UTC 2025


This is an automated email from the git hooks/post-receive script.

hjmikkon pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=bd4c97928ae6ea8455ed77f8f079baaf6685ba7b

The following commit(s) were added to refs/heads/main by this push:
     new bd4c9792 JOIDC-235 - AdministrativeLogoutConfiguration for OIDC
bd4c9792 is described below

commit bd4c97928ae6ea8455ed77f8f079baaf6685ba7b
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Jun 11 17:57:00 2025 +0300

    JOIDC-235 - AdministrativeLogoutConfiguration for OIDC
    
    https://shibboleth.atlassian.net/browse/JOIDC-235
    
    Initial implementation for the administrative token revocation -hook and attribute based revocation implementation
    - The hook is of type: BiPredicate<ProfileRequestContext,JWTClaimsSet>
    - May be activated via 'idp.oauth2.revocationCondition', defaults to false
    - May be customised via 'idp.oauth2.revocationCondition.custom', defaults to 'shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition'
      - The attribute ID for the default bean can be customized via 'idp.oauth2.revocationCondition.attributeId', defaults to 'revocation'
    - Activated bean is wired to:
      - ValidateGrant SWF-action in the token flow (to cover authorization code and refresh token grant vaidation)
      - Default claims validator sets for introspection and userinfo profiles
---
 .../plugin/oidc/op/profile/impl/ValidateGrant.java |  23 ++
 .../logic/AttributeTokenRevocationCondition.java   | 277 +++++++++++++++++++++
 .../impl/TokenRevocationConditionValidator.java    |  57 +++++
 .../META-INF/net.shibboleth.idp/postconfig.xml     |  10 +
 .../idp/flows/oidc/token/token-beans.xml           |   3 +-
 .../idp/service/relying-party/postconfig.xml       |   7 +
 .../idp/plugin/oidc/op/conf/oidc.properties        |   7 +
 .../op/profile/flow/AbstractOidcApiFlowTest.java   |   2 +
 .../op/profile/flow/IntrospectionFlowTest.java     |  78 +++++-
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java |  22 ++
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  |  25 ++
 .../AttributeTokenRevocationConditionTest.java     | 167 +++++++++++++
 .../idp/module/conf/attribute-resolver.xml         |  11 +
 .../net/shibboleth/idp/module/conf/global.xml      |  10 +
 .../net/shibboleth/idp/module/conf/oidc.properties |   5 +-
 15 files changed, 701 insertions(+), 3 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
index 70f41ea0..c884911d 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateGrant.java
@@ -19,6 +19,7 @@ import java.time.Duration;
 import java.time.Instant;
 import java.util.List;
 import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -32,6 +33,7 @@ import org.opensaml.storage.ReplayCache;
 import org.opensaml.storage.RevocationCache;
 import org.slf4j.Logger;
 
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.oauth2.sdk.AuthorizationCodeGrant;
 import com.nimbusds.oauth2.sdk.AuthorizationGrant;
 import com.nimbusds.oauth2.sdk.GrantType;
@@ -111,6 +113,9 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
     /** Strategy used to locate thumbprint of validated DPoP Proof JWT. */
     @Nonnull private Function<ProfileRequestContext, String> dpopProofThumbprintLookupStrategy;
 
+    /** Predicate used to indicate whether the authorization code or refresh token is revoked. */
+    @Nullable private BiPredicate<ProfileRequestContext,JWTClaimsSet> tokenRevocationCondition;
+
     /** The RelyingPartyContext to operate on. */
     @Nullable private RelyingPartyContext rpCtx;
 
@@ -226,6 +231,18 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
         dpopProofThumbprintLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
     }
 
+    /**
+     * Set the predicate used to indicate whether the authorization code or refresh token is revoked.
+     * 
+     * @param condition token revocation condition
+     * 
+     * @since 4.3.0
+     */
+    public void setTokenRevocationCondition(@Nullable final BiPredicate<ProfileRequestContext,JWTClaimsSet> condition) {
+        checkSetterPreconditions();
+        tokenRevocationCondition = condition;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -402,6 +419,12 @@ public class ValidateGrant extends AbstractOIDCTokenResponseAction {
             ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
             return;
         }
+        if (tokenRevocationCondition != null
+                && tokenRevocationCondition.test(profileRequestContext, tokenClaimsSet.getClaimsSet())) {
+            log.warn("{} Token has been revoked by the token revocation condition", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_GRANT);
+            return;
+        }
         final ClientID clientId = tokenClaimsSet.getClientID();
         assert clientId != null;
         assert rpCtx != null;
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeTokenRevocationCondition.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeTokenRevocationCondition.java
new file mode 100644
index 00000000..b0986eea
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeTokenRevocationCondition.java
@@ -0,0 +1,277 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.logic;
+
+import java.text.ParseException;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.ScratchContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.attribute.DateTimeAttributeValue;
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.IdPAttributeValue;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
+import net.shibboleth.profile.context.navigate.RelyingPartyIdLookupFunction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+import net.shibboleth.shared.service.ReloadableService;
+
+/**
+ * A condition that checks for token revocation against a resolved {@link IdPAttribute}.
+ * 
+ * @since 4.3.0
+ */
+public class AttributeTokenRevocationCondition extends AbstractInitializableComponent
+    implements BiPredicate<ProfileRequestContext,JWTClaimsSet> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AttributeTokenRevocationCondition.class);
+
+    /** Lookup strategy for principal name. */
+    @NonnullAfterInit private BiFunction<ProfileRequestContext,JWTClaimsSet,String> principalNameLookupStrategy;
+
+    /** Strategy used to locate the identity of the issuer associated with the attribute resolution. */
+    @Nullable private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+    /** Strategy used to locate the identity of the recipient associated with the attribute resolution. */
+    @Nullable private Function<ProfileRequestContext,String> recipientLookupStrategy;
+
+    /** Attribute Resolver service. */
+    @NonnullAfterInit private ReloadableService<AttributeResolver> attributeResolver;
+
+    /** Attribute ID to resolve. */
+    @NonnullAfterInit @NotEmpty private String attributeId;
+
+    /** Constructor. */
+    public AttributeTokenRevocationCondition() {
+        issuerLookupStrategy = new IssuerLookupFunction();
+        recipientLookupStrategy = new RelyingPartyIdLookupFunction();
+    }
+
+    /**
+     * Set lookup strategy for principal name.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setPrincipalNameLookupStrategy(
+            @Nonnull final BiFunction<ProfileRequestContext,JWTClaimsSet,String> strategy) {
+        checkSetterPreconditions();
+
+        principalNameLookupStrategy = Constraint.isNotNull(strategy, "Principal name lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup the issuer for this attribute resolution.
+     * 
+     * @param strategy  lookup strategy
+     */
+    public void setIssuerLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+
+        issuerLookupStrategy = strategy;
+    }
+
+    /**
+     * Set the strategy used to lookup the recipient for this attribute resolution.
+     * 
+     * @param strategy  lookup strategy
+     */
+    public void setRecipientLookupStrategy(@Nullable final Function<ProfileRequestContext,String> strategy) {
+        checkSetterPreconditions();
+
+        recipientLookupStrategy = strategy;
+    }
+
+    /**
+     * Set {@link AttributeResolver} to use.
+     * 
+     * @param service attribute resolver service
+     */
+    public void setAttributeResolver(@Nonnull final ReloadableService<AttributeResolver> service) {
+        checkSetterPreconditions();
+
+        attributeResolver = Constraint.isNotNull(service, "ReloadableService<AttributeResolver> cannot be null");
+    }
+
+    /**
+     * Set the ID of an {@link IdPAttribute} to resolve to obtain revocation records for the principal.
+     * 
+     * @param id attribute ID to resolve
+     */
+    public void setAttributeId(@Nonnull @NotEmpty final String id) {
+        checkSetterPreconditions();
+
+        attributeId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Attribute ID cannot be null or empty");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (attributeResolver == null) {
+            throw new ComponentInitializationException("ReloadableService<AttributeResolver> cannot be null");
+        } else if (principalNameLookupStrategy == null) {
+            throw new ComponentInitializationException("Principal name lookup strategy cannot be null");
+        } else if (attributeId == null) {
+            throw new ComponentInitializationException("Attribute ID to resolve cannot be null or empty");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings("unchecked")
+    public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
+            @Nullable final JWTClaimsSet claimsSet) {
+        checkComponentActive();
+
+        if (profileRequestContext == null || claimsSet == null) {
+            log.error("Called with null inputs");
+            return true;
+        }
+
+        final String principal = principalNameLookupStrategy.apply(profileRequestContext, claimsSet);
+        if (principal == null) {
+            log.error("Principal lookup strategy returned null value");
+            return true;
+        }
+
+        log.debug("Checking revocation for principal name {} for token {} result via attribute resolver", principal,
+                claimsSet.getJWTID());
+
+        final ScratchContext context = profileRequestContext.ensureSubcontext(ScratchContext.class);
+
+        if (!context.getMap().containsKey(getClass())) {
+            final AttributeResolutionContext resolutionContext = buildResolutionContext(profileRequestContext,
+                    principal);
+            assert attributeResolver != null;
+            resolutionContext.resolveAttributes(attributeResolver);
+
+            final Collection<Instant> records = new ArrayList<>();
+            if (resolutionContext.getResolvedIdPAttributes().containsKey(attributeId)) {
+                for (final IdPAttributeValue value :
+                    resolutionContext.getResolvedIdPAttributes().get(attributeId).getValues()) {
+                    if (value instanceof DateTimeAttributeValue) {
+                        records.add(((DateTimeAttributeValue) value).getValue());
+                    } else if (value instanceof StringAttributeValue) {
+                        try {
+                            records.add(Instant.ofEpochSecond(Long.valueOf(((StringAttributeValue) value).getValue())));
+                        } catch (final NumberFormatException|DateTimeException e) {
+                            log.error("Error parsing timestamp '{}' into epoch",
+                                    ((StringAttributeValue) value).getValue(), e);
+                        }
+                    } else {
+                        log.warn("Ignoring non-string attribute value type: {}", value.getClass().getName());
+                    }
+                }
+            } else {
+                log.debug("Resolver did not return an IdPAttribute named {} for principal {}", attributeId, principal);
+            }
+
+            context.getMap().put(getClass(), records);
+            resolutionContext.removeFromParent();
+        }
+
+        return isRevoked(principal, claimsSet, (Collection<Instant>) context.getMap().get(getClass()));
+    }
+
+    /**
+     * Build an {@link AttributeResolutionContext} to use.
+     * 
+     * @param profileRequestContext profile request context
+     * @param principal name of principal
+     * 
+     * @return the attached context
+     */
+    @Nonnull private AttributeResolutionContext buildResolutionContext(
+            @Nonnull final ProfileRequestContext profileRequestContext, @Nonnull @NotEmpty final String principal) {
+
+        final AttributeResolutionContext resolutionContext = new AttributeResolutionContext();
+
+        resolutionContext
+            .setPrincipal(principal)
+            .setResolutionLabel("Token revocation");
+        assert attributeId != null;
+        resolutionContext.setRequestedIdPAttributeNames(CollectionSupport.singletonList(attributeId));
+
+        if (recipientLookupStrategy != null) {
+            resolutionContext.setAttributeRecipientID(recipientLookupStrategy.apply(profileRequestContext));
+        }
+
+        if (issuerLookupStrategy != null) {
+            resolutionContext.setAttributeIssuerID(issuerLookupStrategy.apply(profileRequestContext));
+        }
+
+        profileRequestContext.addSubcontext(resolutionContext, true);
+        return resolutionContext;
+    }
+
+    /**
+     * Check the revocation records' timestamps for applicability.
+     * 
+     * @param principal name of principal
+     * @param claimsSet claims set containing {@link TokenClaimsSet#KEY_AUTH_TIME}
+     * @param revocationRecords the records from the cache
+     * 
+     * @return true iff the revocation applies to this claims set
+     */
+    protected boolean isRevoked(@Nonnull @NotEmpty final String principal, @Nonnull final JWTClaimsSet claimsSet,
+            @Nonnull final Collection<Instant> revocationRecords) {
+
+        for (final Instant i : revocationRecords) {
+            final Date authTime;
+            try {
+                authTime = claimsSet.getDateClaim(TokenClaimsSet.KEY_AUTH_TIME);
+            } catch (final ParseException e) {
+                log.debug("Could not parse authentication time from the claims set for {}", principal, e);
+                log.info("Token identifier {} for principal {} is revoked: authentication time cannot be parsed",
+                        principal, claimsSet.getJWTID());
+                return true;
+            }
+            log.debug("Authentication time in token {} vs revocation record {}", authTime.toInstant(), i);
+            if (authTime.toInstant().isBefore(i)) {
+                log.info("Token identifier {} for principal {} has been revoked", claimsSet.getJWTID(),
+                        principal);
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/TokenRevocationConditionValidator.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/TokenRevocationConditionValidator.java
new file mode 100644
index 00000000..a610eb89
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/security/jwt/claims/impl/TokenRevocationConditionValidator.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.security.jwt.claims.impl;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.oidc.jwt.claims.AbstractClaimsValidator;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
+
+/**
+ * Verifies the claims set against configurable token revocation condition.
+ */
+public class TokenRevocationConditionValidator extends AbstractClaimsValidator {
+
+    /** Token revocation condition to use. */
+    @Nullable private BiPredicate<ProfileRequestContext,JWTClaimsSet> tokenRevocationCondition;
+
+    /**
+     * Set the token revocation condition to use.
+     * 
+     * @param condition token revocation condition to set
+     */
+    public void setTokenRevocationCondition(@Nullable final BiPredicate<ProfileRequestContext,JWTClaimsSet> condition) {
+        checkSetterPreconditions();
+        tokenRevocationCondition = condition;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doValidate(@Nonnull final JWTClaimsSet claims,
+            @Nullable final ProfileRequestContext profileRequestContext) throws JWTValidationException {
+
+        if (tokenRevocationCondition != null && tokenRevocationCondition.test(profileRequestContext, claims)) {
+            throw new JWTValidationException(
+                    "Token " + claims.getJWTID() + " for subject '" + claims.getSubject() + "' has been revoked");
+        }
+    }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 532f73f7..af2e2b82 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -883,4 +883,14 @@
                 p:customMetadataPolicyOperators="#{getObject('shibboleth.oidc.RedirectUriValidator.MetadataPolicyCustomOperators') ?: getObject('shibboleth.oidc.DefaultMetadataPolicyCustomOperators')}"/>
         </property>
     </bean>
+
+    <bean id="shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition"
+        class="net.shibboleth.idp.plugin.oidc.op.profile.logic.AttributeTokenRevocationCondition" lazy-init="true"
+        p:attributeResolver-ref="shibboleth.AttributeResolverService"
+        p:attributeId="#{'%{idp.oauth2.revocationCondition.attributeId:revocation}'.trim()}">
+        <property name="principalNameLookupStrategy">
+            <bean parent="shibboleth.BiFunctions.Expression" c:expression="#input2.getSubject()" />
+        </property>
+    </bean>
+
 </beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 8507a891..49d8b38f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -77,7 +77,8 @@
         c:sealer-ref="#{'%{idp.oidc.tokenSealer:shibboleth.oidc.TokenSealer}'.trim()}"
         p:replayCache-ref="shibboleth.ReplayCache"
         p:revocationCache-ref="shibboleth.oidc.RevocationCache"
-        p:refreshTokenDeserializers-ref="#{'%{idp.oauth2.refreshToken.deserializers:shibboleth.oidc.DefaultRefreshTokenDeserializers}'.trim()}">
+        p:refreshTokenDeserializers-ref="#{'%{idp.oauth2.refreshToken.deserializers:shibboleth.oidc.DefaultRefreshTokenDeserializers}'.trim()}"
+        p:tokenRevocationCondition="#{%{idp.oauth2.revocationCondition:false} == true ? getObject('%{idp.oauth2.revocationCondition.custom:shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition}') : {null} }">
         <property name="chainRevocationLifetimeLookupStrategy">
             <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultChainRevocationLifetimeLookupStrategy"
                 p:clockSkew="%{idp.policy.clockSkew:PT5M}" p:useActiveProfileOnly="false" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 94ff61cf..9f98a984 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -601,6 +601,11 @@
         p:revocationCache-ref="shibboleth.oidc.RevocationCache"
         p:context="#{T(net.shibboleth.idp.plugin.oidc.op.storage.RevocationCacheContexts).AUTHORIZATION_CODE}" />
 
+    <bean id="TokenRevocationConditionValidator"
+        class="net.shibboleth.idp.plugin.oidc.op.security.jwt.claims.impl.TokenRevocationConditionValidator"
+        p:tokenRevocationCondition="#{%{idp.oauth2.revocationCondition:false} == true ? getObject('%{idp.oauth2.revocationCondition.custom:shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition}') : {null} }">
+    </bean>
+
     <util:list id="IntrospectionClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
         <ref bean="RequiredClaimsValidator" />
         <ref bean="ExpiryClaimsValidator" />
@@ -619,6 +624,7 @@
         </bean>
         <ref bean="JWTIDRevocationClaimsValidator" />
         <ref bean="RootJWTIDRevocationClaimsValidator" />
+        <ref bean="TokenRevocationConditionValidator" />
     </util:list>
 
     <util:list id="RevocationClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
@@ -664,6 +670,7 @@
         <ref bean="OPInAudienceClaimsValidator" />
         <ref bean="JWTIDRevocationClaimsValidator" />
         <ref bean="RootJWTIDRevocationClaimsValidator" />
+        <ref bean="TokenRevocationConditionValidator" />
     </util:list>
 
     <!--
diff --git a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
index 7d602743..6462997f 100644
--- a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
@@ -198,6 +198,13 @@ idp.oidc.subject.salt = this_too_should_be_ch4ng3d
 # Revocation method: set to TOKEN to revoke single tokens (defaults to full chain (value = CHAIN))
 #idp.oauth2.revocationMethod = TOKEN
 
+# Revocation condition: activate (disabled by default) and modify the attribute resolution parameters
+#idp.oauth2.revocationCondition = false
+# Custom bean name that implements BiPredicate<ProfileRequestContext,JWTClaimsSet> for activating condition
+#idp.oauth2.revocationCondition.custom = shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition
+# The attribute ID for the shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition
+#idp.oauth2.revocationCondition.attributeId = revocation
+
 # Bean used to validate audience claim in the JWT authentication.
 #idp.oauth2.jwtAuth.audienceValidator = DefaultAuthenticationAudienceClaimsValidator
 # The default pattern also accepts token endpoint URL as the audience in introspection, revocation and PAR endpoints.
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
index b938522d..cd507a0e 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AbstractOidcApiFlowTest.java
@@ -55,6 +55,8 @@ import net.shibboleth.shared.security.DataSealerException;
  * Abstract unit test for the OIDC flows using access tokens.
  */
 public class AbstractOidcApiFlowTest extends AbstractOidcFlowTest {
+
+    String clientIdActivateRevocationCondition = "mockClientIdActivateRevocationCondition";
     
     protected AbstractOidcApiFlowTest(final String flowId) {
         super(flowId);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
index 98107a7a..5e1cdca4 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
@@ -92,6 +92,7 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         removeMetadata(storageService, clientIdDPoPProofEnforced);
         removeMetadata(storageService, clientIdEndpointAudienceDisabled);
         removeMetadata(storageService, clientIdRequireClientAuthenticationJWTType);
+        removeMetadata(storageService, clientIdActivateRevocationCondition);
     }
 
     @Test
@@ -719,7 +720,82 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         Assert.assertNull(resp.getClientID());
         Assert.assertFalse(resp.isActive());
     }
-    
+
+    @Test
+    public void testFailureWithRevokedAccessToken() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException {
+        final String clientId = clientIdActivateRevocationCondition;
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Map.of(
+                "token",
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token"),
+                "token_type",
+                "access_token"));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final TokenIntrospectionSuccessResponse resp =
+                parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+        Assert.assertFalse(resp.isActive());
+    }
+
+    @Test
+    public void testFailureWithRevokedJwtAccessToken() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        final String clientId = clientIdActivateRevocationCondition;
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Map.of(
+                "token",
+                buildJWTToken(clientId, "sub", scope, null, signingKey.getPrivateKey(), "RS256").toJSONObject()
+                    .getAsString("access_token"),
+                "token_type",
+                "access_token"));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final TokenIntrospectionSuccessResponse resp =
+                parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+        Assert.assertFalse(resp.isActive());
+    }
+
+    @Test
+    public void testFailureWithRevokedRefreshToken() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException {
+        final String clientId = clientIdActivateRevocationCondition;
+        final String rootId = idGenerator.generateIdentifier();
+        final String jti = idGenerator.generateIdentifier();
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Map.of(
+                "token",
+                buildRefreshToken(clientId, "sub", Scope.parse("openid"), null, jti, rootId).toJSONObject()
+                    .getAsString("refresh_token"),
+                "token_type",
+                "refresh_token"));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final TokenIntrospectionSuccessResponse resp =
+                parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+        Assert.assertFalse(resp.isActive());
+    }
+
+    @Test
+    public void testFailureWithRevokedJwtRefreshToken() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        final String clientId = clientIdActivateRevocationCondition;
+        final String rootId = idGenerator.generateIdentifier();
+        final String jti = idGenerator.generateIdentifier();
+        storeMetadata(storageService, clientId, clientSecret, scope);
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Map.of(
+                "token",
+                buildJwtRefreshToken(clientId, jti, rootId, Scope.parse("openid"),
+                        List.of("http://localhost/idp/profile/oidc/token"), Instant.now().plusSeconds(600), null),
+                "token_type",
+                "refresh_token"));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final TokenIntrospectionSuccessResponse resp =
+                parseSuccessResponse(result, TokenIntrospectionSuccessResponse.class);
+        Assert.assertFalse(resp.isActive());
+    }
+
     protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
             final ClientAuthenticationMethod method, final PublicKey publicKey) throws Exception {
         // use 'iss' claim from JWT as clientId if set, 'sub' otherwise
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index 18ae8094..71334a81 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -141,6 +141,7 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         removeMetadata(storageService, clientIdAlwaysBearerAccessToken);
         removeMetadata(storageService, clientIdEndpointAudienceDisabled);
         removeMetadata(storageService, clientIdRequireClientAuthenticationJWTType);
+        removeMetadata(storageService, clientIdActivateRevocationCondition);
     }
 
     @Test
@@ -1370,6 +1371,27 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertNotNull(response.getOIDCTokens().getIDToken());
     }
 
+    @Test
+    public void testRevokedAuthorizationGrant() throws Exception {
+        final String clientId = clientIdActivateRevocationCondition;
+        initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "authorization_code",
+                buildAuthorizationCode(clientId), clientId));
+        storeConsent(storageService, "jdoe", clientId, "mail");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
+    }
+
+    @Test
+    public void testRevokedRefreshTokenGrant() throws Exception {
+        final String clientId = clientIdActivateRevocationCondition;
+        final String id = idGenerator.generateIdentifier();
+        final String rootId = idGenerator.generateIdentifier();
+        initializeGrantAndRequest(clientId, createRequestParameters(redirectUri, "refresh_token",
+                buildRefreshToken(clientId, id, rootId, null), clientId));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_GRANT_CODE);
+    }
+
     protected String buildAuthorizationCode(final String clientId) throws Exception {
         return buildAuthorizationCode(clientId, null);
     }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
index ab976b4a..ab653fdc 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
@@ -93,6 +93,7 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
     @AfterMethod
     public void tearDown() throws IOException {
         removeMetadata(storageService, clientId);
+        removeMetadata(storageService, clientIdActivateRevocationCondition);
     }
 
     @Test
@@ -821,6 +822,30 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
     }
 
+    @Test
+    public void testFailsWithRevokedAccessToken() throws URISyntaxException, NoSuchAlgorithmException,
+        DataSealerException, ComponentInitializationException, IOException {
+        final String clientId = clientIdActivateRevocationCondition;
+        final BearerAccessToken token = buildToken(clientId, subject, new Scope("openid"));
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
+    }
+
+    @Test
+    public void testFailsWithRevokedJWTAccessToken() throws URISyntaxException, NoSuchAlgorithmException,
+        DataSealerException, ComponentInitializationException, IOException, com.nimbusds.oauth2.sdk.ParseException,
+        JOSEException {
+        final String clientId = clientIdActivateRevocationCondition;
+        final BearerAccessToken token = buildJWTToken(clientId, subject , new Scope("openid"), null, signingKey.getPrivateKey(), "RS256");
+
+        storeMetadata(storageService, clientId, "mockSecret", scope);
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
+    }
+
     @Factory
     public Object[] createUserInfoAsJwtSecurityTests() {
         return new Object[] {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeTokenRevocationConditionTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeTokenRevocationConditionTest.java
new file mode 100644
index 00000000..11c866a1
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/logic/AttributeTokenRevocationConditionTest.java
@@ -0,0 +1,167 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.profile.logic;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Date;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.testng.Assert;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.idp.attribute.IdPAttribute;
+import net.shibboleth.idp.attribute.StringAttributeValue;
+import net.shibboleth.idp.attribute.resolver.AttributeResolver;
+import net.shibboleth.idp.attribute.resolver.ResolutionException;
+import net.shibboleth.idp.attribute.resolver.context.AttributeResolutionContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.BiFunctionSupport;
+import net.shibboleth.shared.service.ReloadableService;
+import net.shibboleth.shared.service.ServiceableComponent;
+
+/**
+ * Unit tests for {@link AttributeTokenRevocationCondition}.
+ */
+public class AttributeTokenRevocationConditionTest {
+
+    private Collection<Instant> revocationsToResolve;
+    
+    private AttributeTokenRevocationCondition condition; 
+
+    @BeforeMethod
+    public void setUp() throws ComponentInitializationException {
+        condition = new AttributeTokenRevocationCondition();
+        condition.setPrincipalNameLookupStrategy(BiFunctionSupport.constant("jdoe"));
+        condition.setAttributeResolver(new MockResolver());
+        condition.setAttributeId("revocation");
+        condition.initialize();
+        
+    }
+    
+    @AfterMethod
+    public void tearDown() {
+        condition.destroy();
+    }
+    
+    
+    @Test
+    public void testNotRevoked() {
+        Assert.assertFalse(condition.test(new ProfileRequestContext(),
+                new JWTClaimsSet.Builder()
+                    .claim(TokenClaimsSet.KEY_AUTH_TIME, new Date())
+                    .jwtID(UUID.randomUUID().toString())
+                    .build()));
+    }
+    
+    @Test
+    public void testRevoked() {
+        revocationsToResolve = CollectionSupport.singletonList(Instant.now());
+        Assert.assertTrue(condition.test(new ProfileRequestContext(),
+                new JWTClaimsSet.Builder()
+                    .claim(TokenClaimsSet.KEY_AUTH_TIME, Date.from(Instant.now().minusSeconds(3600)))
+                    .jwtID(UUID.randomUUID().toString())
+                    .build()));
+    }
+
+    @Test
+    public void testPastRevoked() {
+        revocationsToResolve = CollectionSupport.singletonList(Instant.now().minusSeconds(3600));
+        Assert.assertFalse(condition.test(new ProfileRequestContext(),
+                new JWTClaimsSet.Builder()
+                    .claim(TokenClaimsSet.KEY_AUTH_TIME, Date.from(Instant.now()))
+                    .jwtID(UUID.randomUUID().toString())
+                    .build()));
+    }
+
+    /**
+     * Mock attribute source.
+     */
+    private class MockResolver implements ReloadableService<AttributeResolver> {
+
+        /** {@inheritDoc} */
+        public boolean isInitialized() {
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        public void initialize() throws ComponentInitializationException {            
+        }
+
+        /** {@inheritDoc} */
+        public Instant getLastSuccessfulReloadInstant() {
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public Instant getLastReloadAttemptInstant() {
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public Throwable getReloadFailureCause() {
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public void reload() {
+        }
+
+        /** {@inheritDoc} */
+        public @Nonnull ServiceableComponent<AttributeResolver> getServiceableComponent() {
+            return new ServiceableComponent<AttributeResolver>() {
+
+                public @Nonnull AttributeResolver getComponent() {
+                    return new AttributeResolver() {
+
+                        public String getId() {
+                            return "test";
+                        }
+
+                        public void resolveAttributes(@Nonnull final AttributeResolutionContext resolutionContext)
+                                throws ResolutionException {
+                            if ("jdoe".equals(resolutionContext.getPrincipal()) && revocationsToResolve != null) {
+                                final IdPAttribute attr = new IdPAttribute("revocation");
+                                attr.setValues(
+                                        revocationsToResolve.stream()
+                                            .map(i -> StringAttributeValue.valueOf(Long.toString(i.getEpochSecond())))
+                                            .collect(Collectors.toUnmodifiableList())
+                                        );
+                                resolutionContext.setResolvedIdPAttributes(CollectionSupport.singletonList(attr));
+                            }
+                        }
+                    };
+                }
+
+                public void close() {
+                }
+
+            };
+        }
+        
+    }
+    
+
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-resolver.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-resolver.xml
index 5d88aeef..2ee49ded 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-resolver.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/attribute-resolver.xml
@@ -76,6 +76,17 @@
         <AttributeEncoder xsi:type="oidc:OIDCString" name="sub" />
     </AttributeDefinition>
 
+    <AttributeDefinition id="customRevocation" xsi:type="DateTime" epochInSeconds="false">
+        <InputAttributeDefinition ref="currentTimePlusHour"/>
+    </AttributeDefinition>
+
+    <AttributeDefinition id="currentTimePlusHour" xsi:type="ScriptedAttribute" dependencyOnly="true">
+        <Script><![CDATA[
+            currentTimePlusHour.addValue((new Date().getTime() + 3600000).toString());
+            ]]>
+        </Script>
+    </AttributeDefinition>
+
     <!-- ========================================== -->
     <!--      Data Connectors                       -->
     <!-- ========================================== -->
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
index 707f855f..26c6ba84 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
@@ -12,6 +12,16 @@
        default-init-method="initialize"
        default-destroy-method="destroy">
 
+    <bean id="CustomTokenRevocationCondition" parent="shibboleth.BiConditions.Expression"
+        c:expression="'mockClientIdActivateRevocationCondition'.equals(#custom.get('rp').apply(#input1)) and #custom.get('condition').test(#input1, #input2)">
+        <property name="customObject">
+            <util:map value-type="java.lang.Object">
+                <entry key="rp"><ref bean="shibboleth.RelyingPartyIdLookup.Simple"/></entry>
+                <entry key="condition"><ref bean="shibboleth.oauth2.revocationCondition.AttributeTokenRevocationCondition"/></entry>
+            </util:map>
+        </property>
+    </bean>
+
     <util:list id="shibboleth.oidc.ClientSecretValueResolvers">
         <bean parent="shibboleth.oidc.PropertiesClientSecretValueResolver"
             p:resource="classpath:/net/shibboleth/idp/oidc/metadata/impl/client-secret-test.properties" />
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
index d2e0c884..2897761d 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc.properties
@@ -22,4 +22,7 @@ idp.oidc.discovery.resolver.values = CustomConfigurationValues
 
 idp.oidc.DefaultUnregisteredClientPolicyFile = src/test/resources/net/shibboleth/idp/module/conf/unregistered-policy.json
 
-idp.oauth2.jwtAuth.targetedEndpointAsJWTAudience = true
\ No newline at end of file
+idp.oauth2.jwtAuth.targetedEndpointAsJWTAudience = true
+idp.oauth2.revocationCondition = true
+idp.oauth2.revocationCondition.custom = CustomTokenRevocationCondition
+idp.oauth2.revocationCondition.attributeId = customRevocation
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list