CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-keycloak--keycloak-adapter-spi

Service Provider Interface for Keycloak authentication adapters across different application server environments

Pending
Overview
Eval results
Files

authentication.mddocs/

Authentication State Management

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.

Capabilities

AuthOutcome Enum

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
}

KeycloakAccount Interface

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();
}

AuthChallenge Interface

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();
}

AuthenticationError Interface

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
}

LogoutError Interface

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
}

Usage Examples

Authentication Outcome Handling

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 */ }
}

Custom Authentication Challenge

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());
}

Error Handling Implementation

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());
        }
    }
}

User Account Implementation

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

docs

authentication.md

http-facade.md

index.md

session-management.md

tile.json