[KEYCLOAK-17484] OIDC Conformance - Authorization response with Hybrid flow does not contain token_type (#7872)

* [KEYCLOAK-17484] fix oidc conformance for hybrid-flow

* [KEYCLOAK-17484] add TokenType & ExpiresIn to OAuth2Constants

* [KEYCLOAK-17484] add request validation for oidc-flows automated tests
This commit is contained in:
sma1212 2021-03-30 08:59:30 +02:00 committed by GitHub
parent 590ee1b1a2
commit e10f3b3672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 57 additions and 7 deletions

View file

@ -1099,7 +1099,7 @@
supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status']; supportedParams = ['access_token', 'token_type', 'id_token', 'state', 'session_state', 'expires_in', 'kc_action_status'];
break; break;
case 'hybrid': case 'hybrid':
supportedParams = ['access_token', 'id_token', 'code', 'state', 'session_state', 'kc_action_status']; supportedParams = ['access_token', 'token_type', 'id_token', 'code', 'state', 'session_state', 'expires_in', 'kc_action_status'];
break; break;
} }

View file

@ -46,6 +46,10 @@ public interface OAuth2Constants {
String ACCESS_TOKEN = "access_token"; String ACCESS_TOKEN = "access_token";
String TOKEN_TYPE = "token_type";
String EXPIRES_IN = "expires_in";
String ID_TOKEN = "id_token"; String ID_TOKEN = "id_token";
String REFRESH_TOKEN = "refresh_token"; String REFRESH_TOKEN = "refresh_token";

View file

@ -266,10 +266,8 @@ public class OIDCLoginProtocol implements LoginProtocol {
if (responseType.hasResponseType(OIDCResponseType.TOKEN)) { if (responseType.hasResponseType(OIDCResponseType.TOKEN)) {
redirectUri.addParam(OAuth2Constants.ACCESS_TOKEN, res.getToken()); redirectUri.addParam(OAuth2Constants.ACCESS_TOKEN, res.getToken());
if (responseType.isImplicitFlow()) { redirectUri.addParam(OAuth2Constants.TOKEN_TYPE, res.getTokenType());
redirectUri.addParam("token_type", res.getTokenType()); redirectUri.addParam(OAuth2Constants.EXPIRES_IN, String.valueOf(res.getExpiresIn()));
redirectUri.addParam("expires_in", String.valueOf(res.getExpiresIn()));
}
} }
} }

View file

@ -1359,6 +1359,8 @@ public class OAuthClient {
// Just during OIDC implicit or hybrid flow // Just during OIDC implicit or hybrid flow
private String accessToken; private String accessToken;
private String idToken; private String idToken;
private String tokenType;
private String expiresIn;
public AuthorizationEndpointResponse(OAuthClient client) { public AuthorizationEndpointResponse(OAuthClient client) {
boolean fragment; boolean fragment;
@ -1390,6 +1392,8 @@ public class OAuthClient {
sessionState = params.get(OAuth2Constants.SESSION_STATE); sessionState = params.get(OAuth2Constants.SESSION_STATE);
accessToken = params.get(OAuth2Constants.ACCESS_TOKEN); accessToken = params.get(OAuth2Constants.ACCESS_TOKEN);
idToken = params.get(OAuth2Constants.ID_TOKEN); idToken = params.get(OAuth2Constants.ID_TOKEN);
tokenType = params.get(OAuth2Constants.TOKEN_TYPE);
expiresIn = params.get(OAuth2Constants.EXPIRES_IN);
} }
public boolean isRedirected() { public boolean isRedirected() {
@ -1423,6 +1427,14 @@ public class OAuthClient {
public String getIdToken() { public String getIdToken() {
return idToken; return idToken;
} }
public String getTokenType() {
return tokenType;
}
public String getExpiresIn() {
return expiresIn;
}
} }
public static class AccessTokenResponse { public static class AccessTokenResponse {
@ -1480,10 +1492,10 @@ public class OAuthClient {
case OAuth2Constants.ISSUED_TOKEN_TYPE: case OAuth2Constants.ISSUED_TOKEN_TYPE:
issuedTokenType = (String) entry.getValue(); issuedTokenType = (String) entry.getValue();
break; break;
case "token_type": case OAuth2Constants.TOKEN_TYPE:
tokenType = (String) entry.getValue(); tokenType = (String) entry.getValue();
break; break;
case "expires_in": case OAuth2Constants.EXPIRES_IN:
expiresIn = (Integer) entry.getValue(); expiresIn = (Integer) entry.getValue();
break; break;
case "refresh_expires_in": case "refresh_expires_in":

View file

@ -63,6 +63,12 @@ public class OIDCBasicResponseTypeCodeTest extends AbstractOIDCResponseTypeTest
// Validate "at_hash" // Validate "at_hash"
assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken()); assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken());
// Validate if token_type is null
Assert.assertNull(authzResponse.getTokenType());
// Validate if expires_in is null
Assert.assertNull(authzResponse.getExpiresIn());
return Collections.singletonList(idToken2); return Collections.singletonList(idToken2);
} }

View file

@ -73,6 +73,12 @@ public class OIDCHybridResponseTypeCodeIDTokenTest extends AbstractOIDCResponseT
Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getState())); Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getState()));
// Validate if token_type is null
Assert.assertNull(authzResponse.getTokenType());
// Validate if expires_in is null
Assert.assertNull(authzResponse.getExpiresIn());
// IDToken exchanged for the code // IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -73,6 +73,12 @@ public class OIDCHybridResponseTypeCodeIDTokenTokenTest extends AbstractOIDCResp
Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getState())); Assert.assertEquals(idToken.getStateHash(), HashUtils.oidcHash(getIdTokenSignatureAlgorithm(), authzResponse.getState()));
// Validate if token_type is present
Assert.assertNotNull(authzResponse.getTokenType());
// Validate if expires_in is present
Assert.assertNotNull(authzResponse.getExpiresIn());
// IDToken exchanged for the code // IDToken exchanged for the code
IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent); IDToken idToken2 = sendTokenRequestAndGetIDToken(loginEvent);

View file

@ -64,6 +64,12 @@ public class OIDCHybridResponseTypeCodeTokenTest extends AbstractOIDCResponseTyp
// Validate "at_hash" // Validate "at_hash"
assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken()); assertValidAccessTokenHash(idToken2.getAccessTokenHash(), authzResponse2.getAccessToken());
// Validate if token_type is present
Assert.assertNotNull(authzResponse.getTokenType());
// Validate if expires_in is present
Assert.assertNotNull(authzResponse.getExpiresIn());
return Collections.singletonList(idToken2); return Collections.singletonList(idToken2);
} }

View file

@ -61,6 +61,12 @@ public class OIDCImplicitResponseTypeIDTokenTest extends AbstractOIDCResponseTyp
Assert.assertNull(idToken.getAccessTokenHash()); Assert.assertNull(idToken.getAccessTokenHash());
Assert.assertNull(idToken.getCodeHash()); Assert.assertNull(idToken.getCodeHash());
// Validate if token_type is null
Assert.assertNull(authzResponse.getTokenType());
// Validate if expires_in is null
Assert.assertNull(authzResponse.getExpiresIn());
return Collections.singletonList(idToken); return Collections.singletonList(idToken);
} }

View file

@ -65,6 +65,12 @@ public class OIDCImplicitResponseTypeIDTokenTokenTest extends AbstractOIDCRespon
// Validate "c_hash" // Validate "c_hash"
Assert.assertNull(idToken.getCodeHash()); Assert.assertNull(idToken.getCodeHash());
// Validate if token_type is present
Assert.assertNotNull(authzResponse.getTokenType());
// Validate if expires_in is present
Assert.assertNotNull(authzResponse.getExpiresIn());
return Collections.singletonList(idToken); return Collections.singletonList(idToken);
} }