Get started
Express
Callback, session middleware, route protection and sign-out for Express apps.
A complete server#
// server.ts — an Express app
import express from 'express';
import {
authMiddleware,
handleCallback,
handleSignOut,
requireAuth,
requirePermission,
verifyWebhook,
WebhookVerificationError,
} from '@3een/auth/express';
import type { AuthConfig } from '@3een/auth';
const config: AuthConfig = {
clientId: process.env.AUTH_CLIENT_ID!,
apiKey: process.env.AUTH_API_KEY!, // secret: server only
apiUrl: process.env.AUTH_API_URL!,
redirectUri: 'http://localhost:3000/dashboard',
};
const app = express();
// Webhooks first, with the raw body: the signature covers the exact bytes.
app.post('/webhooks/3een', express.raw({ type: 'application/json' }), (req, res) => {
try {
const event = verifyWebhook(req.body.toString('utf8'), req.header('3een-Signature'), process.env.AUTH_WEBHOOK_SECRET!);
console.info('3een event', event.type);
res.json({ received: true });
} catch (error) {
if (error instanceof WebhookVerificationError) {
res.status(400).json({ error: error.message });
return;
}
throw error;
}
});
// The hosted page returns here with ?code=… (register this exact URL as a
// redirect URI). The code is redeemed with your API key, the session cookies
// are set, and the browser goes on to a same-origin redirect_uri or
// config.redirectUri.
app.get('/auth/callback', handleCallback(config));
app.post('/auth/sign-out', handleSignOut(config));
// Loads req.user for every request; answers 401 on non-public routes.
app.use(authMiddleware({ ...config, publicRoutes: ['/', '/auth/*', '/webhooks/*'] }));
app.get('/dashboard', requireAuth, (req, res) => {
res.json({ user: req.user });
});
// Verifies the token against the environment's keys itself: 401 without a
// session, 403 without the permission; the claims are left on req.auth.
app.delete('/posts/:id', requirePermission(config, 'posts:delete'), (_req, res) => {
res.status(204).end();
});
app.get('/admin', requirePermission(config, { role: 'admin' }), (_req, res) => {
res.json({ ok: true });
});
// A standalone check that does not need authMiddleware to have run.
app.get('/api/me', requireAuth(config), (req, res) => {
res.json({ email: req.user?.email });
});
app.listen(3000);The sign-in callback#
handleCallback(config) is the route the hosted page returns to. Register its full URL (here http://localhost:3000/auth/callback) as a redirect URI. It redeems ?code= with your API key, bound to that exact URL, sets the session cookies and redirects to a same-origin redirect_uri, or to config.redirectUri. A legacy ?token= is accepted only if it verifies against your environment's keys.
Protecting routes#
authMiddleware(options)loadsreq.userfrom the API for every request and answers401on routes outsidepublicRoutes.requireAuthused bare expectsauthMiddlewareto have run;requireAuth(config)checks the session itself.requirePermission(config, …checks)verifies the token locally against the environment's keys:401without a session,403with{ error, missing }without the permission. The claims are left onreq.auth.
Sign-out#
handleSignOut(config) revokes the session with the API (a failure there is logged, not surfaced), clears the cookie and answers { success: true }.