Authentication#
Buntok provides a zero-dependency JWT implementation using WebCrypto (built-in). Supports HMAC-SHA256 with expiration, Bearer tokens, and HttpOnly cookies.
Info
No additional packages needed! JWT is already included in
@buntok/core.JwtService#
Creating Instance#
typescript
import { JwtService } from "@buntok/core";
const jwt = new JwtService("your-secret-key");Sign Token#
typescript
// Sign token (expires in 1 hour)
const token = await jwt.sign(
{ userId: 1, role: "admin" },
3600 // seconds
);
// Sign token (expires in 7 days)
const token = await jwt.sign(
{ userId: 1, role: "user" },
7 * 24 * 60 * 60
);Verify Token#
typescript
const payload = await jwt.verify(token);
// { userId: 1, role: "admin", exp: 1700000000 } or null if invalid
// Handle expired/invalid token
const payload = await jwt.verify(token);
if (!payload) {
// Token is invalid or expired
}requireAuth Middleware#
Extracts and verifies JWT from cookie or Authorization header. Injects the payload into ctx.user.
typescript
import { requireAuth } from "@buntok/core";
const secret = "your-secret-key";
// Basic usage
app.get("/protected", requireAuth(secret), (ctx) => {
const user = ctx.user as { userId: number; role: string };
return ctx.json({ user });
});Cookie-based Auth#
Set AUTH_COOKIE in your .env to enable cookie-based authentication. When set, requireAuth reads JWT from that cookie first, then falls back to the Authorization header.
Setup#
typescript
# .env (created by buntok init)
AUTH_STORE=cookie
AUTH_COOKIE=sessionLogin Flow#
typescript
import { Buntok } from "@buntok/core";
import { JwtService } from "@buntok/core";
import { setCookie, deleteCookie } from "@buntok/core/helpers";
const jwt = new JwtService(process.env.JWT_SECRET!);
// Login endpoint - set HttpOnly cookie
app.post("/login", async (ctx) => {
const { email, password } = await ctx.body();
const user = await db.user.findUnique({ where: { email } });
if (!user || !await verifyPassword(password, user.password)) {
return ctx.json({ error: "Invalid credentials" }, 401);
}
const token = await jwt.sign(
{ userId: user.id, role: user.role },
86400 // 24 hours
);
// Set HttpOnly cookie (read by requireAuth automatically)
const response = ctx.json({ success: true });
return setCookie(response, process.env.AUTH_COOKIE!, token, {
httpOnly: true,
secure: true, // HTTPS only
sameSite: "lax",
path: "/",
maxAge: 86400, // 24 hours
});
});
// Logout endpoint - clear cookie
app.post("/logout", (ctx) => {
const response = ctx.json({ success: true });
return deleteCookie(response, process.env.AUTH_COOKIE!, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
});
});Info
When
AUTH_STORE=cookie, requireAuth reads JWT from the HttpOnly cookie named in AUTH_COOKIE. Falls back to Authorization: Bearer <token> if cookie is missing.How It Works#
| Env Variable | Default | Description |
|---|---|---|
| AUTH_STORE | header | Where to read JWT: header or cookie |
| AUTH_COOKIE | session | Cookie name (only used when AUTH_STORE=cookie) |
Bearer Token (Header)#
The client can also send the token in the Authorization header:
typescript
// HTTP Header
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// Example with fetch
const response = await fetch("/api/profile", {
headers: {
"Authorization": `Bearer ${token}`,
},
});Example: Login Flow#
typescript
import { Buntok } from "@buntok/core";
import { JwtService, requireAuth } from "@buntok/core";
const jwt = new JwtService(process.env.JWT_SECRET!);
// Login endpoint
app.post("/login", async (ctx) => {
const { email, password } = await ctx.body();
// Find user in database
const user = await db.user.findUnique({ where: { email } });
if (!user || !await verifyPassword(password, user.password)) {
return ctx.json({ error: "Invalid credentials" }, 401);
}
// Generate token
const token = await jwt.sign(
{ userId: user.id, role: user.role },
86400 // 24 hours
);
return ctx.json({ token });
});
// Protected endpoint
app.get("/profile", requireAuth(secret), async (ctx) => {
const { userId } = ctx.user as { userId: number };
const user = await db.user.findUnique({
where: { id: userId },
select: { id: true, name: true, email: true },
});
return ctx.json({ user });
});Error Responses#
| Status | Message | Condition |
|---|---|---|
| 401 | Missing or invalid authentication token | No token found in cookie or Authorization header |
| 401 | Token is invalid or expired | Token has expired or is invalid |
Best Practices#
- Store JWT_SECRET in environment variables, not in code
- Use appropriate expiration times (1 hour for access tokens, 7 days for refresh tokens)
- Always use HTTPS in production
- Use
httpOnly: truefor cookies to prevent XSS attacks - Use
sameSite: "lax"or"strict"for CSRF protection - Use RBAC for role-based access control
Next Steps#
- RBAC - Role-based access control
- Middleware - Learn about middleware