0
# Authentication State Management
1
2
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.
3
4
## Capabilities
5
6
### AuthOutcome Enum
7
8
Enumeration representing all possible outcomes of authentication attempts in Keycloak adapters.
9
10
```java { .api }
11
/**
12
* Enumeration representing possible outcomes of authentication attempts
13
*/
14
public enum AuthOutcome {
15
/** Authentication was not attempted */
16
NOT_ATTEMPTED,
17
18
/** Authentication failed */
19
FAILED,
20
21
/** Authentication successful */
22
AUTHENTICATED,
23
24
/** Not authenticated (but may have been attempted) */
25
NOT_AUTHENTICATED,
26
27
/** User logged out */
28
LOGGED_OUT
29
}
30
```
31
32
### KeycloakAccount Interface
33
34
Interface representing a Keycloak user account with access to the security principal and assigned roles.
35
36
```java { .api }
37
import java.security.Principal;
38
import java.util.Set;
39
40
/**
41
* Interface representing a Keycloak user account
42
*/
43
public interface KeycloakAccount {
44
/**
45
* Gets the security principal for this account
46
* @return Principal representing the authenticated user
47
*/
48
Principal getPrincipal();
49
50
/**
51
* Gets the set of roles assigned to this account
52
* @return Set of role names assigned to the user
53
*/
54
Set<String> getRoles();
55
}
56
```
57
58
### AuthChallenge Interface
59
60
Interface for handling authentication challenges across different protocols and platforms, enabling custom challenge implementations for various authentication flows.
61
62
```java { .api }
63
/**
64
* Interface for handling authentication challenges in different protocols and platforms
65
*/
66
public interface AuthChallenge {
67
/**
68
* Performs authentication challenge using the provided HTTP facade
69
* @param exchange HttpFacade for accessing request/response
70
* @return true if challenge was sent successfully, false otherwise
71
*/
72
boolean challenge(HttpFacade exchange);
73
74
/**
75
* Gets the error code that will be sent (needed by some platforms like Undertow)
76
* @return HTTP status code for the challenge response
77
*/
78
int getResponseCode();
79
}
80
```
81
82
### AuthenticationError Interface
83
84
Marker interface for authentication errors that can be extracted from HTTP request attributes. Protocol-specific implementations provide concrete error details.
85
86
```java { .api }
87
/**
88
* Common marker interface used by Keycloak client adapters when there is an error.
89
* For servlets, you'll be able to extract this error from the
90
* HttpServletRequest.getAttribute(AuthenticationError.class.getName()).
91
* Each protocol will have their own subclass of this interface.
92
*/
93
public interface AuthenticationError {
94
// Marker interface - specific protocols implement subclasses with error details
95
}
96
```
97
98
### LogoutError Interface
99
100
Marker interface for logout errors that can be extracted from HTTP request attributes. Protocol-specific implementations provide concrete error details.
101
102
```java { .api }
103
/**
104
* Common marker interface used by Keycloak client adapters when there is an error.
105
* For servlets, you'll be able to extract this error from the
106
* HttpServletRequest.getAttribute(LogoutError.class.getName()).
107
* Each protocol will have their own subclass of this interface.
108
*/
109
public interface LogoutError {
110
// Marker interface - specific protocols implement subclasses with error details
111
}
112
```
113
114
## Usage Examples
115
116
### Authentication Outcome Handling
117
118
```java
119
import org.keycloak.adapters.spi.AuthOutcome;
120
import org.keycloak.adapters.spi.KeycloakAccount;
121
122
public class AuthenticationHandler {
123
124
public void handleAuthenticationResult(AuthOutcome outcome, KeycloakAccount account) {
125
switch (outcome) {
126
case AUTHENTICATED:
127
handleSuccessfulAuth(account);
128
break;
129
130
case FAILED:
131
handleAuthFailure();
132
break;
133
134
case NOT_ATTEMPTED:
135
initiateAuthentication();
136
break;
137
138
case NOT_AUTHENTICATED:
139
handleNotAuthenticated();
140
break;
141
142
case LOGGED_OUT:
143
handleLogout();
144
break;
145
146
default:
147
handleUnknownOutcome(outcome);
148
}
149
}
150
151
private void handleSuccessfulAuth(KeycloakAccount account) {
152
if (account != null) {
153
Principal principal = account.getPrincipal();
154
Set<String> roles = account.getRoles();
155
156
System.out.println("User authenticated: " + principal.getName());
157
System.out.println("User roles: " + roles);
158
159
// Check for specific roles
160
if (roles.contains("admin")) {
161
grantAdminAccess();
162
}
163
}
164
}
165
166
private void handleAuthFailure() {
167
// Log failure and redirect to login
168
System.err.println("Authentication failed");
169
redirectToLogin();
170
}
171
172
private void initiateAuthentication() {
173
// Start authentication flow
174
redirectToLogin();
175
}
176
177
private void handleNotAuthenticated() {
178
// User session exists but not authenticated
179
clearSession();
180
redirectToLogin();
181
}
182
183
private void handleLogout() {
184
// Clean up after logout
185
clearSession();
186
redirectToHome();
187
}
188
189
// Helper methods
190
private void grantAdminAccess() { /* implementation */ }
191
private void redirectToLogin() { /* implementation */ }
192
private void clearSession() { /* implementation */ }
193
private void redirectToHome() { /* implementation */ }
194
private void handleUnknownOutcome(AuthOutcome outcome) { /* implementation */ }
195
}
196
```
197
198
### Custom Authentication Challenge
199
200
```java
201
import org.keycloak.adapters.spi.AuthChallenge;
202
import org.keycloak.adapters.spi.HttpFacade;
203
204
public class BasicAuthChallenge implements AuthChallenge {
205
private final String realm;
206
private final int responseCode;
207
208
public BasicAuthChallenge(String realm) {
209
this.realm = realm;
210
this.responseCode = 401; // Unauthorized
211
}
212
213
@Override
214
public boolean challenge(HttpFacade exchange) {
215
try {
216
HttpFacade.Response response = exchange.getResponse();
217
218
// Set WWW-Authenticate header for Basic auth
219
response.setHeader("WWW-Authenticate", "Basic realm=\"" + realm + "\"");
220
response.setStatus(401);
221
response.end();
222
223
return true;
224
} catch (Exception e) {
225
System.err.println("Failed to send auth challenge: " + e.getMessage());
226
return false;
227
}
228
}
229
230
@Override
231
public int getResponseCode() {
232
return responseCode;
233
}
234
}
235
236
// Usage
237
AuthChallenge challenge = new BasicAuthChallenge("MyApp");
238
boolean challengeSent = challenge.challenge(httpFacade);
239
if (challengeSent) {
240
System.out.println("Authentication challenge sent with code: " + challenge.getResponseCode());
241
}
242
```
243
244
### Error Handling Implementation
245
246
```java
247
import org.keycloak.adapters.spi.AuthenticationError;
248
import org.keycloak.adapters.spi.LogoutError;
249
250
// Custom authentication error implementation
251
public class OidcAuthenticationError implements AuthenticationError {
252
private final String error;
253
private final String errorDescription;
254
private final String errorUri;
255
256
public OidcAuthenticationError(String error, String errorDescription, String errorUri) {
257
this.error = error;
258
this.errorDescription = errorDescription;
259
this.errorUri = errorUri;
260
}
261
262
public String getError() { return error; }
263
public String getErrorDescription() { return errorDescription; }
264
public String getErrorUri() { return errorUri; }
265
}
266
267
// Custom logout error implementation
268
public class OidcLogoutError implements LogoutError {
269
private final String error;
270
private final String errorDescription;
271
272
public OidcLogoutError(String error, String errorDescription) {
273
this.error = error;
274
this.errorDescription = errorDescription;
275
}
276
277
public String getError() { return error; }
278
public String getErrorDescription() { return errorDescription; }
279
}
280
281
// Error handling in adapter
282
public class ErrorHandler {
283
284
public void handleErrors(HttpFacade.Request request) {
285
// Check for authentication errors
286
AuthenticationError authError = (AuthenticationError)
287
request.getAttribute(AuthenticationError.class.getName());
288
289
if (authError instanceof OidcAuthenticationError) {
290
OidcAuthenticationError oidcError = (OidcAuthenticationError) authError;
291
System.err.println("Authentication error: " + oidcError.getError());
292
System.err.println("Description: " + oidcError.getErrorDescription());
293
}
294
295
// Check for logout errors
296
LogoutError logoutError = (LogoutError)
297
request.getAttribute(LogoutError.class.getName());
298
299
if (logoutError instanceof OidcLogoutError) {
300
OidcLogoutError oidcLogoutError = (OidcLogoutError) logoutError;
301
System.err.println("Logout error: " + oidcLogoutError.getError());
302
System.err.println("Description: " + oidcLogoutError.getErrorDescription());
303
}
304
}
305
}
306
```
307
308
### User Account Implementation
309
310
```java
311
import org.keycloak.adapters.spi.KeycloakAccount;
312
import java.security.Principal;
313
import java.util.Set;
314
import java.util.HashSet;
315
316
public class MyKeycloakAccount implements KeycloakAccount {
317
private final Principal principal;
318
private final Set<String> roles;
319
320
public MyKeycloakAccount(String username, Set<String> roles) {
321
this.principal = new SimplePrincipal(username);
322
this.roles = new HashSet<>(roles);
323
}
324
325
@Override
326
public Principal getPrincipal() {
327
return principal;
328
}
329
330
@Override
331
public Set<String> getRoles() {
332
return new HashSet<>(roles); // Return defensive copy
333
}
334
335
// Helper method to check role membership
336
public boolean hasRole(String roleName) {
337
return roles.contains(roleName);
338
}
339
340
// Helper method to check if user is admin
341
public boolean isAdmin() {
342
return hasRole("admin") || hasRole("administrator");
343
}
344
345
// Simple Principal implementation
346
private static class SimplePrincipal implements Principal {
347
private final String name;
348
349
public SimplePrincipal(String name) {
350
this.name = name;
351
}
352
353
@Override
354
public String getName() {
355
return name;
356
}
357
358
@Override
359
public String toString() {
360
return "Principal[" + name + "]";
361
}
362
}
363
}
364
365
// Usage
366
Set<String> userRoles = Set.of("user", "manager", "api-access");
367
KeycloakAccount account = new MyKeycloakAccount("john.doe", userRoles);
368
369
System.out.println("User: " + account.getPrincipal().getName());
370
System.out.println("Roles: " + account.getRoles());
371
372
// Check specific permissions
373
if (account.getRoles().contains("manager")) {
374
// Grant manager-level access
375
}
376
```