@ttoss/http-server-auth
Authentication middleware for @ttoss/http-server (Koa). Wraps @ttoss/auth-core primitives into a ready-to-use Bearer-token strategy chain.
Installation
pnpm add @ttoss/http-server-auth
Usage
Global middleware
import { authMiddleware } from '@ttoss/http-server-auth';
import { App } from '@ttoss/http-server';
const app = new App();
app.use(
authMiddleware({
strategies: ['jwt', 'apiToken', 'system'],
jwt: {
secret: process.env.JWT_SECRET,
},
apiToken: {
lookup: async (tokenHash) => {
const record = await ApiTokenModel.findOne({
where: { tokenHash, revoked: false },
});
if (!record) return null;
await record.update({ lastUsedAt: new Date() });
return { id: record.user.publicId, email: record.user.email };
},
},
system: {
secret: process.env.INTERNAL_API_SECRET,
user: { id: 'system', email: 'system@internal' },
},
allowedOrigins: [
process.env.APP_URL,
'http://localhost:3000',
/\.vercel\.app$/,
],
required: true, // default
})
);
Per-route middleware
import { requireAuth } from '@ttoss/http-server-auth';
import { Router } from '@ttoss/http-server';
const router = new Router();
router.get(
'/internal/revalidate',
requireAuth({
strategies: ['system'],
system: { secret: process.env.INTERNAL_API_SECRET, user: { id: 'system' } },
}),
handler
);
router.get(
'/me',
requireAuth({
strategies: ['jwt', 'apiToken'],
jwt: { secret: process.env.JWT_SECRET },
apiToken: { lookup },
}),
handler
);
Optional auth
app.use(
authMiddleware({
strategies: ['jwt'],
jwt: { secret: process.env.JWT_SECRET },
required: false, // unauthenticated requests pass through with ctx.state.user === undefined
})
);
Context types
On successful authentication the middleware sets:
ctx.state.user; // AuthenticatedUser
ctx.state.authStrategy; // 'jwt' | 'apiToken' | 'system'
type AuthenticatedUser = {
id: string;
email?: string;
[key: string]: unknown;
};
Behavior
- Reads
Authorization: Bearer <token>; missing header → 401 ifrequired. - If
allowedOriginsis configured and theOriginheader doesn't match → 403. Requests without an Origin header are never rejected. - Tries each strategy in
strategiesorder; first match wins.jwt— verifies HS256 JWT via@ttoss/auth-core verifyJwt; mapssub/emailto user (override withjwt.mapPayload).apiToken— hashes the token (SHA-256) and callsapiToken.lookup(hash).system— constant-time comparison againstsystem.secret.
- All strategies fail and
required→ 401. Failure reason is never leaked.