8000 Using DPoP token type in the access-token and as token_type in introspection response by tnorimat · Pull Request #22244 · keycloak/keycloak · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Using DPoP token type in the access-token and as token_type in introspection response #22244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions core/src/main/java/org/keycloak/TokenVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,20 @@ public boolean test(JsonWebToken t) throws VerificationException {

public static class TokenTypeCheck implements Predicate<JsonWebToken> {

private static final TokenTypeCheck INSTANCE_BEARER = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_BEARER);
private static final TokenTypeCheck INSTANCE_DEFAULT_TOKEN_TYPE = new TokenTypeCheck(Arrays.asList(TokenUtil.TOKEN_TYPE_BEARER, TokenUtil.TOKEN_TYPE_DPOP));

private final String tokenType;
private final List<String> tokenTypes;

public TokenTypeCheck(String tokenType) {
this.tokenType = tokenType;
public TokenTypeCheck(List<String> tokenTypes) {
this.tokenTypes = tokenTypes;
}

@Override
public boolean test(JsonWebToken t) throws VerificationException {
if (! tokenType.equalsIgnoreCase(t.getType())) {
throw new VerificationException("Token type is incorrect. Expected '" + tokenType + "' but was '" + t.getType() + "'");
for (String tokenType : tokenTypes) {
if (tokenType.equalsIgnoreCase(t.getType())) return true;
}
return true;
throw new VerificationException("Token type is incorrect. Expected '" + tokenTypes.toString() + "' but was '" + t.getType() + "'");
}
};

Expand Down Expand Up @@ -190,7 +190,7 @@ public boolean test(JsonWebToken jsonWebToken) throws VerificationException {
private PublicKey publicKey;
private SecretKey secretKey;
private String realmUrl;
private String expectedTokenType = TokenUtil.TOKEN_TYPE_BEARER;
private List<String> expectedTokenType = Arrays.asList(TokenUtil.TOKEN_TYPE_BEARER, TokenUtil.TOKEN_TYPE_DPOP);
private boolean checkTokenType = true;
private boolean checkRealmUrl = true;
private final LinkedList<Predicate<? super T>> checks = new LinkedList<>();
Expand Down Expand Up @@ -254,7 +254,7 @@ public TokenVerifier<T> withDefaultChecks() {
return withChecks(
RealmUrlCheck.NULL_INSTANCE,
SUBJECT_EXISTS_CHECK,
TokenTypeCheck.INSTANCE_BEARER,
TokenTypeCheck.INSTANCE_DEFAULT_TOKEN_TYPE,
IS_ACTIVE
);
}
Expand Down Expand Up @@ -344,8 +344,8 @@ public TokenVerifier<T> checkTokenType(boolean checkTokenType) {
*
* @return This token verifier
*/
public TokenVerifier<T> tokenType(String tokenType) {
this.expectedTokenType = tokenType;
public TokenVerifier<T> tokenType(List<String> tokenTypes) {
this.expectedTokenType = tokenTypes;
return replaceCheck(TokenTypeCheck.class, this.checkTokenType, new TokenTypeCheck(expectedTokenType));
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/keycloak/util/TokenUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class TokenUtil {

public static final String TOKEN_TYPE_BEARER = "Bearer";

public static final String TOKEN_TYPE_DPOP = "DPoP";

public static final String TOKEN_TYPE_KEYCLOAK_ID = "Serialized-ID";

public static final String TOKEN_TYPE_ID = "ID";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.jboss.logging.Logger;
import org.keycloak.OAuth2Constants;
import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.SignatureProvider;
Expand Down Expand Up @@ -85,6 +86,9 @@ public Response introspect(String token) {
}
}
}

tokenMetadata.put(OAuth2Constants.TOKEN_TYPE, accessToken.getType());

} else {
tokenMetadata = JsonSerialization.createObjectNode();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

Expand Down Expand Up @@ -200,7 +202,7 @@ public Response logout(@QueryParam(OIDCLoginProtocol.REDIRECT_URI_PARAM) String
if (encodedIdToken != null) {
try {
idToken = tokenManager.verifyIDTokenSignature(session, encodedIdToken);
TokenVerifier.createWithoutSignature(idToken).tokenType(TokenUtil.TOKEN_TYPE_ID).verify();
TokenVerifier.createWithoutSignature(idToken).tokenType(Arrays.asList(TokenUtil.TOKEN_TYPE_ID)).verify();
} catch (OAuthErrorException | VerificationException e) {
event.event(EventType.LOGOUT);
event.error(Errors.INVALID_TOKEN);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,8 +549,7 @@ private void checkAndBindDPoPToken(TokenManager.AccessTokenResponseBuilder respo

if (clientConfig.isUseDPoP() || dPoP != null) {
DPoPUtil.bindToken(responseBuilder.getAccessToken(), dPoP);
// TODO Probably uncomment as the accessToken type "DPoP" will have more sense than "Bearer". It will require some changes in the introspection endpoint too...
// responseBuilder.getAccessToken().type(DPoPUtil.DPOP_TOKEN_TYPE);
responseBuilder.getAccessToken().type(DPoPUtil.DPOP_TOKEN_TYPE);
responseBuilder.responseTokenType(DPoPUtil.DPOP_TOKEN_TYPE);

// Bind refresh tokens for public clients, See "Section 5. DPoP Access Token Request" from DPoP specification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ private void checkToken() {
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.OK);
}

if (!(TokenUtil.TOKEN_TYPE_REFRESH.equals(token.getType()) || TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType()) || TokenUtil.TOKEN_TYPE_BEARER.equals(token.getType()))) {
if (!(TokenUtil.TOKEN_TYPE_REFRESH.equals(token.getType()) || TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType()) || TokenUtil.TOKEN_TYPE_BEARER.equals(token.getType())|| TokenUtil.TOKEN_TYPE_DPOP.equals(token.getType()))) {
event.error(Errors.INVALID_TOKEN_TYPE);
throw new CorsErrorResponseException(cors, OAuthErrorException.UNSUPPORTED_TOKEN_TYPE, "Unsupported token type",
Response.Status.BAD_REQUEST);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -170,7 +171,7 @@ public class AuthenticationManager {
// Parameter of LogoutEndpoint
public static final String INITIATING_IDP_PARAM = "initiating_idp";

private static final TokenTypeCheck VALIDATE_IDENTITY_COOKIE = new TokenTypeCheck(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID);
private static final TokenTypeCheck VALIDATE_IDENTITY_COOKIE = new TokenTypeCheck(Arrays.asList(TokenUtil.TOKEN_TYPE_KEYCLOAK_ID));

public static boolean isSessionValid(RealmModel realm, UserSessionModel userSession) {
if (userSession == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import org.keycloak.testsuite.util.ServerURLs;
import org.keycloak.util.JWKSUtils;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.TokenUtil;

import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
Expand Down Expand Up @@ -193,6 +194,7 @@ public void testDPoPProofByConfidentialClient() throws Exception {
TokenMetadataRepresentation tokenMetadataRepresentation = JsonSerialization.readValue(tokenResponse, TokenMetadataRepresentation.class);
Assert.assertTrue(tokenMetadataRepresentation.isActive());
assertEquals(jkt, tokenMetadataRepresentation.getConfirmation().getKeyThumbprint());
assertEquals(TokenUtil.TOKEN_TYPE_DPOP, tokenMetadataRepresentation.getOtherClaims().get(OAuth2Constants.TOKEN_TYPE));

CloseableHttpResponse closableHttpResponse = oauth.doTokenRevoke(response.getAccessToken(), "access_token", TEST_CONFIDENTIAL_CLIENT_SECRET);
tokenResponse = oauth.introspectTokenWithClientCredential(TEST_CONFIDENTIAL_CLIENT_ID, TEST_CONFIDENTIAL_CLIENT_SECRET, "access_token", response.getAccessToken());
Expand Down Expand Up @@ -309,7 +311,7 @@ public void testDPoPProofCorsPreflight() throws Exception {
String[] headers = response.getHeaders(Cors.ACCESS_CONTROL_ALLOW_HEADERS)[0].getValue().split(", ");
Set<String> allowedHeaders = new HashSet<String>(Arrays.asList(headers));

assertTrue(allowedHeaders.contains("DPoP"));
assertTrue(allowedHeaders.contains(TokenUtil.TOKEN_TYPE_DPOP));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import org.keycloak.testsuite.util.WaitUtils;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
import org.keycloak.util.TokenUtil;

import jakarta.ws.rs.core.UriBuilder;

Expand Down Expand Up @@ -352,6 +353,7 @@ private void testIntrospectAccessToken(String jwaAlgorithm) throws Exception {
assertEquals("test-user@localhost", rep.getUserName());
assertEquals("test-app", rep.getClientId());
assertEquals(loginEvent.getUserId(), rep.getSubject());
assertEquals(TokenUtil.TOKEN_TYPE_BEARER, rep.getOtherClaims().get(OAuth2Constants.TOKEN_TYPE));

// Assert expected scope
OIDCScopeTest.assertScopes("openid email profile", rep.getScope());
Expand Down
0