Volt v0.1 · TypeScript APIs on Bun · Get started
Docs/Plugins Ecosystem/JWT Authentication

JWT Authentication

Issue demo tokens and verify each request using the current JWT plugin.

Configure secrets

The bundled JWT implementation supports HS256, HS384, and HS512. Set a strong JWT_SECRET and a separate DEMO_PASSWORD in your environment. The example password comparison is only a tutorial login flow; replace it with real user lookup and password verification.

bash
export JWT_SECRET='replace-with-a-long-random-secret'
export DEMO_PASSWORD='replace-with-a-demo-password'

Complete token and profile API

index.ts
import { Volt, jwtPlugin, t, type JwtPayload } from 'voltts';

const secret = process.env.JWT_SECRET;
const demoPassword = process.env.DEMO_PASSWORD;
if (!secret || !demoPassword) throw new Error('Set JWT_SECRET and DEMO_PASSWORD');

const app = new Volt();
app.plugin(jwtPlugin({ secret }));
app.post('/token', async (c) => {
  // Demo only: replace this comparison with your user/password verification.
  if (c.body.password !== demoPassword) return c.json({ error: 'Unauthorized' }, 401);
  const token = await c.store.jwt.sign({ sub: 'demo-user' }, { expiresIn: '15m' });
  return c.json({ token });
}, { body: t.Object({ password: t.String({ minLength: 1 }) }) });

app.get('/me', async (c) => {
  const header = c.headers.get('authorization') ?? '';
  if (!header.startsWith('Bearer ')) return c.json({ error: 'Missing token' }, 401);
  try {
    // Keep identity in a local variable for this request.
    const user: JwtPayload = await c.store.jwt.verify(header.slice(7));
    return c.json({ id: user.sub });
  } catch {
    return c.json({ error: 'Invalid or expired token' }, 401);
  }
});
if (globalThis._importMeta_.main) app.listen(3000);
export default app;

Request a token

bash
bun run index.ts
curl -i -X POST http://localhost:3000/token -H 'Content-Type: application/json' -d '{"password":"replace-with-a-demo-password"}'
# Copy the token from the response:
curl -i http://localhost:3000/me -H 'Authorization: Bearer REPLACE_WITH_TOKEN'
curl -i http://localhost:3000/me

Expect 200 for a verified token and 401 when it is missing, invalid, or expired. expiresIn accepts seconds or strings such as 15m and 1h.

Plugin helpers and guards

The plugin decorates c.store.jwt, not c.jwt. protectAll installs the JWT middleware before subsequent routes. The jwt() middleware writes verified claims to c.store.user by default. Because that store is currently shared across requests, the example above verifies into a local variable instead. Decoding a token alone does not verify its signature. OpenAPI security declarations do not enforce token verification.