Express adapter
WIPExpress 4+ middleware. Each mount returns an Express RequestHandler.
Mount
import express from 'express'
import {
authMountProviderBegin,
authMountSession,
authMountSignIn,
authMountSignOut,
} from '@gentleduck/auth/server/express'
import { auth } from './lib/auth'
const app = express()
app.use(express.json()) // duck-auth expects JSON bodies
app.post('/auth/signin', authMountSignIn(auth))
app.post('/auth/signout', authMountSignOut(auth))
app.get('/auth/session', authMountSession(auth))
// Provider begin: redirect to OAuth IdP, send a magic link, etc.
app.post('/auth/providers/:providerId/begin', (req, res, next) => {
return authMountProviderBegin(auth, req.params.providerId)(req, res, next)
})
// OAuth callback: re-use the sign-in handler with the providerId pre-set.
app.get('/auth/google/callback', authMountSignIn(auth))
app.get('/auth/github/callback', authMountSignIn(auth))
app.listen(3000)
In a route
import { AuthError } from '@gentleduck/auth/core'
app.get('/api/me', async (req, res, next) => {
try {
const ctx = await auth.resolveSession(req)
if (!ctx) throw new AuthError('AUTH/UNAUTHENTICATED')
res.json({ identity: ctx.identity, aal: ctx.session.aal })
} catch (err) {
next(err)
}
})
// Centralised error handler.
app.use((err, req, res, next) => {
if (err instanceof AuthError) {
res.status(err.status).json(err.toJSON())
return
}
next(err)
})
Apply intents manually
For custom routes where you need to drive the runtime by hand:
import { authApplyIntents } from '@gentleduck/auth/server/express'
app.post('/auth/custom', async (req, res, next) => {
try {
const intents = await auth.flows.signIn({
providerId: 'password',
input: req.body,
})
authApplyIntents(intents, res)
} catch (err) {
next(err)
}
})
authApplyIntents mutates res - it sets cookies via res.cookie(...),
writes the JSON body, sets status and headers. Equivalent to what the
shipped mount helpers do internally.
CSRF guard
import { authCsrfGuard } from '@gentleduck/auth/core'
app.use(async (req, res, next) => {
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
try {
await authCsrfGuard(auth, req)
} catch (err) {
next(err)
return
}
}
next()
})
The guard is a no-op for non-cookie transports - if your request came in as a Bearer or JWT, the call returns silently.