Service Provider Interface for Keycloak authentication adapters across different application server environments
—
Authentication outcome tracking, user account representation, and error handling for comprehensive authentication flow management. These interfaces provide standardized ways to handle authentication states and errors across different Keycloak adapter implementations.
Enumeration representing all possible outcomes of authentication attempts in Keycloak adapters.
/**
* Enumeration representing possible outcomes of authentication attempts
*/
public enum AuthOutcome {
/** Authentication was not attempted */
NOT_ATTEMPTED,
/** Authentication failed */
FAILED,
/** Authentication successful */
AUTHENTICATED,
/** Not authenticated (but may have been attempted) */
NOT_AUTHENTICATED,
/** User logged out */
LOGGED_OUT
}Interface representing a Keycloak user account with access to the security principal and assigned roles.
import java.security.Principal;
import java.util.Set;
/**
* Interface representing a Keycloak user account
*/
public interface KeycloakAccount {
/**
* Gets the security principal for this account
* @return Principal representing the authenticated user
*/
Principal getPrincipal();
/**
* Gets the set of roles assigned to this account
* @return Set of role names assigned to the user
*/
Set<String> getRoles();
}Interface for handling authentication challenges across different protocols and platforms, enabling custom challenge implementations for various authentication flows.
/**
* Interface for handling authentication challenges in different protocols and platforms
*/
public interface AuthChallenge {
/**
* Performs authentication challenge using the provided HTTP facade
* @param exchange HttpFacade for accessing request/response
* @return true if challenge was sent successfully, false otherwise
*/
boolean challenge(HttpFacade exchange);
/**
* Gets the error code that will be sent (needed by some platforms like Undertow)
* @return HTTP status code for the challenge response
*/
int getResponseCode();
}Marker interface for authentication errors that can be extracted from HTTP request attributes. Protocol-specific implementations provide concrete error details.
/**
* Common marker interface used by Keycloak client adapters when there is an error.
* For servlets, you'll be able to extract this error from the
* HttpServletRequest.getAttribute(AuthenticationError.class.getName()).
* Each protocol will have their own subclass of this interface.
*/
public interface AuthenticationError {
// Marker interface - specific protocols implement subclasses with error details
}Marker interface for logout errors that can be extracted from HTTP request attributes. Protocol-specific implementations provide concrete error details.
/**
* Common marker interface used by Keycloak client adapters when there is an error.
* For servlets, you'll be able to extract this error from the
* HttpServletRequest.getAttribute(LogoutError.class.getName()).
* Each protocol will have their own subclass of this interface.
*/
public interface LogoutError {
// Marker interface - specific protocols implement subclasses with error details
}import org.keycloak.adapters.spi.AuthOutcome;
import org.keycloak.adapters.spi.KeycloakAccount;
public class AuthenticationHandler {
public void handleAuthenticationResult(AuthOutcome outcome, KeycloakAccount account) {
switch (outcome) {
case AUTHENTICATED:
handleSuccessfulAuth(account);
break;
case FAILED:
handleAuthFailure();
break;
case NOT_ATTEMPTED:
initiateAuthentication();
break;
case NOT_AUTHENTICATED:
handleNotAuthenticated();
break;
case LOGGED_OUT:
handleLogout();
break;
default:
handleUnknownOutcome(outcome);
}
}
private void handleSuccessfulAuth(KeycloakAccount account) {
if (account != null) {
Principal principal = account.getPrincipal();
Set<String> roles = account.getRoles();
System.out.println("User authenticated: " + principal.getName());
System.out.println("User roles: " + roles);
// Check for specific roles
if (roles.contains("admin")) {
grantAdminAccess();
}
}
}
private void handleAuthFailure() {
// Log failure and redirect to login
System.err.println("Authentication failed");
redirectToLogin();
}
private void initiateAuthentication() {
// Start authentication flow
redirectToLogin();
}
private void handleNotAuthenticated() {
// User session exists but not authenticated
clearSession();
redirectToLogin();
}
private void handleLogout() {
// Clean up after logout
clearSession();
redirectToHome();
}
// Helper methods
private void grantAdminAccess() { /* implementation */ }
private void redirectToLogin() { /* implementation */ }
private void clearSession() { /* implementation */ }
private void redirectToHome() { /* implementation */ }
private void handleUnknownOutcome(AuthOutcome outcome) { /* implementation */ }
}import org.keycloak.adapters.spi.AuthChallenge;
import org.keycloak.adapters.spi.HttpFacade;
public class BasicAuthChallenge implements AuthChallenge {
private final String realm;
private final int responseCode;
public BasicAuthChallenge(String realm) {
this.realm = realm;
this.responseCode = 401; // Unauthorized
}
@Override
public boolean challenge(HttpFacade exchange) {
try {
HttpFacade.Response response = exchange.getResponse();
// Set WWW-Authenticate header for Basic auth
response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
response.setStatus(401);
response.end();
return true;
} catch (Exception e) {
System.err.println("Failed to send auth challenge: " + e.getMessage());
return false;
}
}
@Override
public int getResponseCode() {
return responseCode;
}
}
// Usage
AuthChallenge challenge = new BasicAuthChallenge("MyApp");
boolean challengeSent = challenge.challenge(httpFacade);
if (challengeSent) {
System.out.println("Authentication challenge sent with code: " + challenge.getResponseCode());
}import org.keycloak.adapters.spi.AuthenticationError;
import org.keycloak.adapters.spi.LogoutError;
// Custom authentication error implementation
public class OidcAuthenticationError implements AuthenticationError {
private final String error;
private final String errorDescription;
private final String errorUri;
public OidcAuthenticationError(String error, String errorDescription, String errorUri) {
this.error = error;
this.errorDescription = errorDescription;
this.errorUri = errorUri;
}
public String getError() { return error; }
public String getErrorDescription() { return errorDescription; }
public String getErrorUri() { return errorUri; }
}
// Custom logout error implementation
public class OidcLogoutError implements LogoutError {
private final String error;
private final String errorDescription;
public OidcLogoutError(String error, String errorDescription) {
this.error = error;
this.errorDescription = errorDescription;
}
public String getError() { return error; }
public String getErrorDescription() { return errorDescription; }
}
// Error handling in adapter
public class ErrorHandler {
public void handleErrors(HttpFacade.Request request) {
// Check for authentication errors
AuthenticationError authError = (AuthenticationError)
request.getAttribute(AuthenticationError.class.getName());
if (authError instanceof OidcAuthenticationError) {
OidcAuthenticationError oidcError = (OidcAuthenticationError) authError;
System.err.println("Authentication error: " + oidcError.getError());
System.err.println("Description: " + oidcError.getErrorDescription());
}
// Check for logout errors
LogoutError logoutError = (LogoutError)
request.getAttribute(LogoutError.class.getName());
if (logoutError instanceof OidcLogoutError) {
OidcLogoutError oidcLogoutError = (OidcLogoutError) logoutError;
System.err.println("Logout error: " + oidcLogoutError.getError());
System.err.println("Description: " + oidcLogoutError.getErrorDescription());
}
}
}import org.keycloak.adapters.spi.KeycloakAccount;
import java.security.Principal;
import java.util.Set;
import java.util.HashSet;
public class MyKeycloakAccount implements KeycloakAccount {
private final Principal principal;
private final Set<String> roles;
public MyKeycloakAccount(String username, Set<String> roles) {
this.principal = new SimplePrincipal(username);
this.roles = new HashSet<>(roles);
}
@Override
public Principal getPrincipal() {
return principal;
}
@Override
public Set<String> getRoles() {
return new HashSet<>(roles); // Return defensive copy
}
// Helper method to check role membership
public boolean hasRole(String roleName) {
return roles.contains(roleName);
}
// Helper method to check if user is admin
public boolean isAdmin() {
return hasRole("admin") || hasRole("administrator");
}
// Simple Principal implementation
private static class SimplePrincipal implements Principal {
private final String name;
public SimplePrincipal(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public String toString() {
return "Principal[" + name + "]";
}
}
}
// Usage
Set<String> userRoles = Set.of("user", "manager", "api-access");
KeycloakAccount account = new MyKeycloakAccount("john.doe", userRoles);
System.out.println("User: " + account.getPrincipal().getName());
System.out.println("Roles: " + account.getRoles());
// Check specific permissions
if (account.getRoles().contains("manager")) {
// Grant manager-level access
}Install with Tessl CLI
npx tessl i tessl/maven-org-keycloak--keycloak-adapter-spi