affaan-m/ECC · error
Invalid token
Error message
Invalid token
What it means
The second failure mode of withAuth: a bearer token was supplied, but verifyToken(token) threw, so the middleware responds 401 { error: 'Invalid token' }. verifyToken (jsonwebtoken's jwt.verify) throws when the signature does not match the secret, the token is expired (exp), malformed/truncated, or uses an unexpected algorithm — the middleware collapses all of these into one message.
Source
Thrown at skills/backend-patterns/SKILL.md:119
### Middleware Pattern
```typescript
// Request/response processing pipeline
export function withAuth(handler: NextApiHandler): NextApiHandler {
return async (req, res) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({ error: 'Unauthorized' })
}
try {
const user = await verifyToken(token)
req.user = user
return handler(req, res)
} catch (error) {
return res.status(401).json({ error: 'Invalid token' })
}
}
}
// Usage
export default withAuth(async (req, res) => {
// Handler has access to req.user
})
```
## Database Patterns
### Query Optimization
```typescript
// PASS: GOOD: Select only needed columns
const { data } = await supabase
.from('markets')View on GitHub (pinned to d8409a4b08)
Solutions
- Get a fresh token (re-login / refresh) and retry — expiry is the most common cause
- Verify the signer and verifier use the same JWT_SECRET env value (a service restarted with a missing secret will fail every token)
- Decode the token client-side (jwt-decode or jwt.io) and inspect exp, alg, and issuer to see what verification would reject
- If secrets were rotated, keep the previous key accepted for a transition window; if clocks drift, sync server time (NTP)
Example fix
// before - every request 401s after token expiry
const res = await fetch('/api/protected', { headers: { Authorization: `Bearer ${token}` } })
if (res.status === 401) throw new Error('broken')
// after - refresh once on 401 and retry
let res = await fetch('/api/protected', { headers: { Authorization: `Bearer ${token}` } })
if (res.status === 401) {
token = await refreshSession()
res = await fetch('/api/protected', { headers: { Authorization: `Bearer ${token}` } })
} Defensive patterns
Strategy: retry
Validate before calling
// Client: cheap pre-flight — refuse to send tokens that are already expired or malformed
function decodeJwt(token: string): { exp?: number } | null {
try {
const payload = JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')))
return typeof payload === 'object' ? payload : null
} catch { return null }
}
const isUsableToken = (token: string) =>
token.split('.').length === 3 && (decodeJwt(token)?.exp ?? 0) * 1000 > Date.now() + 30_000 Type guard
const isExpiredToken = (token: string): boolean => {
const p = decodeJwt(token)
return p === null || (p.exp !== undefined && p.exp * 1000 <= Date.now())
} Try / catch
// Refresh-once pattern: one retry after token refresh, then give up (prevents refresh loops)
let res = await request(token)
if (res.status === 401) {
token = await refreshSession()
res = await request(token)
if (res.status === 401) await logout() // genuinely invalid — stop retrying
} Prevention
- Refresh tokens proactively before exp rather than waiting for a 401
- Ensure every service verifying JWTs shares the same secret/keyset (check env wiring per deployment)
- After secret rotation, accept the previous key for a grace window
- Never copy tokens through logs or shells that can add quotes/whitespace — corruption causes 'Invalid token'
When it happens
Trigger: Expired JWT (exp in the past) sent after a long-lived session; token signed with a different JWT_SECRET than the verifier uses (dev vs prod secret, or two services with different secrets); token truncated or corrupted during copy-paste (missing segment, embedded newline); token signed with alg the verifier rejects.
Common situations: Redeploy changed or failed to load JWT_SECRET so old tokens no longer verify; secret rotated without keeping the old key for a grace period; server clock skew making fresh tokens appear expired; token copied out of a log with surrounding quotes/whitespace.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Unauthorized
- ${command} ${args.join(' ')} failed: ${(result.stderr || res
- ${command} ${args.join(' ')} failed: ${(result.stderr || res
- msg
AI-assisted analysis of affaan-m/ECC@d8409a4b08 (2026-08-26).
Data as JSON: /api/errors/fb601b5a3cdf878f.
Report an issue: GitHub.