@digibuffer/auth-core
v1.0.2
Published
Auth library for JWT tokens and API keys — DB-agnostic, Edge-compatible, works anywhere.
Maintainers
Readme
@digibuffer/auth-core
Auth library for JWT tokens and API keys. DB-agnostic, Edge-compatible, works in Node.js and Edge runtime (Next.js middleware, Cloudflare Workers).
Features
- JWT — sign, verify, decode using
jose(Edge-compatible) - API Keys — generate, hash (SHA-256), constant-time compare
- Request helpers — extract Bearer token or
X-API-Keyfrom any Request - Next.js adapter —
withAuth,withToken,createNextMiddleware - DB-agnostic — you provide
getUserandgetUserByApiKeycallbacks - TypeScript — fully typed
Installation
npm install @digibuffer/auth-coreSetup
// lib/auth.ts
import { createAuth } from '@digibuffer/auth-core';
export const auth = createAuth({
jwt: {
secret: process.env.JWT_SECRET!,
expiresIn: '7d',
issuer: 'storagecone.com', // optional — embed in token, verified on decode
audience: 'app.storagecone.com', // optional
},
// Called when a valid JWT is found — load the user from your DB
getUser: async (userId) => {
return db.users.findUnique({ where: { id: userId } });
},
// Called when X-API-Key header is present — look up by hashed key
getUserByApiKey: async (hashedKey) => {
return db.apiKeys.findFirst({ where: { keyHash: hashedKey, active: true } });
},
});Both callbacks are optional. Omit getUser if you only need token payloads (no DB lookup). Omit getUserByApiKey if you don't use API keys.
JWT
Sign a token
const token = await auth.signToken({ sub: user.id, role: 'admin' });Verify a token
const payload = await auth.verifyToken(token);
// → { sub: 'user-123', role: 'admin', iat: ..., exp: ... } or nullDecode without verifying
const payload = auth.decodeToken(token);
// No signature check — use only when you already trust the sourceAPI Keys
Generate a key pair
const { key, hashedKey } = await auth.generateApiKey('sk');
// key → 'sk_aBcDeFgH...' show to user once, never store
// hashedKey → 'a3f5...' store this in your DB// Save to DB
await db.apiKeys.create({
data: { keyHash: hashedKey, userId: user.id, name: 'My API Key' },
});Hash a key manually
const hashedKey = await auth.hashApiKey(rawKey);Custom prefixes
await auth.generateApiKey('sk'); // sk_... (secret key)
await auth.generateApiKey('pk'); // pk_... (public key)
await auth.generateApiKey('tok'); // tok_... (token)Protecting Routes (Next.js App Router)
import { withAuth, withToken } from '@digibuffer/auth-core/adapters/next';
import { auth } from '@/lib/auth';
// Require a valid user (JWT + DB lookup via getUser)
export const GET = withAuth(auth, async (req, user) => {
return Response.json({ files: await db.files.findMany({ where: { userId: user.id } }) });
});
// Require a valid token only (no DB lookup — faster)
export const GET = withToken(auth, async (req, payload) => {
return Response.json({ userId: payload.sub, role: payload.role });
});Both wrappers return 401 with { error, code } JSON automatically on failure.
Middleware (Next.js)
// middleware.ts
import { createNextMiddleware } from '@digibuffer/auth-core/adapters/next';
import { auth } from '@/lib/auth';
export const middleware = createNextMiddleware(auth, {
publicPaths: ['/login', '/signup', '/api/webhooks', '/api/public'],
redirectTo: '/login', // web pages redirect here; API routes get 401 JSON
});
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Unauthenticated requests to /api/* get { "error": "Unauthorized" } with status 401. All other routes redirect to /login?callbackUrl=<original-path>.
Manual auth context
For more control, use getAuthContext directly:
export async function GET(req: Request) {
const ctx = await auth.getAuthContext(req);
if (!ctx.user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// ctx.user → full user object (from getUser callback)
// ctx.userId → user ID (from JWT sub or API key lookup)
// ctx.tokenPayload → full JWT payload (only when auth via JWT)
return Response.json({ user: ctx.user });
}Auth resolution order:
Authorization: Bearer <token>header → verifies JWT → callsgetUserX-API-Key: <key>header → hashes key → callsgetUserByApiKey- Returns
{}if neither is valid (never throws)
For a hard require, use auth.requireUser(req) or auth.requireToken(req) — these throw AuthError on failure.
Cross-product SSO
Issue a token from one product and verify it in another using a shared secret and issuer/audience:
// storagecone.com — issue token on login
const token = await auth.signToken({
sub: user.id,
role: user.role,
// JWT carries issuer: 'storagecone.com', audience: 'marketplace.storagecone.com'
});
// marketplace.storagecone.com — verify the same token
const marketplaceAuth = createAuth({
jwt: {
secret: process.env.SHARED_JWT_SECRET!,
issuer: 'storagecone.com',
audience: 'marketplace.storagecone.com',
},
getUser: async (id) => marketplaceDb.users.findUnique({ where: { id } }),
});API Reference
createAuth(config) → Auth
| Config field | Type | Description |
|---|---|---|
| jwt.secret | string | Signing secret — keep this in env vars |
| jwt.expiresIn | string \| number | e.g. '7d', '1h', 3600. Default: '7d' |
| jwt.issuer | string | Optional issuer claim |
| jwt.audience | string \| string[] | Optional audience claim |
| getUser | (id) => AuthUser \| null | DB callback for JWT auth |
| getUserByApiKey | (hash) => AuthUser \| null | DB callback for API key auth |
Auth instance
| Method | Returns | Description |
|---|---|---|
| signToken(payload) | Promise<string> | Sign a JWT |
| verifyToken(token) | Promise<TokenPayload \| null> | Verify a JWT |
| decodeToken(token) | TokenPayload \| null | Decode without verifying |
| generateApiKey(prefix?) | Promise<ApiKeyPair> | Generate { key, hashedKey } |
| hashApiKey(key) | Promise<string> | SHA-256 hash a raw key |
| getAuthContext(req) | Promise<AuthContext> | Resolve auth, never throws |
| requireUser(req) | Promise<AuthUser> | Throws AuthError if no valid user |
| requireToken(req) | Promise<TokenPayload> | Throws AuthError if no valid token |
| extractBearerToken(req) | string \| null | Read Bearer token from headers/cookie |
| extractApiKey(req) | string \| null | Read X-API-Key header |
AuthError
throw new AuthError('Forbidden', 'FORBIDDEN', 403);
// .message → 'Forbidden'
// .code → 'UNAUTHORIZED' | 'FORBIDDEN' | 'INVALID_TOKEN' | 'EXPIRED_TOKEN'
// .statusCode → 403License
Proprietary — All rights reserved.
