Koa
WIPWriting the auth routes on Koa with @koa/router over auth.flows, guarding your own routes, and binding the actor
import { AuthError } from '@gentleduck/auth/core'
import { errorToHttp, nodeHeadersToFetch } from '@gentleduck/auth/server/generic'
import { koaActorContext, koaApplyIntents, koaCaller, koaCsrf } from '@gentleduck/auth/server/koa'
import { bodyParser } from '@koa/bodyparser'
import Router from '@koa/router'
import Koa from 'koa'
const app = new Koa({ proxy: true })
const router = new Router()
router.use('/auth', koaCsrf(auth))
router.post('/auth/signin', async (ctx) => {
const { providerId, input } = ctx.request.body ?? {}
if (typeof providerId !== 'string') throw new AuthError('AUTH_INVALID_PARAMETERS')
const headers = nodeHeadersToFetch(ctx.request.headers)
const { intents } = await auth.flows.signIn({
input: input ?? {},
providerId,
...koaCaller(ctx),
previousSid: auth.transport.extract({ headers }) ?? undefined,
})
await koaApplyIntents(intents, ctx)
})
router.post('/auth/signout', async (ctx) => {
const sid = auth.transport.extract({ headers: nodeHeadersToFetch(ctx.request.headers) })
await koaApplyIntents(sid ? (await auth.flows.signOut(sid)).intents : auth.transport.revoke(), ctx)
})
router.get('/auth/session', async (ctx) => {
const resolved = await auth.resolveSession({ headers: nodeHeadersToFetch(ctx.request.headers) }).orNull()
if (!resolved) return koaApplyIntents([{ type: 'json', status: 200, body: { session: null, identity: null } }], ctx)
// `csrfHash` is server-side state; the browser holds the plaintext
const { csrfHash: _csrfHash, ...session } = resolved.session
await koaApplyIntents([{ type: 'json', status: 200, body: { session, identity: resolved.identity } }], ctx)
})
app.use(async (ctx, next) => {
try {
await next()
} catch (err) {
const { status, body } = errorToHttp(err)
ctx.status = status
ctx.set('cache-control', 'no-store')
ctx.body = body
}
})
app.use(bodyParser())
app.use(router.routes())
- Koa answers a throw as plain text, so the first middleware answers it with
errorToHttpinstead: theAuthError'sstatusand the{ ok: false, error }body the other adapters send. proxy: truemakesctx.request.ipread the forwarded address, and so what the session records. Leave it off unless a proxy you control sets that header.- The provider routes are on the OAuth page. Mount the callback before
koaCsrf, and let the body parser read Apple's form post as text:bodyParser({ enableTypes: ['json', 'text'], extendTypes: { text: ['application/x-www-form-urlencoded'] } }).
Your own routes
router.use('/api', koaCsrf(auth), koaActorContext(auth))
koaActorContext throws a refusal, which the error middleware answers at the error's status.