@auth/express
Version:
Authentication for Express.
179 lines (178 loc) • 6.74 kB
JavaScript
/**
*
* :::warning
* `@auth/express` is currently experimental. The API _will_ change in the future.
* :::
*
* Express Auth is the official Express integration for Auth.js.
* It provides a simple way to add authentication to your Express app in a few lines of code.
*
* ## Installation
* ```bash npm2yarn
* npm install @auth/express
* ```
*
* ## Usage
*
* ```ts title="src/routes/auth.route.ts"
* import { ExpressAuth } from "@auth/express"
* import GitHub from "@auth/express/providers/github"
* import express from "express"
*
* const app = express()
*
* // If app is served through a proxy, trust the proxy to allow HTTPS protocol to be detected
* // https://expressjs.com/en/guide/behind-proxies.html
* app.set('trust proxy', true)
* app.use("/auth/*", ExpressAuth({ providers: [ GitHub ] }))
* ```
*
* Don't forget to set the `AUTH_SECRET` environment variable. This should be a minimum of 32 characters, random string. On UNIX systems you can use `openssl rand -hex 32` or check out `https://generate-secret.vercel.app/32`.
*
* You will also need to load the environment variables into your runtime environment. For example in Node.js with a package like [`dotenv`](https://www.npmjs.com/package/dotenv) or `Deno.env` in Deno.
*
* ### Provider Configuration
* The callback URL used by the [providers](https://authjs.dev/reference/core/providers) must be set to the following, unless you mount the `ExpressAuth` handler on a different path:
*
* ```
* [origin]/auth/callback/[provider]
* ```
*
* ## Signing in and signing out
* Once your application is mounted you can sign in or out by making requests to the following [REST API endpoints](https://authjs.dev/reference/core/types#authaction) from your client-side code.
* NB: Make sure to include the `csrfToken` in the request body for all sign-in and sign-out requests.
*
* ## Managing the session
* If you are using Express with a template engine (e.g EJS, Pug), you can make the session data available to all routes via middleware as follows
*
* ```ts title="app.ts"
* import { getSession } from "@auth/express"
*
* export function authSession(req: Request, res: Response, next: NextFunction) {
* res.locals.session = await getSession(req)
* next()
* }
*
* app.use(authSession)
*
* // Now in your route
* app.get("/", (req, res) => {
* const { session } = res.locals
* res.render("index", { user: session?.user })
* })
* ```
*
* ## Authorization
* You can protect routes by checking for the presence of a session and then redirect to a login page if the session is not present.
* This can either be done per route, or for a group of routes using a middleware such as the following:
*
* ```ts
* export async function authenticatedUser(
* req: Request,
* res: Response,
* next: NextFunction
* ) {
* const session = res.locals.session ?? (await getSession(req, authConfig))
* if (!session?.user) {
* res.redirect("/login")
* } else {
* next()
* }
* }
* ```
*
* ### Per Route
* To protect a single route, simply add the middleware to the route as follows:
* ```ts title="app.ts"
* // This route is protected
* app.get("/profile", authenticatedUser, (req, res) => {
* const { session } = res.locals
* res.render("profile", { user: session?.user })
* })
*
* // This route is not protected
* app.get("/", (req, res) => {
* res.render("index")
* })
*
* app.use("/", root)
* ```
* ### Per Group of Routes
* To protect a group of routes, define a router and add the middleware to the router as follows:
*
* ```ts title="routes/protected.route.ts"
* import { Router } from "express"
*
* const router = Router()
*
* router.use(authenticatedUser) // All routes defined after this will be protected
*
* router.get("/", (req, res) => {
* res.render("protected")
* })
*
* export default router
* ```
*
* Then we mount the router as follows:
* ```ts title="app.ts"
* import protected from "./routes/protected.route"
*
* app.use("/protected", protected)
* ```
*
* ## Notes on ESM
* @auth/express is ESM only. This means your package.json must contain `"type": "module"` and tsconfig.json should contain `"module": "NodeNext"` or `ESNext`.
* File imports must use the `.js` extension, e.g. `import { MyRouter } from "./my-router.js"`.
*
* Your dev server should either be run with [tsx](https://www.npmjs.com/package/tsx) with `tsx index.ts` (fast startup, with no type checking), or ts-node with 'node --loader ts-node/esm index.ts' (slower startup, but has type checking).
*
* While it is NOT recommended, if you wish to use @auth/express within a CommonJS project without migrating and making the above changes, you can run the dev server with tsx and may be able to compile with [pkgroll](https://tsx.is/compilation).
* Add '"name": "./dist/index.js"' or '"name": "./dist/index.mjs"' to your package.json and run 'pkgroll' to compile with both ESM and CommonJS support. For new projects it is recommended to just use ESM.
*
* @module @auth/express
*/
import { Auth, setEnvDefaults, createActionURL, customFetch, } from "@auth/core";
import * as e from "express";
import { toWebRequest, toExpressResponse } from "./lib/index.js";
export { customFetch };
export { AuthError, CredentialsSignin } from "@auth/core/errors";
export function ExpressAuth(config) {
return async (req, res, next) => {
e.json()(req, res, async (err) => {
if (err)
return next(err);
e.urlencoded({ extended: true })(req, res, async (err) => {
if (err)
return next(err);
try {
config.basePath = getBasePath(req);
setEnvDefaults(process.env, config);
await toExpressResponse(await Auth(toWebRequest(req), config), res);
if (!res.headersSent)
next();
}
catch (error) {
next(error);
}
});
});
};
}
export async function getSession(req, config) {
setEnvDefaults(process.env, config);
const url = createActionURL("session", req.protocol,
// @ts-expect-error
new Headers(req.headers), process.env, config);
const response = await Auth(new Request(url, { headers: { cookie: req.headers.cookie ?? "" } }), config);
const { status = 200 } = response;
const data = await response.json();
if (!data || !Object.keys(data).length)
return null;
if (status === 200)
return data;
throw new Error(data.message);
}
function getBasePath(req) {
return req.baseUrl.split(req.params[0])[0].replace(/\/$/, "");
}