Express
Build your first authentication flow with Aura Auth and Express
This guide walks you through to implement Aura Auth in a Express application to a complete support for IncomingMessage and ServerResponse objects.
If you haven't configured Aura Auth yet, start with the Installation Guide and Quick Start Guide to set up your Auth instance and environment variables. Then follow the steps in this guide to integrate Aura Auth with your Express application.
Aura Auth is working on developing a Express-specific package that will provide additional utilities for Express applications. For now, the core package can be used to implement authentication in Express with the patterns outlined in this guide. Stay tuned for updates on the Express package!
Setup Aura Auth
Create an Auth Instance
Create an auth.ts file in src/lib directory to configure your Aura Auth instance.
import { createAuth } from "@aura-stack/auth"
export const auth = createAuth({
oauth: ["github"],
basePath: "/api/auth",
baseURL: "http://localhost:3000",
})
export const { handlers, jose, api } = authThe basePath should match the path where your auth route handlers are mounted and baseURL should point to your local
development server or deployed application URL.
Mount HTTP Handlers
Because Express uses IncomingMessage and ServerResponse, create an adapter that converts them into the web-standard Request and Response objects Aura Auth expects.
The adapter below translates the incoming request into a Web Request, then translates the Aura Auth Response back into Express.
import { handlers } from "@/lib/auth.js"
import type { Request, Response } from "express"
const splitSetCookieHeaderValue = (value: string): string[] => {
return value
.split(/,(?=\s*[^;,\s]+=)/g)
.map((cookie) => cookie.trim())
.filter(Boolean)
}
/**
* Convert an Express request to a Web API Request so it can be handled
* by the framework-agnostic Aura Auth handlers.
*/
export const toWebRequest = (req: Request): globalThis.Request => {
const method = req.method ?? "GET"
const protocol = req.protocol ?? "http"
const host = req.get("host") ?? "localhost"
const baseURL = `${protocol}://${host}`
const url = new URL(req.originalUrl ?? req.url, baseURL)
const headers = new Headers()
for (const [key, value] of Object.entries(req.headers)) {
if (Array.isArray(value)) {
for (const v of value) headers.append(key, v)
} else if (value !== undefined) {
headers.set(key, value)
}
}
const body = method !== "GET" && method !== "HEAD" && req.body !== undefined ? JSON.stringify(req.body) : undefined
if (body !== undefined) {
headers.set("content-type", "application/json")
}
return new globalThis.Request(url, {
method,
headers,
body,
})
}
/**
* Forward the Web API Response back to the Express response.
* Handles redirects, multiple Set-Cookie headers, and JSON/text bodies.
*/
export const toExpressResponse = async (webResponse: globalThis.Response, res: Response) => {
for (const [key, value] of webResponse.headers.entries()) {
if (key.toLowerCase() === "set-cookie") {
const cookies = webResponse.headers.getSetCookie?.() ?? splitSetCookieHeaderValue(value)
for (const cookie of cookies) {
res.append("Set-Cookie", cookie)
}
} else {
res.setHeader(key, value)
}
}
res.status(webResponse.status)
if (webResponse.status >= 300 && webResponse.status < 400) {
const location = webResponse.headers.get("location") ?? "/"
return res.redirect(webResponse.status, location)
}
const contentType = webResponse.headers.get("content-type")
if (contentType?.includes("application/json")) {
const data = await webResponse.json()
return res.json(data)
}
const text = await webResponse.text()
return res.send(text)
}
/**
* Express middleware that bridges Aura Auth Web API handlers to Express.
* Mount this on the `basePath` configured in `createAuth()` (default: `/api/auth`).
*/
export const toExpressHandler = async (req: Request, res: Response) => {
const webResponse = await handlers.ALL(toWebRequest(req))
return toExpressResponse(webResponse, res)
}Mount the Aura Auth handlers inside your Express app using the Web Standard adapter.
import express, { type Express } from "express"
import { toExpressHandler } from "@/lib/handler.js"
const app: Express = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.all("/api/auth/*", toExpressHandler)
app.listen(3000, () => {
console.log("Server running on http://localhost:3000")
})Use this route for all auth methods, including sign-in, sign-out, session lookups, and provider callbacks.
Usage
Middleware
Middlewares are a common pattern in Express apps for protecting routes. You can create a middleware that checks for an active session before allowing access to protected routes.
import { toWebRequest } from "@/lib/handler.ts"
import type { RequestHandler } from "express"
import type { AuthInstance, User, Session } from "@aura-stack/auth"
export type LocalsWithSession<DefaultUser extends User = User> = {
session?: Session<DefaultUser> | null
}
export const withAuth = <DefaultUser extends User = User>({
api,
}: AuthInstance<DefaultUser>): RequestHandler<any, any, any, any, LocalsWithSession<DefaultUser>> => {
return async (req, res, next) => {
try {
const webRequest = toWebRequest(req)
const { session, headers } = await api.getSession({
headers: webRequest.headers,
})
for (const [key, value] of headers.entries()) {
if (key.toLowerCase() === "set-cookie") {
res.append(key, value)
} else {
res.setHeader(key, value)
}
}
res.locals.session = session
return next()
} catch (error) {
return next(error)
}
}
}Get Session
To access the active session in your protected Express routes, use the withAuth middleware and read res.locals.session.
import express, { type Express } from "express"
import { toExpressHandler } from "@/lib/handler.js"
import { withAuth } from "@/middlewares/with-auth.js"
const app: Express = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.all("/api/auth/*", toExpressHandler)
app.get("/api/protected", withAuth, (req, res) => {
const session = res.locals.session
return res.json({
message: "You have access to this protected resource.",
session,
})
})
app.listen(3000, () => {
console.log("Server running on http://localhost:3000")
})This pattern keeps the session lookup centralized and makes protected route logic easy to reuse across the app.
Common Pitfalls
- Keep
basePathaligned with the mounted auth route. If your auth endpoint is/api/auth/*, the auth config should usebasePath: "/api/auth". - Always convert Express headers before calling Aura Auth. The Web API session helper expects standard request headers.
- Use
session.authenticatedas the guard. Check that flag before exposing private data. - Keep the adapter and middleware separate. The adapter should only translate request and response objects, while the middleware should only enforce access.