Skip to main content

Express adapter

WIP

Express 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.