BlogHow to Store JWT Secrets Securely: Env Vars, Vaults, and KMS
·8 min read·JWTSecrets Team

How to Store JWT Secrets Securely: Env Vars, Vaults, and KMS

Storing JWT secrets incorrectly is one of the most common security mistakes in web development. This guide covers environment variables, secrets managers, and cloud KMS solutions.

How to Store JWT Secrets Securely: Env Vars, Vaults, and KMS

One of the most consequential decisions in your application's security posture is where and how you store your JWT signing secrets. A hardcoded secret is a ticking time bomb; a properly managed secret in a vault is as safe as you can get.

Rule Zero: Never Hardcode Secrets

// WRONG — never do this
const secret = 'my-super-secret-key-1234';

// CORRECT — read from environment
const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('JWT_SECRET environment variable is not set');

Hardcoded secrets end up in git history, Docker images, log files, and error messages. They cannot be rotated without a code deployment.

Option 1: Environment Variables

The simplest approach. Store the secret in your deployment environment and read it at runtime.

# .env (development only — never commit)
JWT_SECRET=your-256-bit-random-secret-here

# Production: set via your hosting platform
railway variables set JWT_SECRET=your-secret
heroku config:set JWT_SECRET=your-secret

Best for: Small projects, early-stage products, simple deployments with a single environment.

Risks: Secrets appear in process environment listings, may be logged accidentally, and rotation requires restart.

Option 2: AWS Secrets Manager

AWS Secrets Manager stores secrets encrypted with KMS and provides IAM-based access control. Your application fetches the secret at startup or on each request.

const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');

const client = new SecretsManagerClient({ region: 'us-east-1' });

async function getJwtSecret() {
  const response = await client.send(
    new GetSecretValueCommand({ SecretId: 'prod/app/jwt-secret' })
  );
  return response.SecretString;
}

Best for: Production deployments on AWS, teams that need audit logging and automatic rotation.

Option 3: HashiCorp Vault

A dedicated secrets management platform with dynamic secrets, lease TTLs, and detailed audit logging.

const vault = require('node-vault')({ endpoint: process.env.VAULT_ADDR });

async function getJwtSecret() {
  await vault.approleLogin({ role_id: process.env.VAULT_ROLE_ID, secret_id: process.env.VAULT_SECRET_ID });
  const { data } = await vault.read('secret/data/jwt');
  return data.data.secret;
}

Best for: Medium-to-large teams with dedicated platform engineering, multi-cloud environments, fine-grained access control needs.

Option 4: Cloud KMS (Envelope Encryption)

For the highest security, store only an encrypted version of your secret in your database or config, and use cloud KMS to decrypt at runtime. The plaintext key material never persists outside memory.

Best for: Enterprise, regulated industries (HIPAA, PCI-DSS), highest security requirements.

Quick Decision Matrix

StageRecommendation
Development.env file (never committed)
Small productionPlatform env vars (Railway, Render, Vercel)
Growing teamAWS Secrets Manager or Doppler
EnterpriseHashiCorp Vault or Cloud KMS

Generate your JWT secret with the JWT Secret Generator, then store it using the method appropriate for your deployment stage.