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:
- Expanded blast radius — a single compromised secret gave an attacker the ability to both forge sessions and trigger arbitrary database schema migrations.
- 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:
| Requirement | Detail |
|---|---|
MIGRATION_SECRET must be set | There is no fallback to any other variable |
MIGRATION_SECRET must not equal NEXTAUTH_SECRET | Sharing 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:
| Environment | MIGRATION_SECRET missing or equals NEXTAUTH_SECRET | Correctly configured |
|---|---|---|
| Production | console.error + Sentry alert | No action |
| Development / Staging | console.warn | No action |
The check is part of the composite runStartupChecks() call; hasIssues returns true whenever migration secret misconfiguration is detected.
Endpoint behaviour summary
| State | HTTP response |
|---|---|
MIGRATION_SECRET not set | 503 Migration endpoint misconfigured |
MIGRATION_SECRET === NEXTAUTH_SECRET | 503 Migration endpoint misconfigured |
| Correct secret, wrong bearer token | 401 Unauthorized |
| Correct secret, correct bearer token | 200 { success: true, message: "Migrations applied" } |