0
# Token Storage
1
2
Token storage abstraction and utilities for managing token lifecycle, including cookie-based storage, token refresh operations, and storage strategy implementations. This module provides flexible token persistence strategies for different deployment scenarios.
3
4
## Capabilities
5
6
### AdapterTokenStore
7
8
Interface defining the contract for token storage implementations on the adapter side.
9
10
```java { .api }
11
/**
12
* Interface defining the contract for token storage implementations (per-request object)
13
*/
14
public interface AdapterTokenStore {
15
/**
16
* Check the current token validity and refresh if necessary
17
*/
18
void checkCurrentToken();
19
20
/**
21
* Check if authenticator results are cached
22
* @param authenticator The request authenticator to check
23
* @return true if cached, false otherwise
24
*/
25
boolean isCached(RequestAuthenticator authenticator);
26
27
/**
28
* Save account information after successful authentication
29
* @param account Authenticated account to store
30
*/
31
void saveAccountInfo(OidcKeycloakAccount account);
32
33
/**
34
* Logout and clear stored token information
35
*/
36
void logout();
37
38
/**
39
* Callback invoked after token refresh
40
* @param securityContext Updated security context with new tokens
41
*/
42
void refreshCallback(RefreshableKeycloakSecurityContext securityContext);
43
}
44
```
45
46
**Usage Examples:**
47
48
```java
49
// Custom token store implementation
50
public class DatabaseTokenStore implements AdapterTokenStore {
51
private final String sessionId;
52
private final TokenRepository tokenRepository;
53
54
public DatabaseTokenStore(String sessionId, TokenRepository repository) {
55
this.sessionId = sessionId;
56
this.tokenRepository = repository;
57
}
58
59
@Override
60
public void checkCurrentToken() {
61
TokenInfo tokenInfo = tokenRepository.findBySessionId(sessionId);
62
if (tokenInfo != null && tokenInfo.isExpired()) {
63
// Token expired, trigger refresh
64
refreshToken(tokenInfo);
65
}
66
}
67
68
@Override
69
public boolean isCached(RequestAuthenticator authenticator) {
70
return tokenRepository.existsBySessionId(sessionId);
71
}
72
73
@Override
74
public void saveAccountInfo(OidcKeycloakAccount account) {
75
RefreshableKeycloakSecurityContext context =
76
(RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
77
78
TokenInfo tokenInfo = new TokenInfo();
79
tokenInfo.setSessionId(sessionId);
80
tokenInfo.setAccessToken(context.getTokenString());
81
tokenInfo.setRefreshToken(context.getRefreshToken());
82
tokenInfo.setIdToken(context.getIdTokenString());
83
tokenInfo.setExpiresAt(context.getToken().getExpiration());
84
85
tokenRepository.save(tokenInfo);
86
}
87
88
@Override
89
public void logout() {
90
tokenRepository.deleteBySessionId(sessionId);
91
}
92
93
@Override
94
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
95
// Update stored tokens after refresh
96
TokenInfo tokenInfo = tokenRepository.findBySessionId(sessionId);
97
if (tokenInfo != null) {
98
tokenInfo.setAccessToken(securityContext.getTokenString());
99
tokenInfo.setRefreshToken(securityContext.getRefreshToken());
100
tokenInfo.setExpiresAt(securityContext.getToken().getExpiration());
101
tokenRepository.save(tokenInfo);
102
}
103
}
104
}
105
```
106
107
### CookieTokenStore
108
109
Utility class for cookie-based token storage operations.
110
111
```java { .api }
112
/**
113
* Utility class for cookie-based token storage operations
114
*/
115
public class CookieTokenStore {
116
/**
117
* Set authentication token as a secure cookie
118
* @param deployment Keycloak deployment configuration
119
* @param facade HTTP facade for cookie operations
120
* @param session Security context containing token information
121
*/
122
public static void setTokenCookie(
123
KeycloakDeployment deployment,
124
HttpFacade facade,
125
RefreshableKeycloakSecurityContext session
126
);
127
128
/**
129
* Retrieve principal from authentication cookie
130
* @param deployment Keycloak deployment configuration
131
* @param facade HTTP facade for cookie operations
132
* @param tokenStore Token store for caching
133
* @return KeycloakPrincipal if valid cookie found, null otherwise
134
*/
135
public static KeycloakPrincipal<RefreshableKeycloakSecurityContext> getPrincipalFromCookie(
136
KeycloakDeployment deployment,
137
HttpFacade facade,
138
AdapterTokenStore tokenStore
139
);
140
141
/**
142
* Remove authentication cookie
143
* @param deployment Keycloak deployment configuration
144
* @param facade HTTP facade for cookie operations
145
*/
146
public static void removeCookie(KeycloakDeployment deployment, HttpFacade facade);
147
}
148
```
149
150
**Usage Examples:**
151
152
```java
153
// Set authentication cookie after successful login
154
RefreshableKeycloakSecurityContext securityContext =
155
(RefreshableKeycloakSecurityContext) principal.getKeycloakSecurityContext();
156
CookieTokenStore.setTokenCookie(deployment, httpFacade, securityContext);
157
158
// Retrieve principal from cookie on subsequent requests
159
KeycloakPrincipal<RefreshableKeycloakSecurityContext> principal =
160
CookieTokenStore.getPrincipalFromCookie(deployment, httpFacade, tokenStore);
161
162
if (principal != null) {
163
// User authenticated via cookie
164
RefreshableKeycloakSecurityContext context = principal.getKeycloakSecurityContext();
165
AccessToken token = context.getToken();
166
167
// Check if token needs refresh
168
if (!context.isTokenTimeToLiveSufficient(token)) {
169
boolean refreshed = context.refreshExpiredToken(true);
170
if (refreshed) {
171
// Update cookie with new tokens
172
CookieTokenStore.setTokenCookie(deployment, httpFacade, context);
173
} else {
174
// Refresh failed, remove invalid cookie
175
CookieTokenStore.removeCookie(deployment, httpFacade);
176
}
177
}
178
}
179
180
// Logout - remove cookie
181
CookieTokenStore.removeCookie(deployment, httpFacade);
182
```
183
184
## Token Storage Patterns
185
186
### Session-Based Storage
187
188
```java
189
// HTTP session-based token store
190
public class SessionTokenStore implements AdapterTokenStore {
191
private final HttpSession session;
192
private static final String ACCOUNT_KEY = "keycloak.account";
193
194
public SessionTokenStore(HttpSession session) {
195
this.session = session;
196
}
197
198
@Override
199
public void saveAccountInfo(OidcKeycloakAccount account) {
200
session.setAttribute(ACCOUNT_KEY, account);
201
}
202
203
@Override
204
public boolean isCached(RequestAuthenticator authenticator) {
205
return session.getAttribute(ACCOUNT_KEY) != null;
206
}
207
208
@Override
209
public void logout() {
210
session.removeAttribute(ACCOUNT_KEY);
211
session.invalidate();
212
}
213
214
public OidcKeycloakAccount getAccount() {
215
return (OidcKeycloakAccount) session.getAttribute(ACCOUNT_KEY);
216
}
217
}
218
```
219
220
### Redis-Based Storage
221
222
```java
223
// Redis-based distributed token store
224
public class RedisTokenStore implements AdapterTokenStore {
225
private final RedisTemplate<String, Object> redisTemplate;
226
private final String sessionId;
227
private final Duration tokenTtl;
228
229
public RedisTokenStore(RedisTemplate<String, Object> redisTemplate,
230
String sessionId, Duration tokenTtl) {
231
this.redisTemplate = redisTemplate;
232
this.sessionId = sessionId;
233
this.tokenTtl = tokenTtl;
234
}
235
236
@Override
237
public void saveAccountInfo(OidcKeycloakAccount account) {
238
String key = "keycloak:session:" + sessionId;
239
RefreshableKeycloakSecurityContext context =
240
(RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
241
242
Map<String, Object> tokenData = new HashMap<>();
243
tokenData.put("accessToken", context.getTokenString());
244
tokenData.put("refreshToken", context.getRefreshToken());
245
tokenData.put("idToken", context.getIdTokenString());
246
tokenData.put("principal", account.getPrincipal());
247
tokenData.put("roles", account.getRoles());
248
249
redisTemplate.opsForHash().putAll(key, tokenData);
250
redisTemplate.expire(key, tokenTtl);
251
}
252
253
@Override
254
public boolean isCached(RequestAuthenticator authenticator) {
255
String key = "keycloak:session:" + sessionId;
256
return redisTemplate.hasKey(key);
257
}
258
259
@Override
260
public void logout() {
261
String key = "keycloak:session:" + sessionId;
262
redisTemplate.delete(key);
263
}
264
265
@Override
266
public void refreshCallback(RefreshableKeycloakSecurityContext securityContext) {
267
// Update tokens in Redis after refresh
268
String key = "keycloak:session:" + sessionId;
269
if (redisTemplate.hasKey(key)) {
270
redisTemplate.opsForHash().put(key, "accessToken", securityContext.getTokenString());
271
redisTemplate.opsForHash().put(key, "refreshToken", securityContext.getRefreshToken());
272
redisTemplate.opsForHash().put(key, "idToken", securityContext.getIdTokenString());
273
redisTemplate.expire(key, tokenTtl);
274
}
275
}
276
}
277
```
278
279
### JWT Cookie Storage
280
281
```java
282
// Custom JWT-based cookie storage
283
public class JwtCookieTokenStore implements AdapterTokenStore {
284
private final HttpFacade facade;
285
private final KeycloakDeployment deployment;
286
private final String cookieName;
287
288
public JwtCookieTokenStore(HttpFacade facade, KeycloakDeployment deployment) {
289
this.facade = facade;
290
this.deployment = deployment;
291
this.cookieName = deployment.getStateCookieName() + "_token";
292
}
293
294
@Override
295
public void saveAccountInfo(OidcKeycloakAccount account) {
296
RefreshableKeycloakSecurityContext context =
297
(RefreshableKeycloakSecurityContext) account.getKeycloakSecurityContext();
298
299
// Create compact token info for cookie
300
Map<String, Object> tokenInfo = new HashMap<>();
301
tokenInfo.put("sub", context.getToken().getSubject());
302
tokenInfo.put("exp", context.getToken().getExpiration());
303
tokenInfo.put("roles", account.getRoles());
304
tokenInfo.put("rt", context.getRefreshToken()); // Refresh token
305
306
// Sign and serialize as JWT
307
String jwtToken = createSignedJwt(tokenInfo);
308
309
// Set secure cookie
310
Cookie cookie = new Cookie(cookieName, jwtToken);
311
cookie.setPath(deployment.getAdapterStateCookiePath());
312
cookie.setSecure(deployment.getSslRequired() == SslRequired.ALL);
313
cookie.setHttpOnly(true);
314
cookie.setMaxAge((int) (context.getToken().getExpiration() - System.currentTimeMillis() / 1000));
315
316
facade.getResponse().setCookie(cookie);
317
}
318
319
@Override
320
public boolean isCached(RequestAuthenticator authenticator) {
321
Cookie cookie = facade.getRequest().getCookie(cookieName);
322
return cookie != null && isValidJwtCookie(cookie.getValue());
323
}
324
325
@Override
326
public void logout() {
327
Cookie cookie = new Cookie(cookieName, "");
328
cookie.setPath(deployment.getAdapterStateCookiePath());
329
cookie.setMaxAge(0);
330
facade.getResponse().setCookie(cookie);
331
}
332
333
private String createSignedJwt(Map<String, Object> claims) {
334
// Implementation depends on JWT library (e.g., jose4j, nimbus-jose-jwt)
335
// Sign with deployment's client secret or private key
336
return "signed.jwt.token";
337
}
338
339
private boolean isValidJwtCookie(String jwt) {
340
try {
341
// Verify JWT signature and expiration
342
Map<String, Object> claims = parseAndVerifyJwt(jwt);
343
long exp = (Long) claims.get("exp");
344
return exp > System.currentTimeMillis() / 1000;
345
} catch (Exception e) {
346
return false;
347
}
348
}
349
}
350
```
351
352
## Storage Configuration
353
354
### Deployment Configuration
355
356
```java
357
// Configure token store in deployment
358
KeycloakDeployment deployment = new KeycloakDeployment();
359
deployment.setTokenStore(TokenStore.COOKIE); // or TokenStore.SESSION
360
361
// Configure cookie settings
362
deployment.setAdapterStateCookiePath("/app");
363
deployment.setStateCookieName("KEYCLOAK_ADAPTER");
364
365
// For custom storage implementations
366
public class CustomAdapterTokenStoreFactory {
367
public static AdapterTokenStore create(HttpFacade facade, KeycloakDeployment deployment) {
368
TokenStore tokenStore = deployment.getTokenStore();
369
370
switch (tokenStore) {
371
case COOKIE:
372
return new CookieBasedTokenStore(facade, deployment);
373
case SESSION:
374
return new SessionTokenStore(facade.getRequest().getSession());
375
default:
376
// Custom implementation
377
return new RedisTokenStore(getRedisTemplate(), getSessionId(facade), Duration.ofHours(1));
378
}
379
}
380
}
381
```