or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdcore-adapters.mdhttp-operations.mdindex.mdjaas-integration.mdkey-rotation.mdpolicy-enforcement.mdtoken-storage.mdutility-operations.md

token-storage.mddocs/

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

```