diff --git a/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java b/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java
index 0b79a3789c77c245c7c1ff5dc37e7c2d9adb1bbf..0c778cd5ca04289fe63869058deffdaaef10248d 100644
--- a/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java
+++ b/authenticator/src/main/java/cz/cesnet/keycloak/CustomAuthenticator.java
@@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
 
 import jakarta.ws.rs.core.Response;
 
-import java.util.List;
 import java.util.Map;
 
 import static org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator.BROKERED_CONTEXT_NOTE;
diff --git a/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProvider.java b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..7ea5967ad36b5f15e6fff7443d06291ac26a2b43
--- /dev/null
+++ b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProvider.java
@@ -0,0 +1,60 @@
+package cz.cesnet.keycloak;
+
+import jakarta.ws.rs.core.Response;
+import org.keycloak.OAuthErrorException;
+import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.protocol.oidc.TokenExchangeContext;
+import org.keycloak.protocol.oidc.TokenExchangeProvider;
+import org.keycloak.protocol.oidc.DefaultTokenExchangeProvider;
+import org.keycloak.jose.jws.JWSInput;
+import org.keycloak.jose.jws.JWSInputException;
+import org.keycloak.representations.JsonWebToken;
+import org.keycloak.services.CorsErrorResponseException;
+import org.keycloak.services.cors.Cors;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CustomTokenExchangeProvider implements TokenExchangeProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(CustomTokenExchangeProvider.class);
+
+    @Override
+    public Response exchange(TokenExchangeContext context) {
+        logger.debug("Exchanging token by custom token exchange provider.");
+
+        EventBuilder event = context.getEvent();
+        JsonWebToken jwt;
+        Cors cors = context.getCors();
+        TokenExchangeContext.Params params = context.getParams();
+        String subjectToken = params.getSubjectToken();
+
+        try {
+            JWSInput jws = new JWSInput(subjectToken);
+            jwt = jws.readJsonContent(JsonWebToken.class);
+        } catch (JWSInputException e) {
+            event.detail(Details.REASON, "unable to parse jwt subject_token");
+            event.error(Errors.INVALID_TOKEN);
+            throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_TOKEN, "Invalid subject token", Response.Status.BAD_REQUEST);
+        }
+
+        String issuer = jwt.getIssuer();
+        logger.info("Token exchange issuer: {}", issuer);
+
+        logger.info(jwt.getOtherClaims().toString());
+
+        DefaultTokenExchangeProvider defaultTokenExchangeProvider = new DefaultTokenExchangeProvider();
+        return defaultTokenExchangeProvider.exchange(context);
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public boolean supports(TokenExchangeContext context) {
+        return true;
+    }
+
+}
\ No newline at end of file
diff --git a/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProviderFactory.java b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProviderFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..3d4fab02739b04ba85527ced0c353ed137c6c78d
--- /dev/null
+++ b/authenticator/src/main/java/cz/cesnet/keycloak/CustomTokenExchangeProviderFactory.java
@@ -0,0 +1,40 @@
+package cz.cesnet.keycloak;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.protocol.oidc.TokenExchangeProvider;
+import org.keycloak.protocol.oidc.TokenExchangeProviderFactory;
+
+public class CustomTokenExchangeProviderFactory implements TokenExchangeProviderFactory {
+    private static final String PROVIDER_ID = "custom-token-exchange";
+    private static final Logger logger = LoggerFactory.getLogger(CustomTokenExchangeProviderFactory.class);
+
+    @Override
+    public TokenExchangeProvider create(KeycloakSession session) {
+        return new CustomTokenExchangeProvider();
+    }
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public void init(org.keycloak.Config.Scope config) {
+    }
+
+    @Override
+    public void postInit(org.keycloak.models.KeycloakSessionFactory factory) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public int order() {
+        // important that this value is higher than the default TokenExchangeProvider, otherwise it won't use this one.
+        return 1000;
+    }
+}
\ No newline at end of file
diff --git a/authenticator/src/main/resources/META-INF/services/org.keycloak.protocol.oidc.TokenExchangeProviderFactory b/authenticator/src/main/resources/META-INF/services/org.keycloak.protocol.oidc.TokenExchangeProviderFactory
new file mode 100644
index 0000000000000000000000000000000000000000..17d4e6a7be4b18f5c9ff6bb23f679f75470c20b3
--- /dev/null
+++ b/authenticator/src/main/resources/META-INF/services/org.keycloak.protocol.oidc.TokenExchangeProviderFactory
@@ -0,0 +1 @@
+cz.cesnet.keycloak.CustomTokenExchangeProviderFactory
\ No newline at end of file