jwtk/jjwt

Error building JWT with non-string audience claim type

arvindkrishnakumar-okta opened this issue ยท 4 comments

Describe the bug

If I try building a JWT with a "List" type of "aud" claim, then I see the below exception trace thrown during validation (this used to work in 0.11.x and started breaking since 0.12.x):

java.lang.IllegalArgumentException: Invalid Map 'aud' (Audience) value: [invalid-clientId, test-aud]. Unsupported value type. Expected: java.lang.String, found: java.util.ArrayList

	at io.jsonwebtoken.impl.ParameterMap.apply(ParameterMap.java:193)
	at io.jsonwebtoken.impl.ParameterMap.put(ParameterMap.java:139)
	at io.jsonwebtoken.impl.ParameterMap.put(ParameterMap.java:149)
	at io.jsonwebtoken.impl.ParameterMap.put(ParameterMap.java:36)
	at io.jsonwebtoken.impl.lang.DelegatingMap.put(DelegatingMap.java:81)
	at io.jsonwebtoken.impl.lang.DelegatingMapMutator.add(DelegatingMapMutator.java:45)
	at io.jsonwebtoken.impl.DefaultJwtBuilder.claim(DefaultJwtBuilder.java:406)
	at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
	at com.okta.jwt.impl.jjwt.JjwtAccessTokenVerifierTest.validAudienceIds(JjwtAccessTokenVerifierTest.groovy:44)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:135)
	at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:673)
	at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:220)
	at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50)
	at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:945)
	at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:193)
	at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
	at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at org.testng.TestRunner.privateRun(TestRunner.java:808)
	at org.testng.TestRunner.run(TestRunner.java:603)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:429)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:423)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:383)
	at org.testng.SuiteRunner.run(SuiteRunner.java:326)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1249)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
	at org.testng.TestNG.runSuites(TestNG.java:1092)
	at org.testng.TestNG.run(TestNG.java:1060)
	at com.intellij.rt.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:66)
	at com.intellij.rt.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:109)
Caused by: java.lang.IllegalArgumentException: Unsupported value type. Expected: java.lang.String, found: java.util.ArrayList
	at io.jsonwebtoken.impl.lang.RequiredTypeConverter.applyFrom(RequiredTypeConverter.java:44)
	at io.jsonwebtoken.impl.lang.DefaultParameter.applyFrom(DefaultParameter.java:124)
	at io.jsonwebtoken.impl.ParameterMap.apply(ParameterMap.java:176)
	... 33 more

I am actually unable to supply any type other than "String" to the claims while building the JWT.

Can you please let me know if I am missing something here? Thanks!

Expected behavior

I should be able to set List type "aud" claim as before without any issues.

Screenshots

image

image

image

Hi @arvindkrishnakumar-okta !

Because the RFC allows aud values to be either a String or a List of Strings, the JwtBuilder#setAudience method has been deprecated and replaced in JJWT 0.12.0 with an audience sub-builder to support both string and list values. For example:

Jwts.builder().audience().add(list).and()...

But it appears that you're setting this value with the generic jwtBuilder.claim method, is that correct?

I suppose we could check if the claim is named aud and the value is a collection, that the claim method delegate to the .audience().add(collection).and() method automatically?

Just trying to understand your preferences/expectations.

As a workaround, is it possible to refactor your code to call the .audience().add....and()... variant instead?

@arvindkrishnakumar-okta just letting you know that we're going to track work for a fix via #890, so I'm closing this issue in favor of that one. Please do feel free to comment here or in that issue if necessary. Thank you for a well-written issue (with screenshots!), that was super helpful!

@lhazlewood Thanks so much for your help with this one.

Actually Jwts.builder().audience().add(list).and()... worked for me for setting a list of aud claim values.

But, I want to note that I see the same issue while trying to set a Boolean type for aud claim.

java.lang.IllegalArgumentException: Invalid Map 'aud' (Audience) value: true. Unsupported value type. Expected: java.lang.String, found: java.lang.Boolean
        at io.jsonwebtoken.impl.ParameterMap.apply(ParameterMap.java:193)
        at io.jsonwebtoken.impl.ParameterMap.put(ParameterMap.java:139)
        at io.jsonwebtoken.impl.ParameterMap.put(ParameterMap.java:149)
        at io.jsonwebtoken.impl.ParameterMap.put(ParameterMap.java:36)
        at io.jsonwebtoken.impl.lang.DelegatingMap.put(DelegatingMap.java:81)
        at io.jsonwebtoken.impl.lang.DelegatingMapMutator.add(DelegatingMapMutator.java:45)
        at io.jsonwebtoken.impl.DefaultJwtBuilder.claim(DefaultJwtBuilder.java:406)
        at org.codehaus.groovy.vmplugin.v8.IndyInterface.selectMethod(IndyInterface.java:355)
        at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
        at com.okta.jwt.impl.jjwt.JjwtAccessTokenVerifierTest$_invalidAudienceIds_closure1.doCall(JjwtAccessTokenVerifierTest.groovy:56)
        at com.okta.jwt.impl.jjwt.JjwtAccessTokenVerifierTest$_invalidAudienceIds_closure1.doCall(JjwtAccessTokenVerifierTest.groovy)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:343)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:328)
        at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:279)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1006)
        at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
        at com.okta.jwt.impl.TestUtil.expect(TestUtil.groovy:29)
        at org.codehaus.groovy.vmplugin.v8.IndyInterface.fromCache(IndyInterface.java:321)
        at com.okta.jwt.impl.jjwt.JjwtAccessTokenVerifierTest.invalidAudienceIds(JjwtAccessTokenVerifierTest.groovy:55)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)
        at org.testng.internal.invokers.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:135)
        at org.testng.internal.invokers.TestInvoker.invokeMethod(TestInvoker.java:673)
        at org.testng.internal.invokers.TestInvoker.invokeTestMethod(TestInvoker.java:220)
        at org.testng.internal.invokers.MethodRunner.runInSequence(MethodRunner.java:50)
        at org.testng.internal.invokers.TestInvoker$MethodInvocationAgent.invoke(TestInvoker.java:945)
        at org.testng.internal.invokers.TestInvoker.invokeTestMethods(TestInvoker.java:193)
        at org.testng.internal.invokers.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:146)
        at org.testng.internal.invokers.TestMethodWorker.run(TestMethodWorker.java:128)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at org.testng.TestRunner.privateRun(TestRunner.java:808)
        at org.testng.TestRunner.run(TestRunner.java:603)
        at org.testng.SuiteRunner.runTest(SuiteRunner.java:429)
        at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:423)
        at org.testng.SuiteRunner.privateRun(SuiteRunner.java:383)
        at org.testng.SuiteRunner.run(SuiteRunner.java:326)
        at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
        at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:95)
        at org.testng.TestNG.runSuitesSequentially(TestNG.java:1249)
        at org.testng.TestNG.runSuitesLocally(TestNG.java:1169)
        at org.testng.TestNG.runSuites(TestNG.java:1092)
        at org.testng.TestNG.run(TestNG.java:1060)
        at org.apache.maven.surefire.testng.TestNGExecutor.run(TestNGExecutor.java:155)
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.executeMulti(TestNGDirectoryTestSuite.java:169)
        at org.apache.maven.surefire.testng.TestNGDirectoryTestSuite.execute(TestNGDirectoryTestSuite.java:88)
        at org.apache.maven.surefire.testng.TestNGProvider.invoke(TestNGProvider.java:137)
        at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:385)
        at org.apache.maven.surefire.booter.ForkedBooter.execute(ForkedBooter.java:162)
        at org.apache.maven.surefire.booter.ForkedBooter.run(ForkedBooter.java:507)
        at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:495)
Caused by: java.lang.IllegalArgumentException: Unsupported value type. Expected: java.lang.String, found: java.lang.Boolean
        at io.jsonwebtoken.impl.lang.RequiredTypeConverter.applyFrom(RequiredTypeConverter.java:44)
        at io.jsonwebtoken.impl.lang.DefaultParameter.applyFrom(DefaultParameter.java:124)
        at io.jsonwebtoken.impl.ParameterMap.apply(ParameterMap.java:176)
        ... 51 more

@arvindkrishnakumar-okta that's expected behavior. The RFC specification requires that value to be a String or a List-of-Strings. Anything else should fail because it violates the specification.