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.
export JWT_SECRET='replace-with-a-long-random-secret'
export DEMO_PASSWORD='replace-with-a-demo-password'Complete token and profile API
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
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/meExpect 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.