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

key-rotation.mddocs/

0

# Key Rotation

1

2

Public key location and token verification utilities supporting key rotation for secure token validation. This module provides flexible key management strategies and comprehensive token verification capabilities.

3

4

## Capabilities

5

6

### PublicKeyLocator

7

8

Interface for locating public keys used in token verification with support for key rotation.

9

10

```java { .api }

11

/**

12

* Interface for locating public keys used in token verification with support for key rotation

13

*/

14

public interface PublicKeyLocator {

15

/**

16

* Get public key for token verification

17

* @param kid Key identifier from token header, may be null

18

* @param deployment Keycloak deployment configuration

19

* @return PublicKey for verification or null if not found

20

*/

21

PublicKey getPublicKey(String kid, KeycloakDeployment deployment);

22

23

/**

24

* Reset/clear cached keys, forcing fresh retrieval

25

* @param deployment Keycloak deployment configuration

26

*/

27

void reset(KeycloakDeployment deployment);

28

}

29

```

30

31

### HardcodedPublicKeyLocator

32

33

Public key locator implementation using a single hardcoded public key.

34

35

```java { .api }

36

/**

37

* Public key locator implementation using a single hardcoded public key

38

*/

39

public class HardcodedPublicKeyLocator implements PublicKeyLocator {

40

/**

41

* Constructor with hardcoded public key

42

* @param publicKey Public key to use for all token verification

43

*/

44

public HardcodedPublicKeyLocator(PublicKey publicKey);

45

46

/**

47

* Get the hardcoded public key

48

* @param kid Key identifier (ignored)

49

* @param deployment Keycloak deployment (ignored)

50

* @return The configured public key

51

*/

52

public PublicKey getPublicKey(String kid, KeycloakDeployment deployment);

53

54

/**

55

* Reset operation (no-op for hardcoded key)

56

* @param deployment Keycloak deployment (ignored)

57

*/

58

public void reset(KeycloakDeployment deployment);

59

}

60

```

61

62

**Usage Examples:**

63

64

```java

65

// Create hardcoded key locator from certificate

66

Certificate cert = loadCertificateFromFile("/path/to/realm-certificate.pem");

67

PublicKey publicKey = cert.getPublicKey();

68

PublicKeyLocator keyLocator = new HardcodedPublicKeyLocator(publicKey);

69

70

// Configure in deployment

71

KeycloakDeployment deployment = new KeycloakDeployment();

72

deployment.setPublicKeyLocator(keyLocator);

73

74

// Use for token verification

75

AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);

76

```

77

78

### JWKPublicKeyLocator

79

80

Public key locator that downloads keys from Keycloak's JSON Web Key Set (JWKS) endpoint with caching and rotation support.

81

82

```java { .api }

83

/**

84

* Public key locator that downloads keys from Keycloak's JWKS endpoint with caching and rotation support

85

*/

86

public class JWKPublicKeyLocator implements PublicKeyLocator {

87

/**

88

* Default constructor

89

*/

90

public JWKPublicKeyLocator();

91

92

/**

93

* Get public key from JWKS endpoint, with caching

94

* @param kid Key identifier from token header

95

* @param deployment Keycloak deployment configuration

96

* @return PublicKey for verification or null if not found

97

*/

98

public PublicKey getPublicKey(String kid, KeycloakDeployment deployment);

99

100

/**

101

* Reset cached keys, forcing fresh download from JWKS endpoint

102

* @param deployment Keycloak deployment configuration

103

*/

104

public void reset(KeycloakDeployment deployment);

105

}

106

```

107

108

**Usage Examples:**

109

110

```java

111

// Create JWKS-based key locator (recommended for production)

112

PublicKeyLocator keyLocator = new JWKPublicKeyLocator();

113

114

// Configure deployment with JWKS support

115

KeycloakDeployment deployment = new KeycloakDeployment();

116

deployment.setPublicKeyLocator(keyLocator);

117

deployment.setMinTimeBetweenJwksRequests(10); // Minimum 10 seconds between JWKS requests

118

deployment.setPublicKeyCacheTtl(3600); // Cache keys for 1 hour

119

120

// The locator will automatically:

121

// 1. Download JWKS from deployment.getJwksUrl()

122

// 2. Cache keys with TTL

123

// 3. Handle key rotation automatically

124

// 4. Respect rate limiting for JWKS requests

125

126

// Token verification with automatic key resolution

127

try {

128

AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);

129

// Token verified successfully with appropriate key

130

} catch (VerificationException e) {

131

// Verification failed - could be due to key rotation

132

// Reset keys and retry once

133

keyLocator.reset(deployment);

134

try {

135

AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);

136

} catch (VerificationException retryEx) {

137

// Verification failed after key refresh

138

logger.warn("Token verification failed: {}", retryEx.getMessage());

139

}

140

}

141

```

142

143

### AdapterTokenVerifier

144

145

Utility class for token verification with integrated public key resolution.

146

147

```java { .api }

148

/**

149

* Utility class for token verification with integrated public key resolution

150

*/

151

public class AdapterTokenVerifier {

152

/**

153

* Container for verified tokens

154

*/

155

public static class VerifiedTokens {

156

// Implementation details - contains verified access and ID tokens

157

}

158

159

/**

160

* Verify access token using deployment configuration

161

* @param tokenString Raw JWT token string

162

* @param deployment Keycloak deployment with key locator

163

* @return Verified AccessToken instance

164

* @throws VerificationException If token verification fails

165

*/

166

public static AccessToken verifyToken(String tokenString, KeycloakDeployment deployment)

167

throws VerificationException;

168

169

/**

170

* Verify both access token and ID token

171

* @param accessTokenString Raw access token JWT string

172

* @param idTokenString Raw ID token JWT string

173

* @param deployment Keycloak deployment with key locator

174

* @return VerifiedTokens container with both verified tokens

175

* @throws VerificationException If either token verification fails

176

*/

177

public static VerifiedTokens verifyTokens(

178

String accessTokenString,

179

String idTokenString,

180

KeycloakDeployment deployment

181

) throws VerificationException;

182

183

/**

184

* Create token verifier with custom configuration

185

* @param tokenString Raw JWT token string

186

* @param deployment Keycloak deployment configuration

187

* @param withDefaultChecks Whether to include default verification checks

188

* @param tokenClass Class of token to verify (AccessToken, IDToken, etc.)

189

* @return Configured TokenVerifier instance

190

* @throws VerificationException If verifier creation fails

191

*/

192

public static <T extends JsonWebToken> TokenVerifier<T> createVerifier(

193

String tokenString,

194

KeycloakDeployment deployment,

195

boolean withDefaultChecks,

196

Class<T> tokenClass

197

) throws VerificationException;

198

}

199

```

200

201

**Usage Examples:**

202

203

```java

204

// Basic token verification

205

try {

206

AccessToken accessToken = AdapterTokenVerifier.verifyToken(tokenString, deployment);

207

208

// Token is valid, extract information

209

String username = accessToken.getPreferredUsername();

210

String email = accessToken.getEmail();

211

Set<String> roles = accessToken.getRealmAccess().getRoles();

212

long expiration = accessToken.getExpiration();

213

214

// Check if token is still valid (not expired)

215

if (expiration < System.currentTimeMillis() / 1000) {

216

logger.warn("Token has expired");

217

}

218

219

} catch (VerificationException e) {

220

logger.warn("Token verification failed: {}", e.getMessage());

221

// Handle invalid token (return 401, redirect to login, etc.)

222

}

223

224

// Verify both access and ID tokens

225

try {

226

AdapterTokenVerifier.VerifiedTokens tokens = AdapterTokenVerifier.verifyTokens(

227

accessTokenString,

228

idTokenString,

229

deployment

230

);

231

232

// Both tokens verified successfully

233

// Access token information and ID token information are available

234

235

} catch (VerificationException e) {

236

logger.warn("Token verification failed: {}", e.getMessage());

237

}

238

239

// Custom token verification with specific checks

240

try {

241

TokenVerifier<AccessToken> verifier = AdapterTokenVerifier.createVerifier(

242

tokenString,

243

deployment,

244

true, // Include default checks (signature, expiration, audience, etc.)

245

AccessToken.class

246

);

247

248

// Add custom verification checks

249

verifier.check(token -> {

250

// Custom validation logic

251

if (!token.getIssuer().equals(expectedIssuer)) {

252

throw new VerificationException("Invalid issuer");

253

}

254

255

// Check custom claims

256

Object customClaim = token.getOtherClaims().get("custom_claim");

257

if (customClaim == null) {

258

throw new VerificationException("Missing required custom claim");

259

}

260

});

261

262

AccessToken token = verifier.verify();

263

264

} catch (VerificationException e) {

265

logger.warn("Custom token verification failed: {}", e.getMessage());

266

}

267

```

268

269

## Key Management Patterns

270

271

### Custom Public Key Locator

272

273

```java

274

// Database-backed public key locator with caching

275

public class DatabasePublicKeyLocator implements PublicKeyLocator {

276

private final PublicKeyRepository keyRepository;

277

private final Map<String, PublicKey> keyCache = new ConcurrentHashMap<>();

278

private final Map<String, Long> cacheTimestamps = new ConcurrentHashMap<>();

279

private final long cacheTtlMs = TimeUnit.HOURS.toMillis(1);

280

281

public DatabasePublicKeyLocator(PublicKeyRepository keyRepository) {

282

this.keyRepository = keyRepository;

283

}

284

285

@Override

286

public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {

287

String realm = deployment.getRealm();

288

String cacheKey = realm + ":" + (kid != null ? kid : "default");

289

290

// Check cache first

291

PublicKey cachedKey = keyCache.get(cacheKey);

292

Long cacheTime = cacheTimestamps.get(cacheKey);

293

294

if (cachedKey != null && cacheTime != null &&

295

(System.currentTimeMillis() - cacheTime) < cacheTtlMs) {

296

return cachedKey;

297

}

298

299

// Load from database

300

PublicKeyEntity keyEntity = keyRepository.findByRealmAndKid(realm, kid);

301

if (keyEntity != null) {

302

try {

303

PublicKey publicKey = parsePublicKey(keyEntity.getKeyData());

304

keyCache.put(cacheKey, publicKey);

305

cacheTimestamps.put(cacheKey, System.currentTimeMillis());

306

return publicKey;

307

} catch (Exception e) {

308

logger.warn("Failed to parse public key from database", e);

309

}

310

}

311

312

return null;

313

}

314

315

@Override

316

public void reset(KeycloakDeployment deployment) {

317

String realm = deployment.getRealm();

318

// Remove cached keys for this realm

319

keyCache.entrySet().removeIf(entry -> entry.getKey().startsWith(realm + ":"));

320

cacheTimestamps.entrySet().removeIf(entry -> entry.getKey().startsWith(realm + ":"));

321

}

322

323

private PublicKey parsePublicKey(String keyData) throws Exception {

324

// Parse PEM or other format

325

// Implementation depends on key format

326

}

327

}

328

```

329

330

### Composite Key Locator

331

332

```java

333

// Composite key locator with fallback strategy

334

public class CompositePublicKeyLocator implements PublicKeyLocator {

335

private final List<PublicKeyLocator> locators;

336

337

public CompositePublicKeyLocator(PublicKeyLocator... locators) {

338

this.locators = Arrays.asList(locators);

339

}

340

341

@Override

342

public PublicKey getPublicKey(String kid, KeycloakDeployment deployment) {

343

for (PublicKeyLocator locator : locators) {

344

try {

345

PublicKey key = locator.getPublicKey(kid, deployment);

346

if (key != null) {

347

return key;

348

}

349

} catch (Exception e) {

350

logger.debug("Key locator {} failed: {}", locator.getClass().getSimpleName(), e.getMessage());

351

}

352

}

353

return null;

354

}

355

356

@Override

357

public void reset(KeycloakDeployment deployment) {

358

for (PublicKeyLocator locator : locators) {

359

try {

360

locator.reset(deployment);

361

} catch (Exception e) {

362

logger.warn("Failed to reset key locator {}", locator.getClass().getSimpleName(), e);

363

}

364

}

365

}

366

}

367

368

// Usage: JWKS first, then hardcoded fallback

369

PublicKeyLocator composite = new CompositePublicKeyLocator(

370

new JWKPublicKeyLocator(),

371

new HardcodedPublicKeyLocator(fallbackPublicKey)

372

);

373

```

374

375

### Key Rotation Monitoring

376

377

```java

378

// Monitor key rotation events

379

public class KeyRotationMonitor {

380

private final KeycloakDeployment deployment;

381

private final PublicKeyLocator keyLocator;

382

private volatile String currentKeyId;

383

384

public KeyRotationMonitor(KeycloakDeployment deployment) {

385

this.deployment = deployment;

386

this.keyLocator = deployment.getPublicKeyLocator();

387

}

388

389

public boolean handleTokenVerificationFailure(String tokenString, VerificationException e) {

390

try {

391

// Parse token header to get key ID

392

String kid = extractKeyIdFromToken(tokenString);

393

394

if (kid != null && !kid.equals(currentKeyId)) {

395

logger.info("Detected potential key rotation. Current: {}, Token: {}", currentKeyId, kid);

396

397

// Reset key cache and retry

398

keyLocator.reset(deployment);

399

400

// Attempt verification with fresh keys

401

AccessToken token = AdapterTokenVerifier.verifyToken(tokenString, deployment);

402

currentKeyId = kid;

403

404

logger.info("Token verification successful after key rotation");

405

return true;

406

}

407

} catch (Exception retryException) {

408

logger.warn("Token verification failed even after key rotation handling", retryException);

409

}

410

411

return false;

412

}

413

414

private String extractKeyIdFromToken(String tokenString) {

415

try {

416

String[] parts = tokenString.split("\\.");

417

if (parts.length >= 2) {

418

String headerJson = new String(Base64.getUrlDecoder().decode(parts[0]));

419

// Parse JSON to extract "kid" field

420

// Implementation depends on JSON library

421

}

422

} catch (Exception e) {

423

logger.debug("Failed to extract key ID from token", e);

424

}

425

return null;

426

}

427

}

428

```

429

430

### Configuration Examples

431

432

```java

433

// Production configuration with JWKS and caching

434

KeycloakDeployment deployment = new KeycloakDeployment();

435

deployment.setRealm("production-realm");

436

deployment.setAuthServerBaseUrl("https://keycloak.company.com/auth");

437

deployment.setPublicKeyLocator(new JWKPublicKeyLocator());

438

deployment.setMinTimeBetweenJwksRequests(10); // Rate limit JWKS requests

439

deployment.setPublicKeyCacheTtl(3600); // Cache for 1 hour

440

441

// Development configuration with hardcoded key

442

PublicKey devKey = loadDevelopmentPublicKey();

443

KeycloakDeployment devDeployment = new KeycloakDeployment();

444

devDeployment.setPublicKeyLocator(new HardcodedPublicKeyLocator(devKey));

445

446

// High-availability configuration with multiple strategies

447

PublicKeyLocator haLocator = new CompositePublicKeyLocator(

448

new JWKPublicKeyLocator(), // Primary: JWKS

449

new DatabasePublicKeyLocator(keyRepository), // Fallback: Database

450

new HardcodedPublicKeyLocator(emergencyKey) // Emergency: Hardcoded

451

);

452

deployment.setPublicKeyLocator(haLocator);

453

```