RSA Key Management
What the key doesโ
Every JWT issued by this server โ access tokens and id_tokens โ is signed with RSA-2048 (RS256). Clients verify the signature using the public key from the JWKS endpoint (GET /oauth2/jwks).
RsaKeyConfigโ
On every startup, RsaKeyConfig generates a fresh RSA-2048 key pair using Nimbus JOSE:
@Bean
public RSAKey rsaKey() {
RSAKey rsaKey = new RSAKeyGenerator(2048)
.keyID(UUID.randomUUID().toString())
.generate();
log.info("RSA-2048 signing key generated โ kid={}", rsaKey.getKeyID());
return rsaKey;
}
@Bean
public JWKSource<SecurityContext> jwkSource(RSAKey rsaKey) {
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}
Spring Authorization Server consumes the JWKSource bean automatically to sign all issued JWTs.
Development behaviour (ephemeral key)โ
In development the ephemeral key is fine. Each restart generates a new key, but since all tokens expire quickly (default 3600 s) this is not a problem. SMART client instances that cache the JWKS will fetch a new key on the next request after the kid changes.
Production: load a persistent keyโ
For production you must replace the ephemeral key with one loaded from a keystore or secrets manager. Two reasons:
- Token continuity โ tokens issued before a restart will fail signature verification after the restart
- Multi-instance โ multiple server instances must share the same key or every instance rejects tokens issued by the others
Option A โ Environment variable (JWK JSON)โ
Store the JWK JSON string in a secret (AWS Secrets Manager, HashiCorp Vault, Kubernetes Secret):
smart:
server:
rsa-key-jwk: ${RSA_SIGNING_KEY_JWK:}
@Bean
public RSAKey rsaKey(SmartServerProperties props) {
String jwkJson = props.rsaKeyJwk();
if (jwkJson != null && !jwkJson.isBlank()) {
return RSAKey.parse(jwkJson);
}
// Fail fast in non-dev profiles
throw new IllegalStateException(
"RSA_SIGNING_KEY_JWK must be set in production");
}
Generate a key once and store it:
# Generate a JWK using the Nimbus CLI or any JWK generator
# Then store in your secrets manager
cat > /tmp/generate-key.sh << 'EOF'
java -cp nimbus-jose-jwt.jar com.nimbusds.jose.jwk.gen.RSAKeyGenerator \
--size 2048 --use sig --alg RS256 > signing-key.json
EOF
Option B โ Java KeyStore (JKS/PKCS12)โ
smart:
server:
keystore-path: ${KEYSTORE_PATH:classpath:keystore.p12}
keystore-password: ${KEYSTORE_PASSWORD}
key-alias: smart-fhir-signing
JWKS endpointโ
The public key is served at GET /oauth2/jwks:
{
"keys": [{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"kid": "a1b2c3d4-...",
"alg": "RS256",
"n": "0vx7agoebGcQ..."
}]
}
The kid (key ID) matches the kid header in every JWT. Clients use it to select the right key when the JWKS contains multiple keys during rotation.
Key rotationโ
To rotate the key without downtime:
- Add the new key to the JWKS (publish both old and new)
- Start issuing new tokens with the new key
- Wait for all tokens issued with the old key to expire
- Remove the old key from the JWKS
With ImmutableJWKSet this requires a restart. For zero-downtime rotation use a mutable JWKSet implementation that can be updated via an admin endpoint.
Next: Registered Clients โ