[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:
parent
590ee1b1a2
commit
e10f3b3672
10 changed files with 57 additions and 7 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue