Fix: ES256 JWT key format mismatch — secretOrPrivateKey must be an asymmetric key
Problem
Self-hosted Roomote fails to start with error:
secretOrPrivateKey must be an asymmetric key when using ES256
This occurs because the auth tokens (job, preview, run) and sandbox OIDC all use ES256 algorithm via jsonwebtoken, but the library expects PEM format keys while Roomote passes raw DER bytes.
Root Cause
Roomote stores auth keys in environment variables as Base64-encoded P-256 DER bytes. The code decodes them like this:
// packages/auth/src/auth-token.ts (line 63)
const privateKey = Buffer.from(getJobAuthPrivateKey(), 'base64').toString('utf-8');
return jwt.sign(payload, privateKey, { algorithm: 'ES256' });
This produces raw DER bytes as a string. However, jsonwebtoken ES256 requires PEM format (with -----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY----- headers). Passing raw DER bytes causes the error above.
The same pattern appears in 4 files:
packages/auth/src/auth-token.ts — job auth tokens (sign + verify)
packages/auth/src/preview-token.ts — preview tokens (sign + verify)
packages/auth/src/run-token.ts — run tokens (sign + verify)
packages/auth/src/sandbox-oidc.ts — sandbox OIDC tokens
Fix
Convert Base64 DER → PEM before passing to jsonwebtoken. Add a helper function and use it in all 4 files:
/** Convert Base64 DER bytes to PEM format for jsonwebtoken ES256. */
function derToPem(der: Buffer, label: string): string {
const b64 = der.toString('base64');
const lines = b64.match(/.{1,64}/g) || [];
return `-----BEGIN ${label}-----\n${lines.join('\n')}\n-----END ${label}-----`;
}
// In createAuthToken / validateAuthToken:
const privateKeyDer = Buffer.from(getJobAuthPrivateKey(), 'base64');
const privateKeyPem = derToPem(privateKeyDer, 'PRIVATE KEY');
return jwt.sign(payload, privateKeyPem, { algorithm: 'ES256' });
Same pattern applied to preview-token.ts, run-token.ts, and sandbox-oidc.ts.
Files Changed
| File |
Change |
packages/auth/src/auth-token.ts |
Add derToPem(), convert private/public keys in sign/verify |
packages/auth/src/preview-token.ts |
Same pattern for preview tokens |
packages/auth/src/run-token.ts |
Same pattern for run tokens |
packages/auth/src/sandbox-oidc.ts |
Add derToPem() + decodeDerToPem(), use in OIDC token creation |
Why This Works
- Environment variables remain Base64 DER (Roomote's design, no docker-compose change needed)
- The new
derToPem() converts DER → PEM at runtime before passing to jsonwebtoken
- PEM format is what
jsonwebtoken ES256 expects — it parses the header/footer and extracts the key
Testing
The existing tests in packages/auth/src/__tests__/ should pass since they mock jsonwebtoken. For integration testing, deploy with a self-hosted instance using Base64 DER keys (as generated by the setup script) and verify:
- Roomote starts without ES256 error
- Auth tokens can be created and validated
- Preview URLs work correctly
Fix: ES256 JWT key format mismatch —
secretOrPrivateKey must be an asymmetric keyProblem
Self-hosted Roomote fails to start with error:
This occurs because the auth tokens (job, preview, run) and sandbox OIDC all use ES256 algorithm via
jsonwebtoken, but the library expects PEM format keys while Roomote passes raw DER bytes.Root Cause
Roomote stores auth keys in environment variables as Base64-encoded P-256 DER bytes. The code decodes them like this:
This produces raw DER bytes as a string. However,
jsonwebtokenES256 requires PEM format (with-----BEGIN PUBLIC KEY-----/-----END PUBLIC KEY-----headers). Passing raw DER bytes causes the error above.The same pattern appears in 4 files:
packages/auth/src/auth-token.ts— job auth tokens (sign + verify)packages/auth/src/preview-token.ts— preview tokens (sign + verify)packages/auth/src/run-token.ts— run tokens (sign + verify)packages/auth/src/sandbox-oidc.ts— sandbox OIDC tokensFix
Convert Base64 DER → PEM before passing to
jsonwebtoken. Add a helper function and use it in all 4 files:Same pattern applied to
preview-token.ts,run-token.ts, andsandbox-oidc.ts.Files Changed
packages/auth/src/auth-token.tsderToPem(), convert private/public keys in sign/verifypackages/auth/src/preview-token.tspackages/auth/src/run-token.tspackages/auth/src/sandbox-oidc.tsderToPem()+decodeDerToPem(), use in OIDC token creationWhy This Works
derToPem()converts DER → PEM at runtime before passing to jsonwebtokenjsonwebtokenES256 expects — it parses the header/footer and extracts the keyTesting
The existing tests in
packages/auth/src/__tests__/should pass since they mockjsonwebtoken. For integration testing, deploy with a self-hosted instance using Base64 DER keys (as generated by the setup script) and verify: