All Docs
Getting StartedMaking Tax DigitalUpdated March 8, 2026

Migration Secret Setup

Migration Secret Setup

As of v1.0.321, the POST /api/migrate endpoint requires a dedicated MIGRATION_SECRET environment variable. There is no fallback — the endpoint returns 503 until the variable is configured correctly.

Why a separate secret?

Prior to v1.0.321, the migration endpoint fell back to NEXTAUTH_SECRET when MIGRATION_SECRET was absent. This created two problems:

  1. Expanded blast radius — a single compromised secret gave an attacker the ability to both forge sessions and trigger arbitrary database schema migrations.
  2. Coupled rotation — rotating the session secret would simultaneously invalidate all active sessions and change the migration token.

The two secrets are now fully independent.

Requirements

The runtime enforces both of the following conditions. The endpoint returns 503 if either is violated:

RequirementDetail
MIGRATION_SECRET must be setThere is no fallback to any other variable
MIGRATION_SECRET must not equal NEXTAUTH_SECRETSharing the session-signing key with the migration secret is rejected

Setup steps

1. Generate a dedicated secret

openssl rand -hex 32

Never reuse NEXTAUTH_SECRET, a database password, or any other existing credential.

2. Set the environment variable

Add the generated value to your environment. In .env.example it is documented as:

MIGRATION_SECRET=   # [REQUIRED] Bearer token for POST /api/migrate — must differ from NEXTAUTH_SECRET

For production deployments (Vercel, Railway, etc.) set it in the platform's environment variable UI. For local development add it to your .env.local file.

3. Update any scripts that call /api/migrate

Anything that previously used NEXTAUTH_SECRET as the bearer token must be updated:

# Before (no longer works)
curl -X POST https://your-app.vercel.app/api/migrate \
     -H "Authorization: Bearer $NEXTAUTH_SECRET"

# After
curl -X POST https://your-app.vercel.app/api/migrate \
     -H "Authorization: Bearer $MIGRATION_SECRET"

4. CI configuration

If your CI pipeline runs the startup checks or exercises the migration endpoint, add a distinct dummy value:

env:
  NEXTAUTH_SECRET: "your-ci-nextauth-secret"
  MIGRATION_SECRET: "your-ci-migration-secret-distinct-from-nextauth"

The two values must differ or the isolation check will fail.

Startup check behaviour

On every cold-start checkMigrationSecret() validates the configuration:

EnvironmentMIGRATION_SECRET missing or equals NEXTAUTH_SECRETCorrectly configured
Productionconsole.error + Sentry alertNo action
Development / Stagingconsole.warnNo action

The check is part of the composite runStartupChecks() call; hasIssues returns true whenever migration secret misconfiguration is detected.

Endpoint behaviour summary

StateHTTP response
MIGRATION_SECRET not set503 Migration endpoint misconfigured
MIGRATION_SECRET === NEXTAUTH_SECRET503 Migration endpoint misconfigured
Correct secret, wrong bearer token401 Unauthorized
Correct secret, correct bearer token200 { success: true, message: "Migrations applied" }