Verification of iss at refresh token request

Added iss checking using the existing TokenVerifier.RealmUrlCheck in the verifyRefreshToken method.

Closes #22191
This commit is contained in:
ggraziano 2023-10-22 16:13:50 +02:00 committed by Marek Posolda
parent 1bd6aca629
commit 84112f57b5
3 changed files with 49 additions and 8 deletions

View file

@ -107,7 +107,7 @@ public class TokenVerifier<T extends JsonWebToken> {
}
if (! this.realmUrl.equals(t.getIssuer())) {
throw new VerificationException("Invalid token issuer. Expected '" + this.realmUrl + "', but was '" + t.getIssuer() + "'");
throw new VerificationException("Invalid token issuer. Expected '" + this.realmUrl + "'");
}
return true;

View file

@ -78,6 +78,7 @@ import org.keycloak.representations.LogoutToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.dpop.DPoP;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.UserSessionCrossDCManager;
@ -477,15 +478,18 @@ public class TokenManager {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token");
}
TokenVerifier<RefreshToken> tokenVerifier = TokenVerifier.createWithoutSignature(refreshToken)
.withChecks(new TokenVerifier.RealmUrlCheck(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName())));
if (checkExpiration) {
tokenVerifier.withChecks(NotBeforeCheck.forModel(realm), TokenVerifier.IS_ACTIVE);
}
try {
TokenVerifier.createWithoutSignature(refreshToken)
.withChecks(NotBeforeCheck.forModel(realm), TokenVerifier.IS_ACTIVE)
.verify();
tokenVerifier.verify();
} catch (VerificationException e) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, e.getMessage());
}
}
if (!client.getClientId().equals(refreshToken.getIssuedFor())) {
throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match");

View file

@ -60,6 +60,7 @@ import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.undertow.lb.SimpleUndertowLoadBalancer;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
@ -107,6 +108,7 @@ import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.admin.ApiUtil.findUserByUsername;
import static org.keycloak.testsuite.util.OAuthClient.AUTH_SERVER_ROOT;
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
import static org.keycloak.testsuite.arquillian.AuthServerTestEnricher.getHttpAuthServerContextRoot;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@ -284,6 +286,41 @@ public class RefreshTokenTest extends AbstractKeycloakTest {
assertEquals("123456", refreshedToken.getNonce());
}
@Test
public void refreshTokenWithDifferentIssuer() throws Exception {
final String proxyHost = "proxy.kc.127.0.0.1.nip.io";
final int httpPort = 8666;
final int httpsPort = 8667;
oauth.doLogin("test-user@localhost", "password");
EventRepresentation loginEvent = events.expectLogin().assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
OAuthClient.AccessTokenResponse response = oauth.doAccessTokenRequest(code, "password");
String refreshTokenString = response.getRefreshToken();
events.expectCodeToToken(codeId, sessionId).assertEvent();
SimpleUndertowLoadBalancer proxy = new SimpleUndertowLoadBalancer(proxyHost, httpPort, httpsPort, "node1=" + getHttpAuthServerContextRoot() + "/auth");
proxy.start();
oauth.baseUrl(String.format("http://%s:%s", proxyHost, httpPort));
response = oauth.doRefreshTokenRequest(refreshTokenString, "password");
Assert.assertEquals(400, response.getStatusCode());
events.expect(EventType.REFRESH_TOKEN).error(Errors.INVALID_TOKEN).user((String) null).assertEvent();
proxy.stop();
oauth.baseUrl(AUTH_SERVER_ROOT);
}
@Test
public void refreshTokenWithAccessToken() throws Exception {
oauth.doLogin("test-user@localhost", "password");