Service Provider Interface (SPI) contracts and abstractions for the Keycloak identity and access management server enabling extensibility through custom providers
—
Authentication sessions manage the state during authentication flows in Keycloak. They provide a way to track user progress through multi-step authentication processes and store temporary data during login.
Represents an authentication session for a specific client tab.
public interface AuthenticationSessionModel extends CommonClientSessionModel {
/**
* Gets the tab ID for this authentication session.
*
* @return tab ID
*/
String getTabId();
/**
* Gets the parent root authentication session.
*
* @return parent session
*/
RootAuthenticationSessionModel getParentSession();
/**
* Gets the execution status map for authentication flow.
*
* @return map of authenticator ID to execution status
*/
Map<String, ExecutionStatus> getExecutionStatus();
/**
* Sets the execution status for a specific authenticator.
*
* @param authenticator the authenticator ID
* @param status the execution status
*/
void setExecutionStatus(String authenticator, ExecutionStatus status);
/**
* Clears all execution status entries.
*/
void clearExecutionStatus();
/**
* Gets the authenticated user for this session.
*
* @return authenticated user or null
*/
UserModel getAuthenticatedUser();
/**
* Sets the authenticated user for this session.
*
* @param user the authenticated user
*/
void setAuthenticatedUser(UserModel user);
/**
* Gets required actions that need to be performed.
*
* @return set of required action names
*/
Set<String> getRequiredActions();
/**
* Adds a required action.
*
* @param action the required action name
*/
void addRequiredAction(String action);
/**
* Adds a required action enum.
*
* @param action the required action enum
*/
void addRequiredAction(RequiredAction action);
/**
* Removes a required action.
*
* @param action the required action name
*/
void removeRequiredAction(String action);
/**
* Removes a required action enum.
*
* @param action the required action enum
*/
void removeRequiredAction(RequiredAction action);
/**
* Sets the user session note.
*
* @param name the note name
* @param value the note value
*/
void setUserSessionNote(String name, String value);
/**
* Gets user session notes.
*
* @return map of note names to values
*/
Map<String, String> getUserSessionNotes();
}Represents the root authentication session that can contain multiple client tabs.
public interface RootAuthenticationSessionModel {
/**
* Gets the session ID.
*
* @return session ID
*/
String getId();
/**
* Gets the realm for this session.
*
* @return realm model
*/
RealmModel getRealm();
/**
* Gets the timestamp when the session was created.
*
* @return creation timestamp in seconds
*/
int getTimestamp();
/**
* Sets the timestamp for the session.
*
* @param timestamp timestamp in seconds
*/
void setTimestamp(int timestamp);
/**
* Gets authentication session for a specific client and tab.
*
* @param client the client
* @param tabId the tab ID
* @return authentication session or null
*/
AuthenticationSessionModel getAuthenticationSession(ClientModel client, String tabId);
/**
* Creates a new authentication session for a client and tab.
*
* @param client the client
* @param tabId the tab ID
* @return created authentication session
*/
AuthenticationSessionModel createAuthenticationSession(ClientModel client, String tabId);
/**
* Removes authentication session for a client and tab.
*
* @param client the client
* @param tabId the tab ID
*/
void removeAuthenticationSessionByTabId(ClientModel client, String tabId);
/**
* Restarts the session (clears all authentication sessions).
*/
void restartSession(RealmModel realm);
}Base interface for client sessions with common functionality.
public interface CommonClientSessionModel {
/**
* Gets the session ID.
*
* @return session ID
*/
String getId();
/**
* Gets the realm for this session.
*
* @return realm model
*/
RealmModel getRealm();
/**
* Gets the client for this session.
*
* @return client model
*/
ClientModel getClient();
/**
* Gets the redirect URI.
*
* @return redirect URI
*/
String getRedirectUri();
/**
* Sets the redirect URI.
*
* @param uri the redirect URI
*/
void setRedirectUri(String uri);
/**
* Gets a client note.
*
* @param name the note name
* @return note value or null
*/
String getNote(String name);
/**
* Sets a client note.
*
* @param name the note name
* @param value the note value
*/
void setNote(String name, String value);
/**
* Removes a client note.
*
* @param name the note name
*/
void removeNote(String name);
/**
* Gets all client notes.
*
* @return map of note names to values
*/
Map<String, String> getNotes();
/**
* Gets the authentication method.
*
* @return authentication method
*/
String getAuthMethod();
/**
* Sets the authentication method.
*
* @param method the authentication method
*/
void setAuthMethod(String method);
}Provider for managing authentication sessions.
public interface AuthenticationSessionProvider extends Provider {
/**
* Creates a new root authentication session.
*
* @param realm the realm
* @return created root session
*/
RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm);
/**
* Creates a root authentication session with specific ID.
*
* @param realm the realm
* @param id the session ID
* @return created root session
*/
RootAuthenticationSessionModel createRootAuthenticationSession(RealmModel realm, String id);
/**
* Gets a root authentication session by ID.
*
* @param realm the realm
* @param authenticationSessionId the session ID
* @return root session or null
*/
RootAuthenticationSessionModel getRootAuthenticationSession(RealmModel realm, String authenticationSessionId);
/**
* Removes a root authentication session.
*
* @param realm the realm
* @param authenticationSession the session to remove
*/
void removeRootAuthenticationSession(RealmModel realm, RootAuthenticationSessionModel authenticationSession);
/**
* Removes expired authentication sessions.
*
* @param realm the realm
*/
void removeExpired(RealmModel realm);
/**
* Removes all authentication sessions for a realm.
*
* @param realm the realm
*/
void onRealmRemoved(RealmModel realm);
/**
* Removes all authentication sessions for a client.
*
* @param realm the realm
* @param client the client
*/
void onClientRemoved(RealmModel realm, ClientModel client);
/**
* Updates ownership when a client is renamed.
*
* @param realm the realm
* @param client the client
* @param newClientId the new client ID
*/
void updateNonlocalSessionAuthNotes(RealmModel realm, ClientModel client, String newClientId);
}Utility class for parsing authentication session compound IDs.
public class AuthenticationSessionCompoundId {
private final String rootSessionId;
private final String clientUUID;
private final String tabId;
public AuthenticationSessionCompoundId(String rootSessionId, String clientUUID, String tabId) {
this.rootSessionId = rootSessionId;
this.clientUUID = clientUUID;
this.tabId = tabId;
}
/**
* Parses a compound ID string.
*
* @param compoundId the compound ID string
* @return parsed compound ID
*/
public static AuthenticationSessionCompoundId fromSessionId(String compoundId) {
String[] parts = compoundId.split("\\.");
if (parts.length >= 3) {
return new AuthenticationSessionCompoundId(parts[0], parts[1], parts[2]);
}
throw new IllegalArgumentException("Invalid compound session ID: " + compoundId);
}
/**
* Generates the compound ID string.
*
* @return compound ID string
*/
public String getEncodedId() {
return rootSessionId + "." + clientUUID + "." + tabId;
}
public String getRootSessionId() { return rootSessionId; }
public String getClientUUID() { return clientUUID; }
public String getTabId() { return tabId; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
AuthenticationSessionCompoundId that = (AuthenticationSessionCompoundId) obj;
return Objects.equals(rootSessionId, that.rootSessionId) &&
Objects.equals(clientUUID, that.clientUUID) &&
Objects.equals(tabId, that.tabId);
}
@Override
public int hashCode() {
return Objects.hash(rootSessionId, clientUUID, tabId);
}
@Override
public String toString() {
return getEncodedId();
}
}public enum ExecutionStatus {
SUCCESS,
FAILED,
SETUP_REQUIRED,
ATTEMPTED,
SKIPPED,
CHALLENGED,
FLOW_RESET
}// Create and manage authentication sessions
try (KeycloakSession session = sessionFactory.create()) {
RealmModel realm = session.realms().getRealmByName("myrealm");
ClientModel client = realm.getClientByClientId("my-app");
AuthenticationSessionProvider authSessionProvider = session.authenticationSessions();
// Create root authentication session
RootAuthenticationSessionModel rootSession = authSessionProvider.createRootAuthenticationSession(realm);
// Create authentication session for specific client tab
String tabId = "tab1";
AuthenticationSessionModel authSession = rootSession.createAuthenticationSession(client, tabId);
// Set redirect URI
authSession.setRedirectUri("https://myapp.com/callback");
// Set authentication flow notes
authSession.setNote("login_hint", "john@example.com");
authSession.setNote("kc_locale", "en");
// Track authentication progress
authSession.setExecutionStatus("username-password-form", ExecutionStatus.SUCCESS);
authSession.setExecutionStatus("otp-form", ExecutionStatus.CHALLENGED);
// Set authenticated user after successful authentication
UserModel user = session.users().getUserByUsername(realm, "john");
authSession.setAuthenticatedUser(user);
// Add required actions
authSession.addRequiredAction(RequiredAction.UPDATE_PASSWORD);
authSession.addRequiredAction(RequiredAction.VERIFY_EMAIL);
// Set user session notes for later use
authSession.setUserSessionNote("login_ip", "192.168.1.100");
authSession.setUserSessionNote("login_time", String.valueOf(System.currentTimeMillis()));
}public class CustomAuthenticator implements Authenticator {
@Override
public void authenticate(AuthenticationFlowContext context) {
AuthenticationSessionModel authSession = context.getAuthenticationSession();
// Check if user is already identified
UserModel user = authSession.getAuthenticatedUser();
if (user == null) {
// Redirect to identification step
context.failure(AuthenticationFlowError.UNKNOWN_USER);
return;
}
// Check for previous attempts
String attempts = authSession.getNote("custom_auth_attempts");
int attemptCount = attempts != null ? Integer.parseInt(attempts) : 0;
if (attemptCount >= 3) {
context.failure(AuthenticationFlowError.TOO_MANY_FAILURES);
return;
}
// Generate challenge
String challenge = generateChallenge();
authSession.setNote("custom_challenge", challenge);
// Create challenge form
Response challengeForm = createChallengeForm(context, challenge);
context.challenge(challengeForm);
}
@Override
public void action(AuthenticationFlowContext context) {
AuthenticationSessionModel authSession = context.getAuthenticationSession();
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
String challenge = authSession.getNote("custom_challenge");
String response = formData.getFirst("challenge_response");
if (validateResponse(challenge, response)) {
// Success - clear attempt count
authSession.removeNote("custom_auth_attempts");
authSession.removeNote("custom_challenge");
context.success();
} else {
// Failed - increment attempt count
String attempts = authSession.getNote("custom_auth_attempts");
int attemptCount = attempts != null ? Integer.parseInt(attempts) : 0;
attemptCount++;
authSession.setNote("custom_auth_attempts", String.valueOf(attemptCount));
if (attemptCount >= 3) {
context.failure(AuthenticationFlowError.TOO_MANY_FAILURES);
} else {
Response errorForm = createChallengeForm(context, challenge, "Invalid response. Attempts: " + attemptCount);
context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, errorForm);
}
}
}
private String generateChallenge() {
// Generate random challenge
return UUID.randomUUID().toString();
}
private boolean validateResponse(String challenge, String response) {
// Validate the response against the challenge
return challenge != null && challenge.equals(response);
}
private Response createChallengeForm(AuthenticationFlowContext context, String challenge) {
return createChallengeForm(context, challenge, null);
}
private Response createChallengeForm(AuthenticationFlowContext context, String challenge, String error) {
// Create and return form response
return Response.ok().build(); // Simplified
}
}public class MultiStepAuthenticator implements Authenticator {
@Override
public void authenticate(AuthenticationFlowContext context) {
AuthenticationSessionModel authSession = context.getAuthenticationSession();
// Check current step
String currentStep = authSession.getNote("auth_step");
if (currentStep == null) {
currentStep = "step1";
}
switch (currentStep) {
case "step1":
handleStep1(context);
break;
case "step2":
handleStep2(context);
break;
case "step3":
handleStep3(context);
break;
default:
context.success();
}
}
private void handleStep1(AuthenticationFlowContext context) {
// First step - username/password
AuthenticationSessionModel authSession = context.getAuthenticationSession();
// Store step information
authSession.setNote("auth_step", "step1");
authSession.setNote("step1_start_time", String.valueOf(System.currentTimeMillis()));
// Challenge user for credentials
Response form = createUsernamePasswordForm(context);
context.challenge(form);
}
private void handleStep2(AuthenticationFlowContext context) {
// Second step - OTP verification
AuthenticationSessionModel authSession = context.getAuthenticationSession();
// Verify step1 was completed
if (!"step1_completed".equals(authSession.getNote("step1_status"))) {
context.failure(AuthenticationFlowError.INVALID_CREDENTIALS);
return;
}
authSession.setNote("auth_step", "step2");
authSession.setNote("step2_start_time", String.valueOf(System.currentTimeMillis()));
Response form = createOtpForm(context);
context.challenge(form);
}
private void handleStep3(AuthenticationFlowContext context) {
// Final step - additional verification
AuthenticationSessionModel authSession = context.getAuthenticationSession();
// Verify previous steps
if (!"step2_completed".equals(authSession.getNote("step2_status"))) {
context.failure(AuthenticationFlowError.INVALID_CREDENTIALS);
return;
}
// Set completion notes for user session
authSession.setUserSessionNote("multi_step_auth", "completed");
authSession.setUserSessionNote("auth_duration",
String.valueOf(System.currentTimeMillis() - Long.parseLong(authSession.getNote("step1_start_time"))));
context.success();
}
@Override
public void action(AuthenticationFlowContext context) {
AuthenticationSessionModel authSession = context.getAuthenticationSession();
String currentStep = authSession.getNote("auth_step");
switch (currentStep) {
case "step1":
if (processStep1Action(context)) {
authSession.setNote("step1_status", "step1_completed");
authSession.setNote("auth_step", "step2");
handleStep2(context);
}
break;
case "step2":
if (processStep2Action(context)) {
authSession.setNote("step2_status", "step2_completed");
authSession.setNote("auth_step", "step3");
handleStep3(context);
}
break;
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-keycloak--keycloak-server-spi@26.2.1