kms
Fetches asymmetric public keys from AWS KMS using GetPublicKey and exposes them via request.internal (and optionally request.context). Designed to feed token-verification middleware such as @middy/http-jwt and @middy/http-paseto, but the resolved { publicKey, keySpec } shape can be consumed by any custom middleware.
For each fetchData entry the middleware makes a single GetPublicKey API call per cold start, caches the result, and stores { publicKey, keySpec } under the configured internal key.
Install
To install this middleware you can use NPM:
npm install --save @middy/kms
npm install --save-dev @aws-sdk/client-kms Options
AwsClient(object) (defaultKMSClient): KMSClient class constructor (i.e. that has been instrumented with AWS XRay). Must be from@aws-sdk/client-kms.awsClientOptions(object) (optional): Options to pass to KMSClient class constructor.awsClientAssumeRole(string) (optional): Internal key where temporary credentials are stored. See @middy/sts on how to set this.awsClientCapture(function) (optional): Enable XRay by passingcaptureAWSv3Clientfromaws-xray-sdkin.fetchData(object) (required): Mapping of internal key name to KMSKeyId(key ID, key ARN, alias name, or alias ARN, e.g.alias/jwt-signing-key).disablePrefetch(boolean) (defaultfalse): On cold start requests will trigger early if they can. SettingawsClientAssumeRoledisables prefetch.cacheKey(string) (default@middy/kms): Cache key for the fetched data responses. Must be unique across all middleware.cacheKeyExpiry(object) (default{}): Per-key cache expiry overrides.cacheExpiry(number) (default-1): How long fetch data responses should be cached for.-1: cache forever,0: never cache,n: cache for n ms. KMS public keys do not rotate without an explicitCreateKey, so the default of “cache forever” is appropriate for most deployments.setToContext(boolean) (defaultfalse): Also store fetched keys onrequest.context.
NOTES:
- Lambda is required to have IAM permission for
kms:GetPublicKeyon each KMS key referenced infetchData. - Each internal value has the shape
{ publicKey: Uint8Array, keySpec: string }.publicKeyis the SPKI DER-encoded public key returned by KMS;keySpecis the KMS key spec (e.g.RSA_2048,ECC_NIST_P256,ECC_NIST_ED25519).
Sample usage
Verifying JWTs signed by a KMS key
import middy from '@middy/core'
import kms from '@middy/kms'
import httpJwt from '@middy/http-jwt'
import httpErrorHandler from '@middy/http-error-handler'
const lambdaHandler = (event, context) => {
return { statusCode: 200, body: JSON.stringify({ sub: context.jwt.sub }) }
}
export const handler = middy()
.use(
kms({
fetchData: {
jwtKey: 'alias/jwt-signing-key',
},
}),
)
.use(
httpJwt({
internalKey: 'jwtKey',
issuer: 'https://auth.example.com',
audience: 'api.example.com',
}),
)
.use(httpErrorHandler())
.handler(lambdaHandler) Verifying PASETO v4.public tokens
import middy from '@middy/core'
import kms from '@middy/kms'
import httpPaseto from '@middy/http-paseto'
export const handler = middy()
.use(
kms({
fetchData: {
pasetoKey: 'alias/paseto-signing-key',
},
}),
)
.use(
httpPaseto({
internalKey: 'pasetoKey',
}),
)
.handler(async () => ({ statusCode: 200, body: '{}' })) Reading the public key directly
import middy from '@middy/core'
import kms from '@middy/kms'
import { getInternal } from '@middy/util'
import { createPublicKey } from 'node:crypto'
const lambdaHandler = async (event, context) => {
const { signingKey } = await getInternal(['signingKey'], context)
// signingKey is { publicKey: Uint8Array, keySpec: 'RSA_2048' | ... }
const key = createPublicKey({
key: Buffer.from(signingKey.publicKey),
format: 'der',
type: 'spki',
})
// ... verify a signature with `key`
return { statusCode: 200, body: '{}' }
}
export const handler = middy()
.use(
kms({
fetchData: { signingKey: 'alias/my-app' },
}),
)
.handler(lambdaHandler) Bundling
To exclude @aws-sdk add @aws-sdk/client-kms to the exclude list.