AWS Secrets Manager
Persist the managed JWKS signing keystore in AWS Secrets Manager with @adonis-agora/authkit-vault-aws.
@adonis-agora/authkit-vault-aws backs the managed keystore with
AWS Secrets Manager. The private JWKS blob lives in a single secret; AWS encrypts it at
rest with a KMS key, gates access with IAM, and records every read in CloudTrail. Your app
nodes hold no key material on disk.
Install
Install the package and its cloud SDK peer:
pnpm add @adonis-agora/authkit-vault-aws @aws-sdk/client-secrets-manager@aws-sdk/client-secrets-manager (v3+) is an optional peer dependency. AuthKit
imports it lazily, so the dependency only needs to exist when the aws-secrets-manager
driver is actually selected. A selected-but-missing SDK throws a clear install error — the
signing key is critical and never silently degrades.
Configure
Select the driver in your server config. That's the whole wiring — AuthKit lazy-loads this
package and calls its createKeystoreVault factory the first time it touches the keystore:
import { defineConfig } from '@adonis-agora/authkit-server'
export default defineConfig({
// ...
jwks: {
source: 'managed',
algorithm: 'RS256',
store: {
driver: 'aws-secrets-manager',
secretId: 'prod/authkit/jwks',
region: 'us-east-1',
},
},
})| Field | Type | Required | Notes |
|---|---|---|---|
driver | 'aws-secrets-manager' | yes | Selects this package. |
secretId | string | yes | The secret name or full ARN holding the keystore blob. |
region | string | no | AWS region. Omit to use the ambient SDK/region resolution (AWS_REGION, instance metadata, etc.). |
You do not have to pre-create the secret. On the first rotation/write, if the secret
doesn't exist (ResourceNotFoundException), the vault calls CreateSecret to provision it;
subsequent writes use PutSecretValue to add a new version.
Credentials & IAM
The vault uses the standard AWS SDK credential chain — environment variables, shared config, EC2/ECS/EKS instance roles, or IRSA. No credentials go in AuthKit config. The execution role needs, scoped to your secret ARN:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:PutSecretValue",
"secretsmanager:CreateSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/authkit/jwks-*"
}
]
}CreateSecret can be dropped if you provision the secret out-of-band (Terraform, console).
Grant kms:Decrypt/kms:GenerateDataKey on the secret's KMS key if you use a customer-managed
key.
How it works
Under the hood the package adapts Secrets Manager to AuthKit's
SecretBackend seam:
read()→GetSecretValue— returnsSecretString, ornullonResourceNotFoundException(so a first boot generates the key instead of crashing).write(blob)→PutSecretValue, falling back toCreateSecretwhen the secret doesn't exist yet.head()→version()→GetSecretValue.VersionId— a cheap change token so multi-node deployments can poll for rotations and hot-reload the JWKS without a restart.
import { createKeystoreVault } from '@adonis-agora/authkit-vault-aws'
// This is what AuthKit calls internally; you can also call it directly.
const vault = createKeystoreVault({
secretId: 'prod/authkit/jwks',
region: 'us-east-1',
})
await vault.read() // string | null
await vault.head() // VersionId | nullThe package's public entry point exports exactly three symbols: the
createKeystoreVault factory and the two structural types shared by every vault —
KeystoreVaultLike and SecretBackend. The AWS config shape is:
interface AwsVaultConfig {
/** Secret name or full ARN holding the keystore blob. */
secretId: string
/** AWS region; omit for ambient SDK resolution. */
region?: string
/** Inject a custom SecretBackend to bypass the AWS SDK (tests). */
backend?: SecretBackend
}Testing without AWS
Every config accepts an optional backend that bypasses the AWS SDK entirely, so unit
tests never touch the network:
import { createKeystoreVault } from '@adonis-agora/authkit-vault-aws'
let stored: string | null = null
const vault = createKeystoreVault({
secretId: 'test',
backend: {
get: async () => stored,
put: async (blob) => { stored = blob },
version: async () => (stored ? 'v1' : null),
},
})Losing the secret means every token AuthKit ever signed becomes unverifiable and every
relying party must re-fetch a fresh JWKS. Enable Secrets Manager versioning/recovery and
restrict DeleteSecret accordingly.