[java-idp-plugin-duo] branch main updated: JDUO-28 - Move JWT Claims Validation to OIDC commons

Phil Smart philip.smart at jisc.ac.uk
Fri Jan 29 14:16:27 UTC 2021


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

philsmart pushed a commit to branch main
in repository java-idp-plugin-duo.

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

The following commit(s) were added to refs/heads/main by this push:
       new  ffd8b62   JDUO-28 - Move JWT Claims Validation to OIDC commons
ffd8b62 is described below

commit ffd8b62a2e6ecf5b1e351559e2059b45c34dd716
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jan 29 11:45:51 2021 +0000

    JDUO-28 - Move JWT Claims Validation to OIDC commons
    
    - Moved the JWT ID Token claims validation to oidc-commons.
    - Configured the set of validators.
    
    https://issues.shibboleth.net/jira/browse/JDUO-28
---
 idp-duo-api/pom.xml                                |   7 +-
 .../authn/duo/DefaultDuoOIDCIntegration.java       |   2 +-
 .../idp/plugin/authn/duo/DuoOIDCAuthAPI.java       |  55 ----
 .../authn/duo/DuoOIDCClientCapabilities.java       |   2 +-
 ...DCClientCapabilities.java => package-info.java} |  18 +-
 idp-duo-impl/pom.xml                               | 358 +++++++++++----------
 .../authn/duo/impl/BasicJWTClaimsVerifier.java     | 266 ---------------
 .../duo/impl/DefaultDuoOIDCClientRegistry.java     |   3 +-
 .../duo/impl/DuoAudienceClaimLookupStrategy.java   |  56 ++++
 .../duo/impl/DuoIssuerClaimLookupStrategy.java     |  75 +++++
 .../duo/impl/DuoNonceClaimLookupStrategy.java      |  50 +++
 .../duo/impl/DuoUsernameClaimLookupStrategy.java   |  50 +++
 .../impl/DynamicDuoContextJWTClaimsVerifier.java   | 344 --------------------
 .../plugin/authn/duo/impl/ValidateTokenClaims.java |  30 +-
 .../flows/authn/DuoOIDC/duo-oidc-authn-beans.xml   |  81 ++++-
 .../authn/duo/impl/BasicJWTClaimsVerifierTest.java | 251 ---------------
 .../impl/DuoAudienceClaimLookupStrategyTest.java   |  52 +++
 .../duo/impl/DuoIssuerClaimLookupStrategyTest.java |  54 ++++
 .../duo/impl/DuoNonceClaimLookupStrategyTest.java  |  53 +++
 .../impl/DuoUsernameClaimLookupStrategyTest.java   |  56 ++++
 .../DynamicDuoContextJWTClaimsVerifierTest.java    | 342 --------------------
 .../authn/duo/impl/ValidateTokenClaimsTest.java    |  28 +-
 idp-duo-nimbus-client-impl/pom.xml                 |  12 +-
 pom.xml                                            |  34 +-
 24 files changed, 754 insertions(+), 1525 deletions(-)

diff --git a/idp-duo-api/pom.xml b/idp-duo-api/pom.xml
index f0149e6..7373bc6 100644
--- a/idp-duo-api/pom.xml
+++ b/idp-duo-api/pom.xml
@@ -18,14 +18,13 @@
     </properties>
 
 
-    <dependencies>
-        <!-- Compile time dependencies -->
+    <dependencies>       
+        <!-- Provided dependencies -->
         <dependency>
             <groupId>com.nimbusds</groupId>
             <artifactId>nimbus-jose-jwt</artifactId>
-            <scope>compile</scope>
+            <scope>provided</scope>
         </dependency>
-        <!-- Provided dependencies -->
         <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java
index 07c8c36..ef9f0c8 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DefaultDuoOIDCIntegration.java
@@ -251,7 +251,7 @@ public class DefaultDuoOIDCIntegration extends AbstractInitializableComponent im
     /** {@inheritDoc} */
     @Override
     public String toString() {
-        StringBuilder builder = new StringBuilder();
+        final StringBuilder builder = new StringBuilder();
         builder.append("DefaultDuoOIDCIntegration [apiHost=");
         builder.append(apiHost);
         builder.append(", clientId=");
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
index e0d6f7d..c5ea760 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCAuthAPI.java
@@ -20,8 +20,6 @@ package net.shibboleth.idp.plugin.authn.duo;
 import javax.annotation.Nonnull;
 
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
 
 /**
  * Constants defined in the Duo OIDC Auth API.
@@ -56,59 +54,6 @@ public final class DuoOIDCAuthAPI {
     /** The name of the JSON result status message property.*/
     @Nonnull @NotEmpty public static final String DUO_AUTH_RESULT_STATUS_MSG_JSON_OBJECT = "status_msg";
     
-    //TODO MOVE these to a registered OIDC claims set in commons? 
-    
-    //There are others.
-    /** Core ID Token Claims, see OpenID Connect core 1.0 section 2.*/
-    @Nonnull public enum OIDC_ID_TOKEN_CLAIMS{
-        
-        /**Issuer Identifier for the Issuer of the response.*/
-        ISSUER_CLAIM ("iss"),
-        
-        /** Subject Identifier. A locally unique and never reassigned identifier within the 
-         * Issuer for the End-User.*/
-        SUBJECT_CLAIM ("sub"),
-        
-        /** Audience(s) that this ID Token is intended for.*/
-        AUDIENCE_CLAIM ("aud"),
-        
-        /**  Expiration time on or after which the ID Token MUST NOT be accepted for processing.*/
-        EXPIRATION_TIME_CLAIM ("exp"),
-        
-        /**The "nbf" (not before) claim identifies the time before which the JWT MUST NOT be accepted for processing.*/
-        NOT_BEFORE_CLAIM ("nbf"),
-        
-        /**  Time at which the JWT was issued.*/
-        ISSUED_AT_CLAIM ("iat"),
-        
-        /** Time when the End-User authentication occurred.*/
-        AUTHENTICATION_TIME("auth_time"),
-        
-        /** String value used to associate a Client session with an ID Token, and to mitigate replay attacks.*/
-        NONCE("nonce"),
-        
-        /** Authentication Context Class Reference.*/
-        AUTHENTICATION_CONTEXT_CLASS_REFERENCE("acr"),
-        
-        /** Authentication Methods References.*/
-        AUTHENTICATION_METHODS_REFERENCES("amr"),
-        
-        /** Authorized party - the party to which the ID Token was issued.*/
-        AUTHORIZED_PARTY("azp"),
-        
-        /**  The "jti" (JWT ID) claim provides a unique identifier for the JWT.*/
-        JWT_ID_CLAIM ("jti");
-        
-        @Nonnull @NotEmpty private final String claimName;
-        
-        private OIDC_ID_TOKEN_CLAIMS(@Nonnull @NotEmpty final String name) {
-            claimName = Constraint.isNotNull(StringSupport.trimOrNull(name), "Claim name can not be null or empt");
-        }
-                
-        @Nonnull public String getClaimName() {
-            return claimName;
-        }
-    }
 
     /** Constructor. */
     private DuoOIDCAuthAPI() {
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
index 9f13ac1..78b0a78 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
@@ -26,7 +26,7 @@ public interface DuoOIDCClientCapabilities {
      * <p>Does this client support the OIDC nonce parameter.</p>
      * 
      * <p>If the client does support a nonce, it <b>must</b> be included by the client in the authorisation request URL,
-     * where it should then be returned by the OP in the id_token as part of the 2FA result.</p>
+     * where it must then be returned by the OP in the id_token as part of the 2FA result.</p>
      * 
      * @return true iff the client supports the nonce parameter, false otherwise.
      */
diff --git a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/package-info.java
similarity index 62%
copy from idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
copy to idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/package-info.java
index 9f13ac1..0273347 100644
--- a/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/DuoOIDCClientCapabilities.java
+++ b/idp-duo-api/src/main/java/net/shibboleth/idp/plugin/authn/duo/package-info.java
@@ -15,21 +15,5 @@
  * limitations under the License.
  */
 
+/** Duo OIDC 2FA login flow API classes. */
 package net.shibboleth.idp.plugin.authn.duo;
-
-/**
- * Exposes capabilities of a {@link DuoOIDCClient}.
- */
-public interface DuoOIDCClientCapabilities {
-    
-    /**
-     * <p>Does this client support the OIDC nonce parameter.</p>
-     * 
-     * <p>If the client does support a nonce, it <b>must</b> be included by the client in the authorisation request URL,
-     * where it should then be returned by the OP in the id_token as part of the 2FA result.</p>
-     * 
-     * @return true iff the client supports the nonce parameter, false otherwise.
-     */
-    boolean isSupportsNonce();
-
-}
diff --git a/idp-duo-impl/pom.xml b/idp-duo-impl/pom.xml
index df07ce6..eb3d718 100644
--- a/idp-duo-impl/pom.xml
+++ b/idp-duo-impl/pom.xml
@@ -1,186 +1,196 @@
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-    <modelVersion>4.0.0</modelVersion>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
 
-    <parent>
-        <groupId>net.shibboleth.idp.plugin</groupId>
-        <artifactId>idp-plugin-duo-parent</artifactId>
-        <version>0.0.3-SNAPSHOT</version>
-    </parent>
+	<parent>
+		<groupId>net.shibboleth.idp.plugin</groupId>
+		<artifactId>idp-plugin-duo-parent</artifactId>
+		<version>0.0.3-SNAPSHOT</version>
+	</parent>
 
-    <name>Shibboleth IdP :: Plugins :: Duo 2FA Login Flow Impl</name>
-    <description>IdP Duo OIDC 2FA plugin implementation.</description>
-    <artifactId>idp-plugin-duo-impl</artifactId>
-    <packaging>jar</packaging>
+	<name>Shibboleth IdP :: Plugins :: Duo 2FA Login Flow Impl</name>
+	<description>IdP Duo OIDC 2FA plugin implementation.</description>
+	<artifactId>idp-plugin-duo-impl</artifactId>
+	<packaging>jar</packaging>
 
-    <properties>
-        <checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
-        <automatic.module.name>net.shibboleth.idp.plugin.duo.impl</automatic.module.name>
-        <apache.commons.lang.version>3.10</apache.commons.lang.version>
-    </properties>
+	<properties>
+		<checkstyle.configLocation>${project.basedir}/../checkstyle.xml</checkstyle.configLocation>
+		<automatic.module.name>net.shibboleth.idp.plugin.duo.impl</automatic.module.name>
+		<apache.commons.lang.version>3.10</apache.commons.lang.version>
+	</properties>
 
-    <dependencies>
-        <!-- compile time dependencies -->
-        <dependency>
-            <groupId>net.shibboleth.idp.plugin</groupId>
-            <artifactId>idp-plugin-duo-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.nimbusds</groupId>
-            <artifactId>nimbus-jose-jwt</artifactId>
-            <scope>compile</scope>
-        </dependency>        
+	<dependencies>
+		<!-- compile time dependencies -->
+		<dependency>
+			<groupId>net.shibboleth.idp.plugin</groupId>
+			<artifactId>idp-plugin-duo-api</artifactId>
+		</dependency>
 
-        <!-- Provided dependencies -->
-        <dependency>
-            <groupId>${idp.groupId}</groupId>
-            <artifactId>idp-session-api</artifactId>
+		<!-- Provided dependencies -->
+		<dependency>
+			<groupId>net.shibboleth.oidc</groupId>
+			<artifactId>oidc-common-crypto-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+            <groupId>net.shibboleth.oidc</groupId>
+            <artifactId>oidc-common-crypto-impl</artifactId>
             <scope>provided</scope>
         </dependency>
+		<dependency>
+			<groupId>${idp.groupId}</groupId>
+			<artifactId>idp-session-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.nimbusds</groupId>
+			<artifactId>nimbus-jose-jwt</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${idp.groupId}</groupId>
+			<artifactId>idp-authn-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${opensaml.groupId}</groupId>
+			<artifactId>opensaml-messaging-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.shibboleth.utilities</groupId>
+			<artifactId>java-support</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.findbugs</groupId>
+			<artifactId>jsr305</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>javax.servlet-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${spring-webflow.groupId}</groupId>
+			<artifactId>spring-webflow</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${opensaml.groupId}</groupId>
+			<artifactId>opensaml-profile-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
 
-        <dependency>
-            <groupId>${idp.groupId}</groupId>
-            <artifactId>idp-authn-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>${opensaml.groupId}</groupId>
-            <artifactId>opensaml-messaging-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.shibboleth.utilities</groupId>
-            <artifactId>java-support</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.findbugs</groupId>
-            <artifactId>jsr305</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>${spring-webflow.groupId}</groupId>
-            <artifactId>spring-webflow</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>${opensaml.groupId}</groupId>
-            <artifactId>opensaml-profile-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
+		<dependency>
+			<groupId>com.google.guava</groupId>
+			<artifactId>guava</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${spring.groupId}</groupId>
+			<artifactId>spring-core</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${spring.groupId}</groupId>
+			<artifactId>spring-beans</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>${idp.groupId}</groupId>
+			<artifactId>idp-profile-api</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>commons-codec</groupId>
+			<artifactId>commons-codec</artifactId>
+			<scope>provided</scope>
+		</dependency>
 
-        <dependency>
-            <groupId>com.google.guava</groupId>
-            <artifactId>guava</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>${spring.groupId}</groupId>
-            <artifactId>spring-core</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>${spring.groupId}</groupId>
-            <artifactId>spring-beans</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>${idp.groupId}</groupId>
-            <artifactId>idp-profile-api</artifactId>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-            <groupId>commons-codec</groupId>
-            <artifactId>commons-codec</artifactId>
-            <scope>provided</scope>
-        </dependency>
-
-        <!-- Test Dependencies -->
-        <dependency>
-            <groupId>${spring.groupId}</groupId>
-            <artifactId>spring-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.shibboleth.idp</groupId>
-            <artifactId>idp-conf</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.shibboleth.idp</groupId>
-            <artifactId>idp-conf-impl</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.shibboleth.idp</groupId>
-            <artifactId>idp-profile-impl</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.shibboleth.idp</groupId>
-            <artifactId>idp-authn-impl</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>net.shibboleth.idp</groupId>
-            <artifactId>idp-profile-api</artifactId>
-            <scope>test</scope>
-            <type>test-jar</type>
-        </dependency>
-        <!-- Spring webflow tests require Junit4, runs in TestNG bridge -->
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>4.12</version>
-            <scope>test</scope>
-        </dependency>
+		<!-- Test Dependencies -->
+		<dependency>
+			<groupId>${spring.groupId}</groupId>
+			<artifactId>spring-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.shibboleth.idp</groupId>
+			<artifactId>idp-conf</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.shibboleth.idp</groupId>
+			<artifactId>idp-conf-impl</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.shibboleth.idp</groupId>
+			<artifactId>idp-profile-impl</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.shibboleth.idp</groupId>
+			<artifactId>idp-authn-impl</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>net.shibboleth.idp</groupId>
+			<artifactId>idp-profile-api</artifactId>
+			<scope>test</scope>
+			<type>test-jar</type>
+		</dependency>
+		<!-- Spring webflow tests require Junit4, runs in TestNG bridge -->
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.12</version>
+			<scope>test</scope>
+		</dependency>
 
-    </dependencies>
+	</dependencies>
 
-    <build>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-compiler-plugin</artifactId>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <excludes>
-                        <exclude>system/**</exclude>
-                        <exclude>conf/**</exclude>
-                        <exclude>credentials/**</exclude>
-                    </excludes>
-                </configuration>
-            </plugin>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-jar-plugin</artifactId>
-                <configuration>
-                    <archive>
-                        <index>true</index>
-                        <manifestEntries>
-                            <Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
-                        </manifestEntries>
-                        <manifestSections>
-                            <manifestSection>
-                                <name>net/shibboleth/idp/plugin/duo</name>
-                                <manifestEntries>
-                                    <Implementation-Title>${project.artifactId}</Implementation-Title>
-                                    <Implementation-Version>${project.version}</Implementation-Version>
-                                    <Implementation-Vendor>shibboleth.net</Implementation-Vendor>
-                                </manifestEntries>
-                            </manifestSection>
-                        </manifestSections>
-                    </archive>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<excludes>
+						<exclude>system/**</exclude>
+						<exclude>conf/**</exclude>
+						<exclude>credentials/**</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+				<configuration>
+					<archive>
+						<index>true</index>
+						<manifestEntries>
+							<Automatic-Module-Name>${automatic.module.name}</Automatic-Module-Name>
+						</manifestEntries>
+						<manifestSections>
+							<manifestSection>
+								<name>net/shibboleth/idp/plugin/duo</name>
+								<manifestEntries>
+									<Implementation-Title>${project.artifactId}</Implementation-Title>
+									<Implementation-Version>${project.version}</Implementation-Version>
+									<Implementation-Vendor>shibboleth.net</Implementation-Vendor>
+								</manifestEntries>
+							</manifestSection>
+						</manifestSections>
+					</archive>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
 </project>
\ No newline at end of file
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifier.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifier.java
deleted file mode 100644
index 206a4a7..0000000
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifier.java
+++ /dev/null
@@ -1,266 +0,0 @@
-
-package net.shibboleth.idp.plugin.authn.duo.impl;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.nimbusds.jose.proc.SecurityContext;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.proc.BadJWTException;
-import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
-
-import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
-import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-import net.shibboleth.utilities.java.support.primitive.StringSupport;
-
-/**
- * <p>
- * {@link JWTClaimsSetVerifier JWT claims verifier} implementation that only checks statically configurable claims. 
- * That is, not those that are determined at runtime (for instance the issuer and audience), as these are taken 
- * from the Security Context - see the {@link DynamicDuoContextJWTClaimsVerifier}.
- * </p>
- * <p>
- * This class is thread-safe once it has been initialised.
- * </p>
- *
- * <p>
- * Performs the following checks:
- * <ol>
- * <li>Checks all required claims are present.</li>
- * <li>Checks prohibited claims are not present.</li>
- * <li>If an issued-at time (iat) claim is present, rejects tokens that were issued (iat) to far away from 
- * the current time. See section 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>If an expiration time (exp) claim is present, makes sure it is ahead of the current time, else the JWT claims set
- * is rejected. A few minutes of {@code clockSkew} is allowed. See section 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>If a not-before-time (nbf) claim is present, makes sure it is before the current time, else the JWT claims set is
- * rejected.</li>
- * </ol>
- * <p>To make JWT verification OIDC v1.0 complaint for verifying id_tokens, the set of required claims needs to contain
- * {'iss','sub','aud','exp','iat'}. This verifier does not check 'auth_time', which maybe required if requested 
- * or 'max_age' was set. 
- * </p>
- * <p>
- * This class may be extended to perform additional checks.
- * </p>
- * 
- * @param <ContextType> the {@link SecurityContext} type.
- */
- at ThreadSafeAfterInit
-public class BasicJWTClaimsVerifier<ContextType extends SecurityContext> extends AbstractInitializableComponent
-                            implements JWTClaimsSetVerifier<ContextType> {
-
-    /** 
-     * Positive clock skew adjustment to consider when checking JWT not before and expiration 
-     * (Default value: 60 seconds). 
-     */
-    @Nonnull private Duration clockSkew;
-    
-    /**
-     *  Maximum amount (in either direction from now) of duration for which a token is valid after 
-     *  it is issued (Default value: 60 seconds). 
-     */
-    @Nonnull private Duration iatWindow;
-
-    /**
-     * The names of the JWT claims that must be present, empty set if none.
-     */
-    @Nonnull @NonnullElements private Set<String> requiredClaims;
-
-    /**
-     * The names of the JWT claims that must not be present, empty set if none.
-     */ 
-    @Nonnull @NonnullElements private Set<String> prohibitedClaims;
-
-
-    /** Constructor. */
-    public BasicJWTClaimsVerifier() {        
-        clockSkew = Duration.ofSeconds(60);
-        iatWindow = Duration.ofSeconds(60);
-        requiredClaims = Collections.emptySet();
-        prohibitedClaims = Collections.emptySet();
-    }
-    
-    /**
-     * Set the clock skew.
-     * 
-     * @param skew clock skew to set
-     */
-    public void setClockSkew(@Nonnull final Duration skew) {  
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        clockSkew = Constraint.isNotNull(skew, "Clock skew cannot be null");
-    }
-    
-    /**
-     * Sets the amount of time for which a token is valid from when it was issued.
-     * 
-     * @param window amount of time for which a token is valid
-     */
-    public void setIatWindow(@Nonnull final Duration window) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        Constraint.isNotNull(window, "Token issued at window cannot be null");
-        Constraint.isFalse(window.isNegative(), "Token issued at window cannot be negative");
-
-        iatWindow = window;
-    }
-    
-    /**
-     * Set the required claims.
-     * 
-     * @param claims the required claims.
-     */
-    public void setRequiredClaims(@Nullable final Set<String> claims) {   
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        if (claims !=null) {
-            requiredClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
-        } else {
-            requiredClaims = Collections.emptySet();
-        }
-    }
-    
-    /**
-     * Set the prohibited claims.
-     * 
-     * @param claims the prohibited claims.
-     */
-    public void setProhibitedClaims(@Nullable final Set<String> claims) {   
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        if (claims !=null) {
-            prohibitedClaims = Set.copyOf(StringSupport.normalizeStringCollection(claims));
-        } else {
-            prohibitedClaims = Collections.emptySet();
-        }
-    }
-
-    @Override
-    public void verify(final JWTClaimsSet claimsSet, final ContextType context)
-            throws BadJWTException {
-
-        verifyRequiredClaims(claimsSet);
-        verifyProhibitedClaims(claimsSet);
-        verifyNotBefore(claimsSet);
-        verifyExpiration(claimsSet);
-        verifyIat(claimsSet);
-        
-    }
-    
-    /**
-     * Verify the claims set contains the given, required, claims.
-     * 
-     * @param claimsSet the claims set.
-     * 
-     * @throws BadJWTException if a required claim is not present.
-     */
-    private void verifyRequiredClaims(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
-        if (!claimsSet.getClaims().keySet().containsAll(requiredClaims)) {
-            final Set<String> missingClaims = new HashSet<>(requiredClaims);
-            missingClaims.removeAll(claimsSet.getClaims().keySet());
-            throw new BadJWTException("JWT missing required claims: " + missingClaims);
-        }
-    }
-    
-    /**
-     * Verify the claims set does not contain the given, prohibited, claims.
-     * 
-     * @param claimsSet the claims set.
-     * 
-     * @throws BadJWTException if a prohibited claim is present.
-     */
-    private void verifyProhibitedClaims(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-
-        final Set<String> presentProhibitedClaims = new HashSet<>();
-        for (final String prohibited : prohibitedClaims) {
-            if (claimsSet.getClaims().containsKey(prohibited)) {
-                presentProhibitedClaims.add(prohibited);
-            }
-            if (!presentProhibitedClaims.isEmpty()) {
-                throw new BadJWTException("JWT has prohibited claims: " + presentProhibitedClaims);
-            }
-        }
-    }
-    
-    /**
-     * If present, verifies the not-before-time has passed.
-     * 
-     * @param claimsSet the claims set.
-     * 
-     * @throws BadJWTException if the not-before-time is in the future.
-     */
-    private void verifyNotBefore(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-        
-        final Instant now = Instant.now();  
-        final Date nbf = claimsSet.getNotBeforeTime();
-        if (nbf != null) {
-            final Instant nbfInstant = nbf.toInstant();
-            final Instant nbfInstantMinusSkew = nbfInstant.minus(clockSkew);
-            if (!nbfInstantMinusSkew.isBefore(now)) {
-                throw new BadJWTException("JWT before use time");
-            }
-        }
-       
-    }
-    
-    /**
-     * If present, verifies the expiration time has not passed (including the given clock skew).
-     * 
-     * @param claimsSet the claims set.
-     * 
-     * @throws BadJWTException if the token has expired.
-     */
-    private void verifyExpiration(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-        
-        final Instant now = Instant.now();        
-
-        final Date exp = claimsSet.getExpirationTime();
-        if (exp != null) {
-            final Instant expInstant = exp.toInstant();
-            final Instant expirationPlusSkew = expInstant.plus(clockSkew);
-                            
-            if (now.isAfter(expirationPlusSkew)) {
-                throw new BadJWTException("Expired JWT");
-            }
-        }
-    }
-    
-    /**
-     * If present, verifies the IssuedAt claim exists and is within a specified window from the current time.
-     * 
-     * @param claimsSet the claims set.
-     * 
-     * @throws BadJWTException if the IssuedAt claim is invalid.
-     */
-    private void verifyIat(@Nonnull final JWTClaimsSet claimsSet) throws BadJWTException {
-        
-        final Date iatDate = claimsSet.getIssueTime();
-        if (iatDate != null) {           
-            final Instant iat = iatDate.toInstant();        
-            final Instant now = Instant.now();        
-            final Duration iatDifference = Duration.between(now, iat).abs();
-            
-            if (iatWindow.compareTo(iatDifference)  < 0) {
-                throw new BadJWTException("JWT issued-at time is too far away from the current time. "
-                        + "Token issued at '"+iat+"' was too far away from the current time '"+now+"' "
-                                + "with acceptable deviation of "
-                                + "'"+iatWindow+"', difference is '"+iatDifference+"'");
-            }
-        }
-        
-    }
-
-    
-
-}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java
index 3d2298b..417eb1e 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DefaultDuoOIDCClientRegistry.java
@@ -104,7 +104,8 @@ public class DefaultDuoOIDCClientRegistry extends AbstractIdentifiableInitializa
             //this is an atomic call, avoiding the need to synchronise here e.g. two clients should never 
             //be created for the same integration.
             final DuoOIDCClient client =  clientRegistry.computeIfAbsent(integration,clientRegistryMappingFunction);
-            log.debug("Duo registry returning the DuoClient instance '{}' of type '{}'",client.getClientId(),client.getClass().getCanonicalName());
+            log.debug("Duo registry returning the DuoClient instance '{}' of type '{}'",
+                    client.getClientId(),client.getClass().getCanonicalName());
             return client;
         } catch (final DuoClientInitializationException e) {
             throw new DuoRegistryException("DuoClient could not be found or created in the registry",e);
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategy.java
new file mode 100644
index 0000000..bc50b09
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategy.java
@@ -0,0 +1,56 @@
+/* 
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.duo.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/**
+ * Looks up the audience from the clientID in the {@link DuoOIDCIntegration} inside the context.
+ * Returns null if it fails to find the clientID. Used for JWT ID Token audience claims verification.
+ */
+public class DuoAudienceClaimLookupStrategy implements Function<ProfileRequestContext,String>{
+
+    @Override
+    public String apply(@Nonnull final ProfileRequestContext context) {
+        
+        final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+        if (authnContext == null) {
+            return null;
+        }
+
+        final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+        if (duoContext == null) {
+            return null;
+        }
+        
+        final DuoOIDCIntegration duoIntegration = duoContext.getIntegration();
+        if (duoIntegration == null) {
+            return null;
+        }
+        return duoIntegration.getClientId();
+    }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategy.java
new file mode 100644
index 0000000..6261922
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategy.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.duo.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/** 
+ * Find the issuer from the {@link DuoOIDCIntegration}. Returns null if not found.
+ */
+public class DuoIssuerClaimLookupStrategy implements Function<ProfileRequestContext, String> {
+    
+    /** HTTPS scheme protocol.*/
+    @Nonnull @NotEmpty public static final String HTTPS = "https://";
+    
+    /** The default issuer path, specific to the v1 Duo flow.*/
+    @Nonnull @NotEmpty public static final String DEFAULT_ISSUER_PATH = "/oauth/v1/token"; 
+    
+    /** The URL path component of the issuer.*/
+    @Nonnull @NotEmpty private String issuerPath;
+    
+    /**
+     * Sets the issuer URL path component.
+     * 
+     * @param path the issuer path
+     */
+    public void setIssuerPath(@Nonnull @NotEmpty final String path) {            
+        issuerPath = Constraint.isNotEmpty(path, "Issuer URL path cannot be null or empty");       
+    }
+
+    @Override
+    public String apply(@Nonnull final ProfileRequestContext context) {
+        
+        final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+        if (authnContext == null) {
+            return null;
+        }
+
+        final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+        if (duoContext == null) {
+            return null;
+        }
+        final DuoOIDCIntegration duoIntegration = duoContext.getIntegration();
+        if (duoIntegration == null) {
+            return null;
+        }
+        return HTTPS+duoIntegration.getAPIHost()+issuerPath;
+    }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategy.java
new file mode 100644
index 0000000..c765f47
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategy.java
@@ -0,0 +1,50 @@
+/* 
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.duo.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** 
+ * Find the nonce from the {@link DuoAuthenticationContext}. Returns null if not found.
+ */
+public class DuoNonceClaimLookupStrategy implements Function<ProfileRequestContext, String> {
+
+    @Override
+    public String apply(@Nonnull final ProfileRequestContext context) {
+        
+        final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+        if (authnContext == null) {
+            return null;
+        }
+
+        final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+        if (duoContext == null) {
+            return null;
+        }
+        return duoContext.getNonce();
+    }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategy.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategy.java
new file mode 100644
index 0000000..18ed4bf
--- /dev/null
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategy.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.authn.duo.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.authn.duo.context.DuoAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+
+/** 
+ * Find the authenticating principals username from the {@link DuoAuthenticationContext}. Returns null if not found.
+ */
+public class DuoUsernameClaimLookupStrategy implements Function<ProfileRequestContext, String> {
+
+    @Override
+    public String apply(@Nonnull final ProfileRequestContext context) {
+        
+        final AuthenticationContext authnContext = context.getSubcontext(AuthenticationContext.class);
+        if (authnContext == null) {
+            return null;
+        }
+
+        final DuoOIDCAuthenticationContext duoContext = authnContext.getSubcontext(DuoOIDCAuthenticationContext.class);
+        if (duoContext == null) {
+            return null;
+        }
+        return duoContext.getUsername();
+    }
+
+}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifier.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifier.java
deleted file mode 100644
index 464ba87..0000000
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifier.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.authn.duo.impl;
-
-import java.text.ParseException;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Date;
-import java.util.List;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jose.proc.SecurityContext;
-import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.proc.BadJWTException;
-import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCIntegration;
-import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-import net.shibboleth.utilities.java.support.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.utilities.java.support.component.ComponentSupport;
-import net.shibboleth.utilities.java.support.logic.Constraint;
-
-/**
- * <p>
- * {@link JWTClaimsSetVerifier JWT claims verifier} implementation. Extends the {@link BasicJWTClaimsVerifier}
- * to include claim checks constructed from values inside the {@link DuoOIDCIntegration}
- * stashed in the {@link SecurityContext} at runtime, namely; the issuer and audience.
- * </p>
- * <p>
- * This class is thread-safe once it has been initialised.
- * </p>
- *
- * <p>
- * In addition to the verification checks in the {@link BasicJWTClaimsVerifier}, verifies:
- <ul>
- * <li>The Audience (aud) claim contains the client_id of this client (as registered at the issuer). See
- * section 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>The auth_time (when the End-User authentication took place) is within a valid expiration window. Only
- * for forced authentications.</li>
- * <li>The issuer (iss) of the token exactly matches that of the configured Duo token provider. See section
- * 3.1.3.7 of OpenID Connect core 1.0.</li>
- * <li>The preferred username standard claim in the token matches the user who is currently authenticating</li>
- * <li>If present, the nonce matches that sent in the authorization request.</li>
- * </ul>
- *
- * <p>
- * This class may be extended to perform additional checks.
- * </p>
- */
- at ThreadSafeAfterInit
-public class DynamicDuoContextJWTClaimsVerifier extends BasicJWTClaimsVerifier<ProfileRequestJWTSecurityContext> {
-    
-    /** HTTPS scheme protocol.*/
-    @Nonnull @NotEmpty public static final String HTTPS = "https://";
-    
-    /** The default issuer path, specific to the v1 Duo flow.*/
-    @Nonnull @NotEmpty public static final String DEFAULT_ISSUER_PATH = "/oauth/v1/token"; 
-    
-    /** The default name of the username claim (an OIDC standard claims).*/
-    @Nonnull @NotEmpty public static final String DEFAULT_USERNAME_CLAIM = "preferred_username";
-    
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(DynamicDuoContextJWTClaimsVerifier.class);
-    
-    /** 
-     * If forced authentication, amount of time for which a token is valid 
-     * after if it was issued. (Default value: 60 seconds) 
-     */
-    @Nonnull private Duration authnLifetime;
-    
-    /** The URL path component of the issuer.*/
-    @Nonnull @NotEmpty private String issuerPath;
-    
-    /** The name of the username claim.*/
-    @Nonnull @NotEmpty private String usernameClaim;
-    
-    /** Lookup strategy to locate the Duo authentication context. */
-    @Nonnull
-    private Function<ProfileRequestContext, DuoOIDCAuthenticationContext> duoContextLookupStrategy;
-    
-    /** Lookup strategy to locate the authentication context. */
-    @Nonnull
-    private Function<ProfileRequestContext, AuthenticationContext> authenticationContextLookupStrategy;
-
-    /** Constructor.*/
-    public DynamicDuoContextJWTClaimsVerifier() {      
-        super();        
-        // prc -> ac -> dc        
-        duoContextLookupStrategy = new ChildContextLookup<>(DuoOIDCAuthenticationContext.class)
-                .compose(new ChildContextLookup<>(AuthenticationContext.class));        
-        // prc -> ac
-        authenticationContextLookupStrategy = new ChildContextLookup<>(AuthenticationContext.class);
-        authnLifetime = Duration.ofSeconds(60);
-        issuerPath = DEFAULT_ISSUER_PATH;
-        usernameClaim = DEFAULT_USERNAME_CLAIM;
-    }
-    
-    /**
-     * Set Duo authentication context lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setDuoContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,DuoOIDCAuthenticationContext> strategy) {     
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        duoContextLookupStrategy = Constraint.isNotNull(strategy, "DuoContextLookup strategy cannot be null");
-    }
-    
-    /**
-     * Set Duo authentication context lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setAuthenticationContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,AuthenticationContext> strategy) {    
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        authenticationContextLookupStrategy = Constraint.isNotNull(strategy, 
-                "AuthenticationContextLookup strategy cannot be null");
-    }
-    
-    /**
-     * Sets the amount of time for which a token is valid from when the original authentication took place.
-     * Only applies to forced authentications.
-     * 
-     * @param lifetime amount of time for which a token is valid
-     */
-    public void setAuthnLifetime(@Nonnull final Duration lifetime) {
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        Constraint.isNotNull(lifetime, "Token authentication lifetime cannot be null");
-        Constraint.isFalse(lifetime.isNegative(), "Token authentication lifetime cannot be negative");
-        
-        authnLifetime = lifetime;
-    }
-        
-    /**
-     * Sets the issuer URL path component.
-     * 
-     * @param path the issuer path
-     */
-    public void setIssuerPath(@Nonnull @NotEmpty final String path) {    
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        issuerPath = Constraint.isNotEmpty(path, "Issuer URL path cannot be null or empty");       
-    }
-    
-    /**
-     * Sets the name of the username claim.
-     * 
-     * @param claimName the username claim name
-     */
-    public void setUsernameClaim(@Nonnull @NotEmpty final String claimName) {   
-        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        
-        usernameClaim = Constraint.isNotEmpty(claimName, "Username claim name can not be null or empty");       
-    }
-    
-
-    @Override
-    public void verify(@Nonnull final JWTClaimsSet claimsSet, @Nonnull final ProfileRequestJWTSecurityContext context)
-            throws BadJWTException {
-        
-        //do basic checks first.
-        super.verify(claimsSet,context);
-        
-        //when used within the validation action, it is unlikely these could ever be null.
-        if (context == null) {
-            throw new BadJWTException("Duo claims verifier requires the ProfileRequest security context");
-        }
-
-        final AuthenticationContext authnContext = authenticationContextLookupStrategy.apply(context.getPrc());
-        if (authnContext == null) {
-            throw new BadJWTException("No authentication context returned by lookup strategy, audience and issuer"
-                    + " could not be verified");
-        }
-
-        final DuoOIDCAuthenticationContext duoContext = duoContextLookupStrategy.apply(context.getPrc());
-        if (duoContext == null) {
-            throw new BadJWTException("No Duo context returned by lookup strategy, audience and issuer"
-                    + " could not be verified");
-        }
-        
-        final DuoOIDCIntegration duoIntegration = duoContext.getIntegration();
-        if (duoIntegration == null) {
-            throw new BadJWTException("No Duo integration contained within the Duo context");
-        }
-            
-        verifyAudience(claimsSet,duoIntegration);     
-        verifyAuthenticationTime(claimsSet,authnContext);   
-        
-        //if the nonce exists in the id_token it must be validated alongside the issuer and username.
-        if (claimsSet.getClaim(DuoOIDCAuthAPI.OIDC_ID_TOKEN_CLAIMS.NONCE.getClaimName()) != null) {
-            //verify with nonce if present
-            verifyExactMatchClaims(claimsSet, new JWTClaimsSet.Builder()
-                    .issuer(HTTPS+duoContext.getIntegration().getAPIHost()+issuerPath)
-                    .claim(usernameClaim, duoContext.getUsername())
-                    .claim(DuoOIDCAuthAPI.OIDC_ID_TOKEN_CLAIMS.NONCE.getClaimName(),duoContext.getNonce())
-                    .build());           
-        } else {
-           //verify without a nonce, only issuer and username
-            verifyExactMatchClaims(claimsSet, new JWTClaimsSet.Builder()
-                    .issuer(HTTPS+duoContext.getIntegration().getAPIHost()+issuerPath)
-                    .claim(usernameClaim, duoContext.getUsername())
-                    .build());
-        }        
-    }
-
-    /**
-     * Verify the claims in <code>exactMatchClaims</code> exactly match (by Object equality) those
-     * in the <code>claimsSet</code>.
-     * 
-     * @param claimsSet the JWT claimset.
-     * @param exactMatchClaims the claims to match exactly.
-     * 
-     * @throws BadJWTException if the claim does not exist or does not match the given claim value.
-     */
-    private void verifyExactMatchClaims(@Nonnull final JWTClaimsSet claimsSet, 
-            @Nonnull final JWTClaimsSet exactMatchClaims) throws BadJWTException {
-        
-        for (final String exactMatch: exactMatchClaims.getClaims().keySet()) {
-            final Object actualClaim = claimsSet.getClaim(exactMatch);
-            if (actualClaim == null) {
-                throw new BadJWTException("Claim '"+exactMatch+"' does not exist");
-            }
-            final Object expectedClaim = exactMatchClaims.getClaim(exactMatch);
-            if (! actualClaim.equals(expectedClaim)) {
-                throw new BadJWTException("JWT \"" + exactMatch + "\" claim has value " 
-                            + actualClaim + " but should be " + expectedClaim);
-            }
-        }
-        
-    }
-    
-    /**
-     * Verify one of the audience values in the 'aud' claim matches with the Duo clientId.
-     * 
-     * @param claimsSet the JWT claimset.
-     * @param duoIntegration the Duo integration information.
-     * 
-     * @throws BadJWTException if the accepted audience (Duo ClientId) can not be found in 
-     *                          the JWT's audience list.
-     */
-    private void verifyAudience(@Nonnull final JWTClaimsSet claimsSet, 
-            @Nonnull final DuoOIDCIntegration duoIntegration) throws BadJWTException {
-        
-        // Check audience based on that specified in the duo integration
-        final String acceptedAudience = duoIntegration.getClientId();
-        
-        final List<String> audList = claimsSet.getAudience();
-        if (audList != null && !audList.isEmpty()) {
-            boolean audMatch = false;
-            for (final String aud : audList) {
-                if (acceptedAudience.equals(aud)) {
-                    audMatch = true;
-                    break;
-                }
-            }
-            if (!audMatch) {
-                throw new BadJWTException("JWT audience rejected: " + audList);
-            }
-        } else {
-            throw new BadJWTException("JWT missing required audience");
-        }
-        
-    }
-    
-    /**
-     * Verifies if the auth_time (when the End-User authentication took place) is within a 
-     * valid expiration window. Only applies to forced authentications. 
-     * 
-     * @param claimsSet the JWT claimset.
-     * @param ac the authentication context.
-     * 
-     * @throws BadJWTException if the auth_time is invalid.
-     */
-    private void verifyAuthenticationTime(@Nonnull final JWTClaimsSet claimsSet, 
-            @Nonnull final AuthenticationContext ac) throws BadJWTException {
-        
-        if (!ac.isForceAuthn()) {
-            //no forced authn, so do not validate authentication time
-            return;
-        } else {
-            //forced authn, so check authentication time
-            try { 
-                final Date authTimeDate = claimsSet.getDateClaim(DuoOIDCAuthAPI.OIDC_ID_TOKEN_CLAIMS.
-                        AUTHENTICATION_TIME.getClaimName());
-                if (authTimeDate == null) {
-                    throw new BadJWTException("No authentication time found in token");
-                }
-                final Instant authTime = authTimeDate.toInstant();
-                final Instant now = Instant.now();
-                final Instant expiration = authTime.plus(authnLifetime);
-                
-                // Check time of authentication wasn't in the future
-                if (authTime.isAfter(now)) {
-                    log.warn("Authentication forced but is not yet valid: auth_time was {}, latest valid is: {}",
-                            authTime, now);
-                    throw new BadJWTException("JWT token authentication time is not yet valid");
-                }
-
-                // Check time of authentication has not expired
-                if (expiration.isBefore(now)) {
-                    log.warn(
-                            "Authentication required (forced) but has expired: auth_time was '{}', "
-                            + "expired at: '{}', current time: '{}'",
-                            authTime, expiration, now);
-                    throw new BadJWTException("JWT token authentication time has expired");
-                }
-                //is OK.
-                
-            } catch (final ParseException e) {
-                throw new BadJWTException("Autentication forced, but no authentication time found in token",e);
-            }
-        }
-    }          
-
-}
diff --git a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java
index 5178c10..9ffbaa5 100644
--- a/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java
+++ b/idp-duo-impl/src/main/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaims.java
@@ -30,24 +30,23 @@ import org.slf4j.LoggerFactory;
 
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
-import com.nimbusds.jwt.proc.BadJWTException;
-import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.duo.AbstractDuoAuthenticationAction;
 import net.shibboleth.idp.plugin.authn.duo.DuoException;
 import net.shibboleth.idp.plugin.authn.duo.context.DuoOIDCAuthenticationContext;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
 import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * Action that validates the claims of the Duo id_token using the supplied {@link JWTClaimsSetVerifier}. The 
- * verifier <b>must</b> be thread-safe and validate the claims set against the OpenID Connect core 1.0 
- * section 3.1.3.7 specification, and those required by Duo. However, the action can not guarantee 
- * verification behaves in this way.
+ * Action that validates the claims of the Duo id_token using the supplied 
+ * {@link JWTClaimsValidation claims validator}. The verifier <b>must</b> be thread-safe and validate the 
+ * claims set against the OpenID Connect core 1.0 section 3.1.3.7 specification, and those required by Duo. 
  * 
  * @pre
  * 
@@ -87,14 +86,14 @@ public class ValidateTokenClaims extends AbstractDuoAuthenticationAction {
     /** A cleanup hook to execute after either a successful or unsuccessful claims validation. */
     @Nullable private Consumer<ProfileRequestContext> cleanupHook;
     
-    /** The JWT claims verify used to verify the claimsset.*/
-    @NonnullAfterInit private JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> claimsVerifier;
+    /** The JWT claims validator used to verify the claimsset.*/
+    @NonnullAfterInit private JWTClaimsValidation claimsValidator;
     
     /** {@inheritDoc} */
     @Override protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
 
-        if (claimsVerifier ==  null) {
+        if (claimsValidator ==  null) {
             throw new ComponentInitializationException("Duo ClaimSet Verifier cannot be null");
         }
     }
@@ -123,12 +122,13 @@ public class ValidateTokenClaims extends AbstractDuoAuthenticationAction {
     /**
      * Set the JWT claims verifier to use.
      * 
-     * @param verifier the claims verifier.
+     * @param validator the claims validator.
      */
-    public synchronized void setClaimsVerifier(
-            @Nonnull final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier) {
+    public synchronized void setClaimsValidator(
+            @Nonnull final JWTClaimsValidation validator) {
         ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
-        claimsVerifier = Constraint.isNotNull(verifier, "Claims verifier cannot be null");
+        
+        claimsValidator = Constraint.isNotNull(validator, "Claims validator cannot be null");
     }
     
     @Override
@@ -165,11 +165,11 @@ public class ValidateTokenClaims extends AbstractDuoAuthenticationAction {
         log.debug("{} Validating token claims for subject '{}'",getLogPrefix(),claimsSet.getSubject());
          
         try {
-            claimsVerifier.verify(claimsSet, new ProfileRequestJWTSecurityContext(profileRequestContext));
+            claimsValidator.validate(claimsSet,profileRequestContext);
             if (cleanupHook != null) {
                 cleanupHook.accept(profileRequestContext);
             }
-        } catch (final BadJWTException e) {
+        } catch (final JWTValidationException e) {
             log.error("{} Token verification failed for subject '{}'", getLogPrefix(),claimsSet.getSubject(),e);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             if (cleanupHook != null) {
diff --git a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
index f536054..7db4f8b 100644
--- a/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
+++ b/idp-duo-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/DuoOIDC/duo-oidc-authn-beans.xml
@@ -9,7 +9,6 @@
 
     default-init-method="initialize" default-destroy-method="destroy">
 
-    <!-- For testing, this properties placeholder does not have access to the same set of mock properties defined in SWF tests -->
     <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer" p:placeholderPrefix="%{"
         p:placeholderSuffix="}" />
 
@@ -44,8 +43,8 @@
         class="net.shibboleth.idp.plugin.authn.duo.impl.DefaultDuoOIDCClientRegistry"
         p:clientFactory-ref="%{idp.duo.oidc.clientFactoryBean:shibboleth.authn.DuoOIDC.sdk.clientFactory}" />
 
-    <!-- Load all (or none) factory bean definitions from the classpath. The actual factory bean to use is defined in the
-    client registry and overridable by the property idp.duo.oidc.clientFactoryBean -->
+    <!-- Load all (or none) factory bean definitions from the classpath. The actual factory bean to use is 
+    defined in the client registry and overridable by the property idp.duo.oidc.clientFactoryBean -->
     <import resource="classpath*:/net/shibboleth/idp/plugin/authn/duo/duo-client-factory-bean.xml" />
 
     <!-- Can override one or more of the beans above. Note, the property override is mostly to allow tests
@@ -74,17 +73,75 @@
     <bean id="ValidateTokenClaims" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.duo.impl.ValidateTokenClaims" 
         p:cleanupHook="#{getObject('shibboleth.authn.DuoOIDC.jwt.claims.CleanUpHook') ?: getObject('shibboleth.authn.DuoOIDC.jwt.claims.DefaultCleanupHook')}"
-        p:claimsVerifier="#{getObject('shibboleth.authn.DuoOIDC.DuoTokenClaimsVerifier') ?: getObject('shibboleth.authn.DuoOIDC.DefaultDuoTokenClaimsVerifier')}"/>
+        p:claimsValidator="#{getObject('shibboleth.authn.DuoOIDC.DuoTokenClaimsVerifier') ?: getObject('shibboleth.authn.DuoOIDC.DefaultDuoTokenClaimsVerifier')}"/>
 
+    <!-- OIDC claims verification -->
     <bean id="shibboleth.authn.DuoOIDC.DefaultDuoTokenClaimsVerifier"
-        class="net.shibboleth.idp.plugin.authn.duo.impl.DynamicDuoContextJWTClaimsVerifier"
-        p:clockSkew="%{idp.duo.oidc.jwt.verifier.clockSkew:PT60S}"
-        p:iatWindow="%{idp.duo.oidc.jwt.verifier.iatWindow:PT60S}"
-        p:authnLifetime="%{idp.duo.oidc.jwt.verifier.authLifetime:PT60S}"
-        p:issuerPath="%{idp.duo.oidc.jwt.verifier.issuerPath:/oauth/v1/token}"
-        p:usernameClaim="%{idp.duo.oidc.jwt.verifier.preferredUsername:preferred_username}"
-        p:requiredClaims="#{getObject('shibboleth.authn.DuoOIDC.RequiredOIDCClaims') ?: getObject('shibboleth.authn.DuoOIDC.DefaultRequiredOIDCClaims')}"
-        />
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidation">
+        <property name="claimValidators">
+            <list>
+            <bean id="requiredClaimsValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+                p:requiredClaims="#{getObject('shibboleth.authn.DuoOIDC.RequiredOIDCClaims') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.DefaultRequiredOIDCClaims')}"/>                                
+            <bean id="issuerClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+                p:claimName="iss"
+                p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.IssuerLookupStrategy') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.jwt.DefaultIssuerLookupStrategy')}"/>              
+            <bean id="audienceClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator"
+                p:audienceLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.AudienceLookupStrategy') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.jwt.DefaultAudienceLookupStrategy')}"/>
+            <bean id="notBeforeClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.NotBeforeClaimsValidator"
+                p:clockSkew="%{idp.duo.oidc.jwt.verifier.clockSkew:PT60S}"/>
+            <bean id="expiryClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+                p:clockSkew="%{idp.duo.oidc.jwt.verifier.clockSkew:PT60S}"/>
+             <bean id="issuedAtClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+                p:iatWindow="%{idp.duo.oidc.jwt.verifier.iatWindow:PT60S}"/>
+             <bean id="usernameClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+                p:claimName="%{idp.duo.oidc.jwt.verifier.preferredUsername:preferred_username}"
+                p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.UsernameLookupStrategy') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.jwt.DefaultUsernameLookupStrategy')}"/>
+              <!-- is always returned by Duo, so no need to evaluate if it was requested -->
+             <bean id="authenticationTimeClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.AuthenticationTimeClaimsValidator"
+                p:authnLifetime="%{idp.duo.oidc.jwt.verifier.authLifetime:PT60S}"
+                p:activationCondition="#{getObject('shibboleth.authn.DuoOIDC.jwt.AuthTimeActivationCondition') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.jwt.DefaultAuthTimeActivationCondition')}"/>
+             <bean id="nonceClaimValidator" 
+                class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+                p:claimName="nonce"
+                p:valueToMatchLookupStrategy="#{getObject('shibboleth.authn.DuoOIDC.jwt.NonceLookupStrategy') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.jwt.DefaultNonceLookupStrategy')}"
+                p:activationCondition="#{getObject('shibboleth.authn.DuoOIDC.jwt.NonceActivationCondition') ?: 
+                                getObject('shibboleth.authn.DuoOIDC.jwt.DefaultNonceActivationCondition')}"/>
+            </list>
+        </property>    
+    </bean> 
+    
+    <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultAuthTimeActivationCondition"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ForcedAuthenticationActivationCondition"/> 
+    
+    <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultNonceActivationCondition"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.NonceValidationActiviationCondition"/> 
+    
+    <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultIssuerLookupStrategy"
+        class="net.shibboleth.idp.plugin.authn.duo.impl.DuoIssuerClaimLookupStrategy"
+        p:issuerPath="%{idp.duo.oidc.jwt.verifier.issuerPath:/oauth/v1/token}"/>
+        
+    <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultNonceLookupStrategy"
+        class="net.shibboleth.idp.plugin.authn.duo.impl.DuoNonceClaimLookupStrategy"/>
+    
+    <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultUsernameLookupStrategy"
+        class="net.shibboleth.idp.plugin.authn.duo.impl.DuoUsernameClaimLookupStrategy"/>
+    
+    <bean id="shibboleth.authn.DuoOIDC.jwt.DefaultAudienceLookupStrategy" 
+        class="net.shibboleth.idp.plugin.authn.duo.impl.DuoAudienceClaimLookupStrategy"/>    
      
     <!-- These represent the default set of id_token claims which are **required** by OIDC -->   
     <util:set id="shibboleth.authn.DuoOIDC.DefaultRequiredOIDCClaims">
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifierTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifierTest.java
deleted file mode 100644
index fcd8d0d..0000000
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/BasicJWTClaimsVerifierTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package net.shibboleth.idp.plugin.authn.duo.impl;
-
-import java.text.ParseException;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-import java.util.Set;
-
-import javax.annotation.Nonnull;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.nimbusds.jwt.proc.BadJWTException;
-
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
-
-/**
- * Tests for the {@link BasicJWTClaimsVerifier}.
- */
-public class BasicJWTClaimsVerifierTest  extends AbstractDuoActionTest{
-    
-    /** The verifier to test.*/
-    @Nonnull private BasicJWTClaimsVerifier<ProfileRequestJWTSecurityContext> verifier;
-    
-    @BeforeMethod
-    public void setUp() throws Exception {
-        super.setup();
-        verifier = new BasicJWTClaimsVerifier<ProfileRequestJWTSecurityContext>();
-        
-    }
-
-    @Test(expectedExceptions = UnmodifiableComponentException.class)
-    public final void testSetupAfterInit() throws ComponentInitializationException {
-        verifier.initialize();
-        verifier.setClockSkew(Duration.ofMillis(100));
-    }
-    
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testNotBeforeTimeInFuture() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), Instant.now().plus(Duration.ofMinutes(2)),
-                "api.duosecurity.com", "duo_push"));       
-        
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-        
-    }
-    
-    @Test
-    public final void testNotBeforeTimeInFutureButWithinSkew() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        verifier.setClockSkew(Duration.ofMinutes(3));
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), Instant.now().plus(Duration.ofMinutes(2)),
-                "api.duosecurity.com", "duo_push"));       
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-        
-    }
-    
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testNotBeforeTimeInFutureBeyondSkew() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        verifier.setClockSkew(Duration.ofMinutes(1));
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), Instant.now().plus(Duration.ofMinutes(2)),
-                "api.duosecurity.com", "duo_push"));       
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-        
-    }
-    
-    @Test
-    public final void testNotBeforeTimeInPast() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), Instant.now().minus(Duration.ofMinutes(1)),
-                "api.duosecurity.com", "duo_push"));       
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-        
-    }
-    
-    /**
-     * Test the nbf time is equal to the current time - which should be allowed per JWT spec (rfc7519)
-     * 
-     * @throws BadJWTException on error.
-     * @throws ParseException on error.
-     */
-    @Test
-    public final void testNotBeforeTimeIsNow() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), Instant.now(),
-                "api.duosecurity.com", "duo_push"));       
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-        
-    }
-    
-    /**
-     * Validate an expired token.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testExpiredToken() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().minus(10,ChronoUnit.MINUTES),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * Validate an expired token but with a large clock skew.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error.
-     */
-    @Test
-    public final void testExpiredTokenLargeClockSkew() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        //set clock skew
-        verifier.setClockSkew(Duration.ofMinutes(20));
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now().minus(10,ChronoUnit.MINUTES),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    @Test
-    public final void testAllRequiredOIDCClaimsPresent() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-       
-        verifier.setRequiredClaims(Set.of("iss","exp","sub","aud","iat"));
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testAllRequiredOIDCClaimsPresentSomeMissing() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-       
-        verifier.setRequiredClaims(Set.of("iss","exp","sub","aud","iat"));
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",
-                Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-
-    @Test
-    public final void testRequireClaimsValid() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-       
-        verifier.setRequiredClaims(Set.of("iss","exp","sub"));
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testRequireClaimsInvalid() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-       
-        verifier.setRequiredClaims(Set.of("iss","exp","sub","not_in_jwt"));
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testProhibitedClaims() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-       
-        verifier.setProhibitedClaims(Set.of("iss"));
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    @Test
-    public final void testProhibitedClaimsDoNotExist() throws Exception {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-       
-        //should be fine, as not in JWT
-        verifier.setProhibitedClaims(Set.of("not_in_jwt"));
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        verifier.initialize();
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-
-}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategyTest.java
new file mode 100644
index 0000000..b0d0ce3
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoAudienceClaimLookupStrategyTest.java
@@ -0,0 +1,52 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoAudienceClaimLookupStrategy}.
+ */
+public class DuoAudienceClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+    /** The strategy.*/
+    @Nonnull private DuoAudienceClaimLookupStrategy strategy;
+
+    /**
+     * Setup. 
+     * 
+     * @throws ComponentInitializationException on failure to initialise a component.
+     */
+    @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+        super.setup();
+        strategy = new DuoAudienceClaimLookupStrategy();
+    }
+
+    @Test
+    public void applySuccess() {
+        addDuoContext();
+        addDuoIntegrationToContext();
+        final String audience = strategy.apply(prc);
+        assertEquals(audience, dc.getIntegration().getClientId());
+    }
+    
+    @Test
+    public void applyNoDuoContext() {
+        final String audience = strategy.apply(prc);
+        assertEquals(audience, null);
+    }
+    
+    @Test
+    public void applyNoDuoIntegration() {
+        addDuoContext();
+        final String audience = strategy.apply(prc);
+        assertEquals(audience, null);
+    }
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategyTest.java
new file mode 100644
index 0000000..b6ad8cd
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoIssuerClaimLookupStrategyTest.java
@@ -0,0 +1,54 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoIssuerLookupStrategy}.
+ */
+public class DuoIssuerClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+    /** The strategy.*/
+    @Nonnull private DuoIssuerClaimLookupStrategy strategy;
+
+    /**
+     * Setup. 
+     * 
+     * @throws ComponentInitializationException on failure to initialise a component.
+     */
+    @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+        super.setup();
+        strategy = new DuoIssuerClaimLookupStrategy();
+    }
+
+    @Test
+    public void applySuccess() {
+        addDuoContext();
+        addDuoIntegrationToContext();
+        //set a different path for testing.
+        strategy.setIssuerPath("/oauth/v2/token");
+        final String issuer = strategy.apply(prc);
+        assertEquals(issuer, "https://"+dc.getIntegration().getAPIHost()+"/oauth/v2/token");
+    }
+    
+    @Test
+    public void applyNoDuoContext() {
+        final String issuer = strategy.apply(prc);
+        assertEquals(issuer, null);
+    }
+    
+    @Test
+    public void applyNoDuoIntegration() {
+        addDuoContext();
+        final String issuer = strategy.apply(prc);
+        assertEquals(issuer, null);
+    }
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategyTest.java
new file mode 100644
index 0000000..c998253
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoNonceClaimLookupStrategyTest.java
@@ -0,0 +1,53 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoNonceClaimLookupStrategy}.
+ */
+public class DuoNonceClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+    /** The strategy.*/
+    @Nonnull private DuoNonceClaimLookupStrategy strategy;
+
+    /**
+     * Setup. 
+     * 
+     * @throws ComponentInitializationException on failure to initialise a component.
+     */
+    @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+        super.setup();
+        strategy = new DuoNonceClaimLookupStrategy();
+    }
+
+    @Test
+    public void applySuccess() {
+        addDuoContext();
+        addDuoIntegrationToContext();
+        dc.setNonce("testnonce");
+        final String nonce = strategy.apply(prc);
+        assertEquals(nonce, "testnonce");
+    }
+    
+    @Test
+    public void applyNoDuoContext() {
+        final String nonce = strategy.apply(prc);
+        assertEquals(nonce, null);
+    }
+    
+    @Test
+    public void applyNoDuoIntegration() {
+        addDuoContext();
+        final String nonce = strategy.apply(prc);
+        assertEquals(nonce, null);
+    }
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategyTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategyTest.java
new file mode 100644
index 0000000..626b085
--- /dev/null
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DuoUsernameClaimLookupStrategyTest.java
@@ -0,0 +1,56 @@
+
+package net.shibboleth.idp.plugin.authn.duo.impl;
+
+import org.testng.annotations.Test;
+
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import static org.testng.Assert.assertEquals;
+
+import javax.annotation.Nonnull;
+
+import org.testng.annotations.BeforeMethod;
+
+/**
+ * Test for the {@link DuoUsernameClaimLookupStrategy}.
+ */
+public class DuoUsernameClaimLookupStrategyTest extends AbstractDuoActionTest{
+
+    /** The strategy.*/
+    @Nonnull private DuoUsernameClaimLookupStrategy strategy;
+
+    /**
+     * Setup. 
+     * 
+     * @throws ComponentInitializationException on failure to initialise a component.
+     */
+    @BeforeMethod public void beforeMethod() throws ComponentInitializationException {
+        super.setup();
+        strategy = new DuoUsernameClaimLookupStrategy();
+    }
+
+    @Test
+    public void applySuccess() {
+        addDuoContext();
+        addDuoIntegrationToContext();
+        dc.setUsername("username");
+        final String username = strategy.apply(prc);
+        assertEquals(username, "username");
+    }
+    
+    @Test
+    public void applyNoUsername() {
+        addDuoContext();
+        addDuoIntegrationToContext();
+        dc.setUsername(null);
+        final String username = strategy.apply(prc);
+        assertEquals(username, null);
+    }
+    
+    @Test
+    public void applyNoDuoContext() {
+        final String username = strategy.apply(prc);
+        assertEquals(username, null);
+    }
+    
+}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifierTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifierTest.java
deleted file mode 100644
index d68e912..0000000
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/DynamicDuoContextJWTClaimsVerifierTest.java
+++ /dev/null
@@ -1,342 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.authn.duo.impl;
-
-import java.text.ParseException;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.temporal.ChronoUnit;
-
-import javax.annotation.Nonnull;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import com.nimbusds.jwt.JWT;
-import com.nimbusds.jwt.proc.BadJWTException;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
-import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
-import net.shibboleth.utilities.java.support.component.UnmodifiableComponentException;
-
-public class DynamicDuoContextJWTClaimsVerifierTest extends AbstractDuoActionTest{
-    
-    @Nonnull private DynamicDuoContextJWTClaimsVerifier verifier;
-    
-    @BeforeMethod
-    public void setUp() throws Exception {
-        super.setup();
-        verifier = new DynamicDuoContextJWTClaimsVerifier();
-        
-    }
-    
-    /**
-     * Test component can not be altered after initialisation
-     * 
-     * @throws ComponentInitializationException on error.
-     */
-    @Test(expectedExceptions = UnmodifiableComponentException.class)
-    public final void testSetupAfterInit() throws ComponentInitializationException {
-        verifier.initialize();
-        verifier.setIssuerPath("/test");
-    }
-
-    
-    /**
-     * Test blank duo integration.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testBlankDuoIntegration() throws BadJWTException, ParseException {
-        addDuoContext();
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    
-    /**
-     * Blank the Authentication Context, this is fatal and should result in a bad JWT exception.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testBlankAuthenticationContext() throws BadJWTException, ParseException {
-       
-        final JWT duoToken = createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES), 
-                "api.duosecurity.com", "duo_push");
-        
-        //blank Authentication Context, should throw.
-        prc.removeSubcontext(AuthenticationContext.class);
-     
-        verifier.verify(duoToken.getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    /**
-     * Blank the Duo Context, this is fatal and should result in a bad JWT exception.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testBlankDuoContext() throws BadJWTException, ParseException {
-       
-        final JWT duoToken = createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES), 
-                "api.duosecurity.com", "duo_push");
-     
-        verifier.verify(duoToken.getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    /**
-     * Test if no security context is input - and is required.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testBlankSecurityContext() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "incorrect-issuer", "duo_push"));
-     
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), null);
-    }
-    
-    /**
-     * Validate a token which has the incorrect issuer.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidIssuer() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "incorrect-issuer", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * If the username field does not exist in the claimsset, expect and error.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testClaimDoesNotExist() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        
-        //set the preferred name to a claim that does not exist
-        verifier.setUsernameClaim("not_a_claim");
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    /**
-     * Validate a token which has the incorrect audience.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidAudience() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,
-                "Login Succesful","wrong-client-audience", 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    
-    
-    /**
-     * Validate a token which has the incorrect subject.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidSubject() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        
-        //override subject
-        dc.setUsername("wrong-subject");
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * Validate a token which was issued too far in the past.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidIssuedAtPast() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now().minus(10,ChronoUnit.MINUTES), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * Validate a token which was issued too far in the future.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidIssuedAtFuture() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now().plus(10,ChronoUnit.MINUTES), Instant.now(), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * Validate a token which was outside (in the future)
-     * the authentication lifetime window when using forced authn.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidAuthenticationTimeFuture() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        ac.setForceAuthn(true);
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().plus(10,ChronoUnit.MINUTES), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * Validate a token which was outside (expired) the authentication lifetime window when using forced authn.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error. Expected.
-     */
-    @Test(expectedExceptions = BadJWTException.class)
-    public final void testInvalidAuthenticationTimePast() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        ac.setForceAuthn(true);
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-
-    }
-    
-    /**
-     * Validate a token which is inside the authentication lifetime window when using forced authn
-     * even though it was performed 2 minutes in the past.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error.
-     */
-    @Test
-    public final void testValidAuthenticationWithinWindow() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        ac.setForceAuthn(true);
-        //set a lifetime
-        verifier.setAuthnLifetime(Duration.ofMinutes(5));
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().minus(2,ChronoUnit.MINUTES), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    /**
-     * Validate a token which is outside the authentication lifetime window but authn is not forced.
-     * 
-     * @throws ParseException on error.
-     * @throws BadJWTException on error.
-     */
-    @Test
-    public final void testInvalidAuthenticationOutsideWindowNotForced() throws BadJWTException, ParseException {
-        addDuoContext();
-        addDuoIntegrationToContext();
-        addAttemptedFlow("authn/DuoOIDC");
-        ac.setForceAuthn(false);
-        dc.setAuthToken(createPlainDummyToken(DuoOIDCAuthAPI.DUO_AUTH_RESULT_ALLOW,"Login Succesful",CLIENT_ID, 
-                Instant.now(),Instant.now(), Instant.now().minus(10,ChronoUnit.MINUTES), 
-                "api.duosecurity.com", "duo_push"));
-        
-        verifier.verify(dc.getAuthToken().getJWTClaimsSet(), new ProfileRequestJWTSecurityContext(prc));
-    }
-    
-    
-    
-    
-
-}
diff --git a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java
index b4685b9..e6c9d31 100644
--- a/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java
+++ b/idp-duo-impl/src/test/java/net/shibboleth/idp/plugin/authn/duo/impl/ValidateTokenClaimsTest.java
@@ -49,6 +49,8 @@ import com.nimbusds.jwt.proc.JWTClaimsSetVerifier;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.duo.DuoOIDCAuthAPI;
+import net.shibboleth.oidc.jwt.claims.JWTClaimsValidation;
+import net.shibboleth.oidc.jwt.claims.JWTValidationException;
 import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
 
 /**
@@ -76,9 +78,9 @@ public class ValidateTokenClaimsTest extends AbstractDuoActionTest {
     public final void testValidToken() throws ComponentInitializationException {
         
         //default mock will just return
-        final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier = 
-                Mockito.mock(JWTClaimsSetVerifier.class);       
-        action.setClaimsVerifier(verifier);
+        final JWTClaimsValidation verifier = 
+                Mockito.mock(JWTClaimsValidation.class);       
+        action.setClaimsValidator(verifier);
         
         addDuoContext();
         addDuoIntegrationToContext();
@@ -100,14 +102,16 @@ public class ValidateTokenClaimsTest extends AbstractDuoActionTest {
      * @throws BadJWTException on error.
      */
     @Test
-    public final void testInValidToken() throws ComponentInitializationException, BadJWTException {
+    public final void testInValidToken() throws ComponentInitializationException, JWTValidationException {
         
         //default mock will always throw a validation exception
-        final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier = 
-                Mockito.mock(JWTClaimsSetVerifier.class); 
-        Mockito.doThrow(new BadJWTException("Invalid JWT")).when(verifier).
-                verify(Mockito.any(JWTClaimsSet.class), Mockito.any());
-        action.setClaimsVerifier(verifier);
+       //default mock will just return
+        final JWTClaimsValidation verifier = 
+                Mockito.mock(JWTClaimsValidation.class);     
+       
+        Mockito.doThrow(new JWTValidationException("Invalid JWT")).when(verifier).
+                validate(Mockito.any(JWTClaimsSet.class), Mockito.any());
+        action.setClaimsValidator(verifier); 
         
         addDuoContext();
         addDuoIntegrationToContext();
@@ -131,9 +135,9 @@ public class ValidateTokenClaimsTest extends AbstractDuoActionTest {
     public final void testBlankJWT() throws ComponentInitializationException {
         
         //default mock will just return
-        final JWTClaimsSetVerifier<ProfileRequestJWTSecurityContext> verifier = 
-                Mockito.mock(JWTClaimsSetVerifier.class);       
-        action.setClaimsVerifier(verifier);
+        final JWTClaimsValidation verifier = 
+                Mockito.mock(JWTClaimsValidation.class);         
+        action.setClaimsValidator(verifier); 
         
         addDuoContext();
         addDuoIntegrationToContext();
diff --git a/idp-duo-nimbus-client-impl/pom.xml b/idp-duo-nimbus-client-impl/pom.xml
index 80077ad..e648999 100644
--- a/idp-duo-nimbus-client-impl/pom.xml
+++ b/idp-duo-nimbus-client-impl/pom.xml
@@ -22,12 +22,7 @@
         <dependency>
             <groupId>net.shibboleth.idp.plugin</groupId>
             <artifactId>idp-plugin-duo-api</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.nimbusds</groupId>
-            <artifactId>nimbus-jose-jwt</artifactId>
-            <scope>compile</scope>
-        </dependency>
+        </dependency>      
 
         <!-- provided dependencies -->
         <dependency>
@@ -45,6 +40,11 @@
             <artifactId>httpcore</artifactId>
             <scope>provided</scope>
         </dependency>
+         <dependency>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
+            <scope>provided</scope>
+        </dependency>
 
         <!-- Needed for module/plugin impls. -->
         <dependency>
diff --git a/pom.xml b/pom.xml
index 62d15ca..4a448a4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -26,8 +26,8 @@
         <opensaml.groupId>org.opensaml</opensaml.groupId>
         <opensaml.version>4.1.0-SNAPSHOT</opensaml.version>
         <java-support.version>8.2.0-SNAPSHOT</java-support.version>
-        <odic.common.crypto.version>0.0.1-SNAPSHOT</odic.common.crypto.version>
-        <nimbus.version>9.0</nimbus.version>
+        <odic.common.crypto.version>0.0.1-SNAPSHOT</odic.common.crypto.version>        
+        <oidc.common.version>0.0.1-SNAPSHOT</oidc.common.version>
         <checkstyle.configLocation>${project.basedir}/checkstyle.xml</checkstyle.configLocation>
     </properties>
 
@@ -93,6 +93,14 @@
                 <type>pom</type>
                 <scope>import</scope>
             </dependency>
+            <!-- OIDC Common BOM when importing OIDC dependencies -->
+             <dependency>
+                <groupId>net.shibboleth.oidc</groupId>
+                <artifactId>oidc-common-parent</artifactId>
+                <version>${oidc.common.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
             <!-- Provided support dependencies -->
             <dependency>
                 <groupId>net.shibboleth.utilities</groupId>
@@ -111,28 +119,6 @@
                 <artifactId>idp-plugin-duo-api</artifactId>
                 <version>${project.version}</version>
             </dependency>
-            <dependency>
-                <groupId>net.shibboleth.idp.plugin</groupId>
-                <artifactId>idp-plugin-duo-native-client-impl</artifactId>
-                <version>${project.version}</version>
-            </dependency>
-            <!-- commons OIDC -->
-            <dependency>
-                <groupId>net.shibboleth.oidc</groupId>
-                <artifactId>oidc-common-crypto-api</artifactId>
-                <version>${odic.common.crypto.version}</version>
-            </dependency>
-            <dependency>
-                <groupId>net.shibboleth.oidc</groupId>
-                <artifactId>oidc-common-crypto-impl</artifactId>
-                <version>${odic.common.crypto.version}</version>
-            </dependency>
-            <!-- review this dep when we move to central oidc-commons-lib -->
-            <dependency>
-                <groupId>com.nimbusds</groupId>
-                <artifactId>nimbus-jose-jwt</artifactId>
-                <version>${nimbus.version}</version>
-            </dependency>
             <!-- test bom dependencies -->
             <dependency>
                 <groupId>${idp.groupId}</groupId>

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


More information about the commits mailing list