KEYCLOAK-9635 Add AccessTokenHash to IDToken for OIDC Auth Code flow
Revised tests
This commit is contained in:
parent
5a337d0376
commit
e825ec24cb
7 changed files with 56 additions and 22 deletions
|
@ -93,6 +93,13 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest
|
|||
|
||||
|
||||
protected IDToken sendTokenRequestAndGetIDToken(EventRepresentation loginEvent) {
|
||||
|
||||
OAuthClient.AccessTokenResponse response = sendTokenRequestAndGetResponse(loginEvent);
|
||||
return oauth.verifyIDToken(response.getIdToken());
|
||||
}
|
||||
|
||||
protected OAuthClient.AccessTokenResponse sendTokenRequestAndGetResponse(EventRepresentation loginEvent) {
|
||||
|
||||
String sessionId = loginEvent.getSessionId();
|
||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||
|
||||
|
@ -100,14 +107,15 @@ public abstract class AbstractTestRealmKeycloakTest extends AbstractKeycloakTest
|
|||
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
|
||||
|
||||
Assert.assertEquals(200, response.getStatusCode());
|
||||
IDToken idToken = oauth.verifyIDToken(response.getIdToken());
|
||||
|
||||
|
||||
Field eventsField = Reflections.findDeclaredField(this.getClass(), "events");
|
||||
if (eventsField != null) {
|
||||
AssertEvents events = Reflections.getFieldValue(eventsField, this, AssertEvents.class);
|
||||
events.expectCodeToToken(codeId, sessionId).assertEvent();
|
||||
}
|
||||
return idToken;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/** KEYCLOAK-12065 Inherit Client Connection from parent session **/
|
||||
|
|
|
@ -28,6 +28,7 @@ import org.keycloak.events.Details;
|
|||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.jose.jws.JWSHeader;
|
||||
import org.keycloak.jose.jws.JWSInput;
|
||||
import org.keycloak.jose.jws.crypto.HashUtils;
|
||||
import org.keycloak.representations.IDToken;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -253,11 +254,6 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc
|
|||
for (IDToken idt : idTokens) {
|
||||
Assert.assertEquals("abcdef123456", idt.getNonce());
|
||||
Assert.assertEquals(authzResponse.getSessionState(), idt.getSessionState());
|
||||
// see KEYCLOAK-9635
|
||||
if (authzResponse.getCode() != null && authzResponse.getAccessToken() != null) {
|
||||
// we have an IDToken that was obtained via auth code flow alongside an AccessToken
|
||||
Assert.assertNotNull("claim at_hash should be present in IDToken for OIDC auth code flow requests", idt.getAccessTokenHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,4 +298,29 @@ public abstract class AbstractOIDCResponseTypeTest extends AbstractTestRealmKeyc
|
|||
protected String getIdTokenSignatureAlgorithm() {
|
||||
return this.idTokenSigAlgName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate "at_hash" claim in IDToken.
|
||||
* see KEYCLOAK-9635
|
||||
* @param accessTokenHash
|
||||
* @param accessToken
|
||||
*/
|
||||
protected void assertValidAccessTokenHash(String accessTokenHash, String accessToken) {
|
||||
|
||||
Assert.assertNotNull(accessTokenHash);
|
||||
Assert.assertNotNull(accessToken);
|
||||
assertEquals(accessTokenHash, HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), accessToken));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate "c_hash" claim in IDToken.
|
||||
* @param codeHash
|
||||
* @param code
|
||||
*/
|
||||
protected void assertValidCodeHash(String codeHash, String code) {
|
||||
|
||||
Assert.assertNotNull(codeHash);
|
||||
Assert.assertNotNull(code);
|
||||
Assert.assertEquals(codeHash, HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), code));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,9 +57,13 @@ public class OIDCBasicResponseTypeCodeTest extends AbstractOIDCResponseTypeTest
|
|||
Assert.assertNull(authzResponse.getAccessToken());
|
||||
Assert.assertNull(authzResponse.getIdToken());
|
||||
|
||||
IDToken idToken = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
OAuthClient.AccessTokenResponse authzResponse2 = sendTokenRequestAndGetResponse(loginEvent);
|
||||
IDToken idToken2 = oauth.verifyIDToken(authzResponse2.getIdToken());
|
||||
|
||||
return Collections.singletonList(idToken);
|
||||
// Validate "at_hash"
|
||||
assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken());
|
||||
|
||||
return Collections.singletonList(idToken2);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -60,11 +60,11 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
|
|||
String idTokenStr = authzResponse.getIdToken();
|
||||
IDToken idToken = oauth.verifyIDToken(idTokenStr);
|
||||
|
||||
// Validate "c_hash"
|
||||
// Validate "at_hash"
|
||||
Assert.assertNull(idToken.getAccessTokenHash());
|
||||
Assert.assertNotNull(idToken.getCodeHash());
|
||||
|
||||
Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getCode()));
|
||||
// Validate "c_hash"
|
||||
assertValidCodeHash(idToken.getCodeHash(), authzResponse.getCode());
|
||||
|
||||
// Financial API - Part 2: Read and Write API Security Profile
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
|
|
|
@ -60,13 +60,11 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
|
|||
String idTokenStr = authzResponse.getIdToken();
|
||||
IDToken idToken = oauth.verifyIDToken(idTokenStr);
|
||||
|
||||
// Validate "at_hash" and "c_hash"
|
||||
Assert.assertNotNull(idToken.getAccessTokenHash());
|
||||
// Validate "at_hash"
|
||||
assertValidAccessTokenHash(idToken.getAccessTokenHash(), authzResponse.getAccessToken());
|
||||
|
||||
Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getAccessToken()));
|
||||
Assert.assertNotNull(idToken.getCodeHash());
|
||||
|
||||
Assert.assertEquals(idToken.getCodeHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getCode()));
|
||||
// Validate "c_hash"
|
||||
assertValidCodeHash(idToken.getCodeHash(), authzResponse.getCode());
|
||||
|
||||
// Financial API - Part 2: Read and Write API Security Profile
|
||||
// http://openid.net/specs/openid-financial-api-part-2.html#authorization-server
|
||||
|
@ -81,7 +79,6 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
|
|||
return Arrays.asList(idToken, idToken2);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void nonceNotUsedErrorExpected() {
|
||||
super.validateNonceNotUsedErrorExpected();
|
||||
|
|
|
@ -58,7 +58,11 @@ public class OIDCHybridResponseTypeCodeTokenTest extends AbstractOIDCResponseTyp
|
|||
Assert.assertNull(authzResponse.getIdToken());
|
||||
|
||||
// IDToken exchanged for the code
|
||||
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);
|
||||
OAuthClient.AccessTokenResponse authzResponse2 = sendTokenRequestAndGetResponse(loginEvent);
|
||||
IDToken idToken2 = oauth.verifyIDToken(authzResponse2.getIdToken());
|
||||
|
||||
// Validate "at_hash"
|
||||
assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken());
|
||||
|
||||
return Collections.singletonList(idToken2);
|
||||
}
|
||||
|
|
|
@ -60,9 +60,9 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon
|
|||
IDToken idToken = oauth.verifyIDToken(idTokenStr);
|
||||
|
||||
// Validate "at_hash"
|
||||
Assert.assertNotNull(idToken.getAccessTokenHash());
|
||||
assertValidAccessTokenHash(idToken.getAccessTokenHash(), authzResponse.getAccessToken());
|
||||
|
||||
Assert.assertEquals(idToken.getAccessTokenHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getAccessToken()));
|
||||
// Validate "c_hash"
|
||||
Assert.assertNull(idToken.getCodeHash());
|
||||
|
||||
return Collections.singletonList(idToken);
|
||||
|
|
Loading…
Reference in a new issue