diff --git a/pom.xml b/pom.xml
index 6cd73d4..28fd857 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,6 +11,7 @@
pom
+ utils
event-listener
tokenmapper
flintstones-userprovider
@@ -23,7 +24,7 @@
conditional-authenticators
mfa-authenticator
passkey
-
+
UTF-8
diff --git a/utils/pom.xml b/utils/pom.xml
new file mode 100644
index 0000000..53631a0
--- /dev/null
+++ b/utils/pom.xml
@@ -0,0 +1,51 @@
+
+
+ 4.0.0
+
+
+ dasniko.keycloak
+ keycloak-extensions-demo
+ 1.0-SNAPSHOT
+
+
+ keycloak-utils
+ 1.0-SNAPSHOT
+
+
+
+ org.keycloak
+ keycloak-core
+
+
+ org.keycloak
+ keycloak-server-spi
+
+
+ org.keycloak
+ keycloak-server-spi-private
+
+
+ org.keycloak
+ keycloak-services
+
+
+ org.keycloak
+ keycloak-model-legacy
+
+
+ org.projectlombok
+ lombok
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ ${project.groupId}-${project.artifactId}
+
+
+
diff --git a/utils/src/main/java/dasniko/keycloak/util/TokenUtils.java b/utils/src/main/java/dasniko/keycloak/util/TokenUtils.java
new file mode 100644
index 0000000..f47e7b0
--- /dev/null
+++ b/utils/src/main/java/dasniko/keycloak/util/TokenUtils.java
@@ -0,0 +1,171 @@
+package dasniko.keycloak.util;
+
+import org.keycloak.common.ClientConnection;
+import org.keycloak.common.constants.ServiceAccountConstants;
+import org.keycloak.events.EventBuilder;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionContext;
+import org.keycloak.models.KeycloakContext;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.protocol.oidc.OIDCLoginProtocol;
+import org.keycloak.protocol.oidc.TokenManager;
+import org.keycloak.representations.AccessToken;
+import org.keycloak.representations.AccessTokenResponse;
+import org.keycloak.services.Urls;
+import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.managers.AuthenticationSessionManager;
+import org.keycloak.sessions.AuthenticationSessionModel;
+import org.keycloak.sessions.RootAuthenticationSessionModel;
+
+import java.util.function.Consumer;
+
+import static org.keycloak.models.UserSessionModel.SessionPersistenceState.TRANSIENT;
+
+@SuppressWarnings("unused")
+public class TokenUtils {
+
+ public static String generateServiceAccountAccessToken(KeycloakSession session, String clientId, String scope, Consumer tokenAdjuster) {
+
+ var context = session.getContext();
+ var realm = context.getRealm();
+ var client = session.clients().getClientByClientId(realm, clientId);
+
+ if (client == null) {
+ throw new IllegalStateException("client not found");
+ }
+
+ if (!client.isServiceAccountsEnabled()) {
+ throw new IllegalStateException("service account not enabled");
+ }
+
+ var clientUser = session.users().getServiceAccount(client);
+ var clientUsername = clientUser.getUsername();
+
+ // we need to remember the current authSession since createAuthenticationSession changes the current authSession in the context
+ var currentAuthSession = context.getAuthenticationSession();
+
+ try {
+ var rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
+ var authSession = rootAuthSession.createAuthenticationSession(client);
+
+ authSession.setAuthenticatedUser(clientUser);
+ authSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ authSession.setClientNote(OIDCLoginProtocol.ISSUER, Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()));
+ authSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
+
+ var clientConnection = context.getConnection();
+ var sessionId = authSession.getParentSession().getId();
+ var remoteAddr = clientConnection.getRemoteAddr();
+ var userSession = session.sessions().createUserSession(sessionId, realm, clientUser, clientUsername, //
+ remoteAddr, ServiceAccountConstants.CLIENT_AUTH, false, null, null, TRANSIENT);
+
+ AuthenticationManager.setClientScopesInSession(authSession);
+ var clientSessionCtx = TokenManager.attachAuthenticationSession(session, userSession, authSession);
+
+ // Notes about client details
+ userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
+ userSession.setNote(ServiceAccountConstants.CLIENT_HOST, clientConnection.getRemoteHost());
+ userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, remoteAddr);
+
+ var tokenManager = new TokenManager();
+ var event = new EventBuilder(realm, session, clientConnection);
+ var responseBuilder = tokenManager.responseBuilder(realm, client, event, session, userSession, clientSessionCtx);
+ responseBuilder.generateAccessToken();
+
+ if (tokenAdjuster != null) {
+ tokenAdjuster.accept(responseBuilder.getAccessToken());
+ }
+
+ var accessTokenResponse = responseBuilder.build();
+ return accessTokenResponse.getToken();
+ } finally {
+ // reset current authentication session
+ context.setAuthenticationSession(currentAuthSession);
+ }
+ }
+
+ public static String generateAccessToken(KeycloakSession session, UserSessionModel userSession, String clientId, String scope, Consumer tokenAdjuster) {
+
+ KeycloakContext context = session.getContext();
+ RealmModel realm = userSession.getRealm();
+ ClientModel client = session.clients().getClientByClientId(realm, clientId);
+ String issuer = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName());
+
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
+ AuthenticationSessionModel iamAuthSession = rootAuthSession.createAuthenticationSession(client);
+
+ iamAuthSession.setAuthenticatedUser(userSession.getUser());
+ iamAuthSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ iamAuthSession.setClientNote(OIDCLoginProtocol.ISSUER, issuer);
+ iamAuthSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
+
+ ClientConnection connection = context.getConnection();
+ UserSessionModel iamUserSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, userSession.getUser(), userSession.getUser().getUsername(), connection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, TRANSIENT);
+
+ AuthenticationManager.setClientScopesInSession(iamAuthSession);
+ ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, iamUserSession, iamAuthSession);
+
+ // Notes about client details
+ userSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
+ userSession.setNote(ServiceAccountConstants.CLIENT_HOST, connection.getRemoteHost());
+ userSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, connection.getRemoteAddr());
+
+ TokenManager tokenManager = new TokenManager();
+
+ EventBuilder eventBuilder = new EventBuilder(realm, session, connection);
+ TokenManager.AccessTokenResponseBuilder tokenResponseBuilder = tokenManager.responseBuilder(realm, client, eventBuilder, session, iamUserSession, clientSessionCtx);
+ AccessToken accessToken = tokenResponseBuilder.generateAccessToken().getAccessToken();
+
+ if (tokenAdjuster != null) {
+ tokenAdjuster.accept(accessToken);
+ }
+
+ AccessTokenResponse tokenResponse = tokenResponseBuilder.build();
+
+ return tokenResponse.getToken();
+ }
+
+ public static String generateAccessToken(KeycloakSession session, RealmModel realm, UserModel user, String clientId, String scope, Consumer tokenAdjuster) {
+
+ KeycloakContext context = session.getContext();
+ ClientModel client = session.clients().getClientByClientId(realm, clientId);
+ String issuer = Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName());
+
+ RootAuthenticationSessionModel rootAuthSession = new AuthenticationSessionManager(session).createAuthenticationSession(realm, false);
+ AuthenticationSessionModel iamAuthSession = rootAuthSession.createAuthenticationSession(client);
+
+ iamAuthSession.setAuthenticatedUser(user);
+ iamAuthSession.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
+ iamAuthSession.setClientNote(OIDCLoginProtocol.ISSUER, issuer);
+ iamAuthSession.setClientNote(OIDCLoginProtocol.SCOPE_PARAM, scope);
+
+ ClientConnection connection = context.getConnection();
+ UserSessionModel iamUserSession = session.sessions().createUserSession(KeycloakModelUtils.generateId(), realm, user, user.getUsername(), connection.getRemoteAddr(), ServiceAccountConstants.CLIENT_AUTH, false, null, null, TRANSIENT);
+
+ AuthenticationManager.setClientScopesInSession(iamAuthSession);
+ ClientSessionContext clientSessionCtx = TokenManager.attachAuthenticationSession(session, iamUserSession, iamAuthSession);
+
+ // Notes about client details
+ iamUserSession.setNote(ServiceAccountConstants.CLIENT_ID, client.getClientId());
+ iamUserSession.setNote(ServiceAccountConstants.CLIENT_HOST, connection.getRemoteHost());
+ iamUserSession.setNote(ServiceAccountConstants.CLIENT_ADDRESS, connection.getRemoteAddr());
+
+ TokenManager tokenManager = new TokenManager();
+
+ EventBuilder eventBuilder = new EventBuilder(realm, session, connection);
+ TokenManager.AccessTokenResponseBuilder tokenResponseBuilder = tokenManager.responseBuilder(realm, client, eventBuilder, session, iamUserSession, clientSessionCtx);
+ AccessToken accessToken = tokenResponseBuilder.generateAccessToken().getAccessToken();
+
+ if (tokenAdjuster != null) {
+ tokenAdjuster.accept(accessToken);
+ }
+
+ AccessTokenResponse tokenResponse = tokenResponseBuilder.build();
+
+ return tokenResponse.getToken();
+ }
+}