@geostrategists/cdk-jwks-rotation
v3.0.0
Published
CDK construct for generating and rotating JWT signing keys and JSON Web Key Sets (JWKS)
Readme
CDK JWKS Rotation Construct
This AWS CDK construct creates and manages cryptographic key pairs for signing JSON Web Tokens (JWTs). It stores the private key securely in AWS Secrets Manager and publishes the public key as a JWKS (JSON Web Key Set) file in an S3 bucket. The construct provides seamless, automated key rotation.
There is a blog post by Jan Brennenstuhl at Zalando which nicely explains the general concept.
The construct uses AWS Secrets Manager rotation to manage the lifecycle of the keys. Whenever a new key is rotated in, the next key is already generated, and its public key is added to the JWKS file. This allows consumers to have refetched the JWKS in time, and be prepared when the next key is activated. The next key is stored in the same secret in a separate version labeled "NEXT". Whenever a key is retired and the maximum token lifetime has passed, the corresponding public key is removed from the JWKS file (after a configurable grace period).
Installation
npm install @geostrategists/cdk-jwks-rotation
# or
yarn add @geostrategists/cdk-jwks-rotationTypes
SecretValue
The SecretValue interface represents the structure of cryptographic key data stored in AWS Secrets Manager:
interface SecretValue {
/** The private key in PEM format used for signing JWTs */
privateKeyPem: string;
/** The public key in JWK format for inclusion in JWKS */
publicKeyJwk: JWK;
/** Unique key identifier used in JWT headers and JWKS */
kid: string;
/** Algorithm used for signing (e.g., RS256, ES256) */
alg: string;
/** ISO timestamp when the key was created */
createdAt: string;
/** ISO timestamp when the key was activated for signing (optional) */
activatedAt?: string;
}Usage
Mandatory Props
secretName: Name for the AWS Secrets Manager secret to store the private key.keySpec: Specification for the key pair (algorithm, curve, modulus length, etc.). See jose documentationmaxTokenValidityDuration: Maximum duration for which a token signed by a key is considered valid. This will influence how long a retired key is kept in the JWKS file.
Optional Props
bucket: Existing S3 bucket to store the JWKS file. By default, a new bucket will be created.bucketPath: Path/name for the JWKS file in the bucket. Default:".well-known/jwks.json".bucketName: Name for a new S3 bucket (ifbucketis not provided). By default, a name will be generated by CKD (recommended).rotationInterval: How often to rotate keys. Default:Duration.days(60).minActivationGracePeriod: Grace period before a new key is activated. Default:Duration.days(7).cleanupCheckInterval: How often to check for old keys to clean up. (Doing this too often can influence costs). Default:Duration.hours(24).minKeyCleanupGracePeriod: Grace period before deleting old keys after max token validity duration has passed. Default:Duration.hours(6).
import { JwksRotation } from 'cdk-jwks-rotation';
new JwksRotation(this, 'JwksRotation', {
secretName: 'my-jwks-secret',
keySpec: { algorithm: 'ES256' },
maxTokenValidityDuration: Duration.seconds(3600),
// Optional, these are the default values:
bucket: undefined, // or: myBucket,
bucketPath: ".well-known/jwks.json",
rotationInterval: Duration.days(60),
minActivationGracePeriod: Duration.days(7),
cleanupCheckInterval: Duration.hours(24),
minKeyCleanupGracePeriod: Duration.hours(6),
});IAM and permissions
The construct grants the rotation Lambda:
- Secrets Manager: DescribeSecret, GetSecretValue, PutSecretValue, UpdateSecretVersionStage (scoped to the created secret)
- S3 bucket read/write for the JWKS object
Troubleshooting
- Rotation aborted with "Next key is too new": Allow more time (per
minActivationGracePeriod) between creating NEXT and a rotation cycle, or lower the grace period if appropriate. - "No keys found in JWKS document": Ensure initial deployment updated JWKS; re-run rotation or check Lambda permissions to S3.
- JWT verification failed during
testSecret: Verify the JWKS URL and that the new public key was written; check S3 object and cache.
Potential issues / room for improvement (as identified by AI)
- Bucket exposure flexibility: Consider a prop to toggle bucket
publicReadAccessand optionally attach a CloudFront distribution for best practice public serving. - Custom KMS support: Allow passing a customer-managed KMS key for the secret.
- JWKS cache-control configuration: Make
CacheControlconfigurable to align with consumer caching SLAs. - Health endpoint: Optionally emit a small metric or a health-check log/CloudWatch metric on successful rotation to aid monitoring/alerts.
- Key ID strategy: Current
kidusesnanoid(). Some teams prefer deterministic KIDs (e.g., hash of public key). Consider making strategy pluggable. - Broader algorithm validation: Enforce that ES algorithms are paired with a required
crv, and validate modulus length ranges for RSA in the construct props.
Development
Install and scripts:
yarn install
yarn lint
yarn typecheck
yarn build
yarn test:run