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

  1. Get a fresh token (re-login / refresh) and retry — expiry is the most common cause
  2. Verify the signer and verifier use the same JWT_SECRET env value (a service restarted with a missing secret will fail every token)
  3. Decode the token client-side (jwt-decode or jwt.io) and inspect exp, alg, and issuer to see what verification would reject
  4. 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

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

Related errors


AI-assisted analysis of affaan-m/ECC@d8409a4b08 (2026-08-26). Data as JSON: /api/errors/fb601b5a3cdf878f. Report an issue: GitHub.