JWT authentication utilities in Jen.js.
signToken()
Create a JWT token:
import { signToken } from '@src/auth/jwt';
const token = signToken(payload, expiresIn, options);
Parameters
interface TokenPayload {
[key: string]: any;
}
// Payload - data to encode
const payload = {
userId: '123',
email: 'user@example.com',
role: 'admin'
};
// Expiration time
const expiresIn = '7d'; // 7 days
// or '24h', '3600s', or number (seconds)
// Optional options
const options = {
issuer: 'your-app',
audience: 'your-app',
algorithm: 'HS256'
};
const token = signToken(payload, expiresIn, options);
Examples
// Simple token
const token = signToken({ userId: '1' }, '7d');
// Complex payload
const token = signToken({
userId: '123',
email: 'alice@example.com',
role: 'admin',
permissions: ['read', 'write']
}, '24h');
// Custom options
const token = signToken(
{ userId: '1' },
'30d',
{
issuer: 'my-app',
audience: 'web-client'
}
);
verifyToken()
Verify and decode a JWT:
import { verifyToken } from '@src/auth/jwt';
try {
const payload = verifyToken(token);
console.log(payload.userId);
} catch (err) {
console.error('Invalid token');
}
Returns
interface Payload {
[key: string]: any;
iat?: number; // Issued at
exp?: number; // Expiration time
iss?: string; // Issuer
aud?: string; // Audience
}
const payload = verifyToken(token);
// { userId: '123', email: '...', iat: 1234567890, exp: ... }
Error Handling
try {
const payload = verifyToken(token);
} catch (err) {
if (err.name === 'TokenExpiredError') {
console.error('Token expired');
} else if (err.name === 'JsonWebTokenError') {
console.error('Invalid token');
} else {
console.error('Unknown error:', err);
}
}
Usage Examples
Login Route
// site/api/(login).ts
import { signToken } from '@src/auth/jwt';
export async function handle(req, res) {
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const { email, password } = JSON.parse(body);
// Verify credentials
const user = await verifyCredentials(email, password);
if (user) {
const token = signToken(
{ userId: user.id, email: user.email, role: user.role },
'7d'
);
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ token, user }));
} else {
res.writeHead(401);
res.end(JSON.stringify({ error: 'Invalid credentials' }));
}
});
}
}
Protected Route
// site/dashboard/($userId).tsx
import { verifyToken } from '@src/auth/jwt';
export async function loader(ctx) {
const token = ctx.request.headers.authorization?.split(' ')[1];
if (!token) {
ctx.response.writeHead(302, { location: '/login' });
ctx.response.end();
return {};
}
try {
const payload = verifyToken(token);
const user = await getUser(payload.userId);
return { user };
} catch (err) {
ctx.response.writeHead(302, { location: '/login' });
ctx.response.end();
return {};
}
}
export default function Dashboard({ user }: any) {
return <h1>Welcome, {user.email}</h1>;
}
Protected API
// site/api/(profile).ts
import { verifyToken } from '@src/auth/jwt';
export async function handle(req, res) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
res.writeHead(401);
res.end(JSON.stringify({ error: 'No token' }));
return;
}
try {
const payload = verifyToken(token);
const user = await getUser(payload.userId);
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify(user));
} catch (err) {
res.writeHead(401);
res.end(JSON.stringify({ error: 'Invalid token' }));
}
}
Refresh Tokens
// site/api/(refresh).ts
import { signToken, verifyToken } from '@src/auth/jwt';
export async function handle(req, res) {
if (req.method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const { refreshToken } = JSON.parse(body);
try {
const payload = verifyToken(refreshToken);
// Issue new token
const newToken = signToken(
{ userId: payload.userId },
'7d'
);
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ token: newToken }));
} catch (err) {
res.writeHead(401);
res.end(JSON.stringify({ error: 'Invalid refresh token' }));
}
});
}
}
Configuration
In jen.config.ts:
const config: FrameworkConfig = {
auth: {
secret: process.env.JWT_SECRET || 'dev-secret',
algorithm: 'HS256',
issuer: 'your-app',
audience: 'your-app'
}
};
Token Expiration
Recommended TTLs:
- Access token: 15 minutes to 1 hour
- Refresh token: 7-30 days
- Long-lived: 90+ days
// Short-lived access token
const accessToken = signToken(payload, '15m');
// Long-lived refresh token
const refreshToken = signToken(payload, '30d');
Security Best Practices
-
Use environment variables for secrets:
JWT_SECRET=your-secret-key -
Use HTTPS in production to prevent token interception
-
Store tokens securely on client:
// Good: httpOnly cookie (can't access via JS) // Bad: localStorage (vulnerable to XSS) -
Short expiration for access tokens
-
Validate token signature on every request
-
Implement logout by invalidating tokens
-
Use refresh tokens for long sessions
-
Rotate secrets periodically
-
Rate limit login attempts
-
Log auth events for security audit
Token Structure
JWT consists of 3 parts (header.payload.signature):
// Header
{ "alg": "HS256", "typ": "JWT" }
// Payload (your data)
{ "userId": "123", "iat": 1234567890, "exp": 1234571490 }
// Signature (verification)
base64(HMAC-SHA256(header.payload, secret))
The full token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOiIxMjMifQ.
SIGNATURE
Algorithms
Supported algorithms:
- HS256 (HMAC) — Symmetric, uses shared secret
- RS256 (RSA) — Asymmetric, uses public/private keys
// Symmetric (default)
const token = signToken(payload, '7d'); // Uses secret
// Asymmetric (if configured)
const token = signToken(payload, '7d', {
algorithm: 'RS256',
privateKey: process.env.PRIVATE_KEY
});
Debugging
import { verifyToken } from '@src/auth/jwt';
import jwt from 'jsonwebtoken';
const token = 'your-token...';
// Decode without verification
const decoded = jwt.decode(token);
console.log('Decoded:', decoded);
// Verify with detailed error
try {
const payload = verifyToken(token);
} catch (err) {
console.error('Error:', err.message);
console.error('Name:', err.name);
}