Skip to content

Commit 1e65e56

Browse files
committed
🔄 Synced local '.' with remote 'apps/examples/express'
1 parent 807ca7a commit 1e65e56

23 files changed

+468
-1
lines changed

‎.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
AUTH_SECRET=
2+
3+
AUTH_GITHUB_ID=
4+
AUTH_GITHUB_SECRET=
5+
6+
AUTH_GOOGLE_ID=
7+
AUTH_GOOGLE_SECRET=

‎.eslintrc.cjs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
browser: true,
5+
es2021: true,
6+
},
7+
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
8+
overrides: [
9+
{
10+
env: {
11+
node: true,
12+
},
13+
files: [".eslintrc.{js,cjs}"],
14+
parserOptions: {
15+
sourceType: "script",
16+
},
17+
},
18+
],
19+
parser: "@typescript-eslint/parser",
20+
parserOptions: {
21+
ecmaVersion: "latest",
22+
sourceType: "module",
23+
},
24+
plugins: ["@typescript-eslint"],
25+
rules: {},
26+
}

‎.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# API keys and secrets
2+
.env
3+
4+
# Dependency directory
5+
node_modules
6+
7+
# Editors
8+
.idea
9+
*.iml
10+
.vscode/settings.json
11+
12+
# OS metadata
13+
.DS_Store
14+
Thumbs.db
15+
16+
# Ignore built ts files
17+
dist/**/*
18+
19+
# Ignore built css files
20+
/public/css/output.css
21+

‎.prettierignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
.DS_Store
3+
node_modules
4+
/dist
5+
/.turbo
6+
/package
7+
.env
8+
.env.*
9+
!.env.example
10+
11+
# Ignore files for PNPM, NPM and YARN
12+
pnpm-lock.yaml
13+
package-lock.json
14+
yarn.lock

‎.prettierrc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"semi": false,
3+
"plugins": ["@prettier/plugin-pug", "prettier-plugin-tailwindcss"],
4+
"pugClassNotation": "attribute"
5+
}

‎README.md

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,63 @@
1-
Todo
1+
> The example repository is maintained from a [monorepo](https://github.com/nextauthjs/next-auth/tree/main/apps/examples/express). Pull Requests should be opened against [`nextauthjs/next-auth`](https://github.com/nextauthjs/next-auth).
2+
3+
<p align="center">
4+
<br/>
5+
<a href="https://authjs.dev" target="_blank">
6+
<img height="64" src="https://authjs.dev/img/logo/logo-sm.png" />
7+
</a>
8+
<a href="https://expressjs.com" target="_blank">
9+
<img height="64" src="https://i.cloudup.com/zfY6lL7eFa-3000x3000.png" />
10+
</a>
11+
<h3 align="center"><b>Express Auth</b> - Example App</h3>
12+
<p align="center">
13+
Open Source. Full Stack. Own Your Data.
14+
</p>
15+
<p align="center" style="align: center;">
16+
<a href="https://npm.im/@auth/express">
17+
<img alt="npm" src="https://img.shields.io/npm/v/@auth/express?color=green&label=@auth/express&style=flat-square">
18+
</a>
19+
<a href="https://bundlephobia.com/result?p=@auth/express">
20+
<img src="https://img.shields.io/bundlephobia/minzip/@auth/express?label=size&style=flat-square" alt="Bundle Size"/>
21+
</a>
22+
<a href="https://www.npmtrends.com/@auth/express">
23+
<img src="https://img.shields.io/npm/dm/@auth/express?label=downloads&style=flat-square" alt="Downloads" />
24+
</a>
25+
<a href="https://npm.im/@auth/express">
26+
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
27+
</a>
28+
</p>
29+
</p>
30+
31+
## Overview
32+
33+
This is the official Express Auth example for [Auth.js](https://express.authjs.dev).
34+
35+
## Getting started
36+
37+
You can instantly deploy this example to [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=express-auth-example) by clicking the following button.
38+
39+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/nextauthjs/express-auth-example&project-name=express-auth-example&repository-name=express-auth-example)
40+
41+
## Environment Variables
42+
43+
Once deployed, kindly ensure you set all [required environment variables](https://authjs.dev/getting-started/deployment#environment-variables) in the `Environment` section of your hosting service.
44+
45+
## Node.js Compatibility
46+
47+
The recommended version of Node.js to use in this example is Node.js v20.0.0.
48+
49+
If you are using a version of Node.js lower than this (for example the minimum supported node version v18.0.0), you may need to enable Web Crypto API via the `--experimental-webcrypto` flag in the `start` and `dev` scripts of your `package.json` file.
50+
51+
Instead of using the experimental flag, you may use the following polyfill:
52+
53+
```ts
54+
// polyfill.cts
55+
globalThis.crypto ??= require("crypto").webcrypto
56+
```
57+
58+
And then import it within a top-level file in the application:
59+
60+
```ts
61+
// server.ts
62+
import "./polyfill.cjs"
63+
```

‎api/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import "../src/globals.js"
2+
3+
const { app } = await import("../src/app.js")
4+
5+
export default app

‎package.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"type": "module",
3+
"description": "Express Auth example app",
4+
"engines": {
5+
"node": ">=20.0.0"
6+
},
7+
"private": true,
8+
"scripts": {
9+
"start": "node dist/server.js",
10+
"build": "pnpm build:ts && pnpm build:css",
11+
"build:ts": "tsc",
12+
"build:css": "tailwindcss -i ./public/css/style.css -o ./public/css/output.css",
13+
"build:watch": "pnpm build:ts -w & pnpm build:css -w",
14+
"dev": "concurrently -k -p \"[{name}]\" -n \"Build,Node\" -c \"cyan.bold,green.bold\" \"pnpm run build:watch\" \"pnpm run dev:node\"",
15+
"dev:node": "nodemon dist/server.js",
16+
"lint": "eslint src/*.ts --fix",
17+
"prettier": "prettier src/*.ts --write"
18+
},
19+
"author": "Rexford Essilfie <[email protected]>",
20+
"license": "MIT",
21+
"dependencies": {
22+
"@auth/express": "latest",
23+
"dotenv": "^16.3.1",
24+
"express": "^4.18.2",
25+
"morgan": "^1.10.0",
26+
"pug": "^3.0.2",
27+
"tailwindcss": "^3.4.1"
28+
},
29+
"devDependencies": {
30+
"@prettier/plugin-pug": "^3.0.0",
31+
"@types/express": "^4.17.21",
32+
"@types/morgan": "^1.9.9",
33+
"@types/node": "^20.10.7",
34+
"@types/pug": "^2.0.10",
35+
"@typescript-eslint/eslint-plugin": "^6.18.0",
36+
"@typescript-eslint/parser": "^6.18.0",
37+
"concurrently": "6.5.1",
38+
"eslint": "^8.56.0",
39+
"nodemon": "2.0.22",
40+
"prettier": "3.1.1",
41+
"prettier-plugin-tailwindcss": "^0.5.11",
42+
"typescript": "5.3.3"
43+
}
44+
}

‎public/css/style.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
@tailwind base;
2+
3+
@tailwind components;
4+
5+
@tailwind utilities;

‎src/app.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import express, { Request, Response } from "express"
2+
import logger from "morgan"
3+
import * as path from "path"
4+
5+
import {
6+
errorHandler,
7+
errorNotFoundHandler,
8+
} from "./middleware/error.middleware.js"
9+
10+
import {
11+
authenticatedUser,
12+
currentSession,
13+
} from "./middleware/auth.middleware.js"
14+
import { ExpressAuth } from "@auth/express"
15+
import { authConfig } from "./config/auth.config.js"
16+
import * as pug from "pug"
17+
18+
// Create Express server
19+
export const app = express()
20+
21+
// Express configuration
22+
app.set("port", process.env.PORT || 3000)
23+
24+
// Set up views engine and path
25+
// @ts-expect-error (See https://stackoverflow.com/questions/45342307/error-cannot-find-module-pug)
26+
app.engine("pug", pug.__express)
27+
app.set("views", path.join(__dirname, "../views"))
28+
app.set("view engine", "pug")
29+
30+
// Trust Proxy for Proxies (Heroku, Render.com, etc)
31+
// https://stackoverflow.com/questions/40459511/in-express-js-req-protocol-is-not-picking-up-https-for-my-secure-link-it-alwa
32+
app.enable("trust proxy")
33+
34+
app.use(logger("dev"))
35+
36+
// Serve static files
37+
// NB: Uncomment this out if you want Express to serve static files for you vs. using a
38+
// hosting provider which does so for you (for example through a CDN).
39+
// app.use(express.static(path.join(__dirname, "../public")))
40+
41+
// Parse incoming requests data
42+
app.use(express.urlencoded({ extended: true }))
43+
app.use(express.json())
44+
45+
// Set session in res.locals
46+
app.use(currentSession)
47+
48+
// Set up ExpressAuth to handle authentication
49+
// IMPORTANT: It is highly encouraged set up rate limiting on this route especially if
50+
// it is served or hosted by a traditional server (compared to a serverless function)
51+
app.use("/api/auth/*", ExpressAuth(authConfig))
52+
53+
// Routes
54+
55+
app.get("/protected", async (_req: Request, res: Response) => {
56+
res.render("protected", { session: res.locals.session })
57+
})
58+
59+
app.get(
60+
"/api/protected",
61+
authenticatedUser,
62+
async (req: Request, res: Response) => {
63+
res.json(res.locals.session)
64+
},
65+
)
66+
67+
app.get("/", async (req: Request, res: Response) => {
68+
res.render("index", {
69+
title: "Express Auth Example",
70+
user: res.locals.session?.user,
71+
})
72+
})
73+
74+
// Error handlers
75+
app.use(errorNotFoundHandler)
76+
app.use(errorHandler)

‎src/config/auth.config.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import GitHub from "@auth/express/providers/github"
2+
import Google from "@auth/express/providers/google"
3+
4+
export const authConfig = {
5+
providers: [
6+
GitHub({
7+
clientId: process.env.AUTH_GITHUB_ID,
8+
clientSecret: process.env.AUTH_GITHUB_SECRET,
9+
}),
10+
Google({
11+
clientId: process.env.AUTH_GOOGLE_ID,
12+
clientSecret: process.env.AUTH_GOOGLE_SECRET,
13+
}),
14+
],
15+
}

‎src/errors.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export class HttpError extends Error {
2+
status: number
3+
constructor(status: number, message: string) {
4+
super(message)
5+
this.status = status
6+
}
7+
}
8+
9+
export class NotFoundError extends HttpError {
10+
constructor(message: string, status = 404) {
11+
super(status, message)
12+
this.name = "NotFoundError"
13+
}
14+
}

‎src/globals.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import path from "path"
2+
import { fileURLToPath } from "url"
3+
4+
globalThis.__filename ??= fileURLToPath(import.meta.url)
5+
globalThis.__dirname ??= path.dirname(globalThis.__filename)

‎src/middleware/auth.middleware.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { getSession } from "@auth/express"
2+
import { NextFunction, Request, Response } from "express"
3+
import { authConfig } from "../config/auth.config.js"
4+
5+
export async function authenticatedUser(
6+
req: Request,
7+
res: Response,
8+
next: NextFunction,
9+
) {
10+
const session = res.locals.session ?? (await getSession(req, authConfig))
11+
12+
res.locals.session = session
13+
14+
if (session) {
15+
return next()
16+
}
17+
18+
res.status(400).json({ message: "Not Authenticated" })
19+
}
20+
21+
export async function currentSession(
22+
req: Request,
23+
res: Response,
24+
next: NextFunction,
25+
) {
26+
const session = await getSession(req, authConfig)
27+
res.locals.session = session
28+
return next()
29+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { NextFunction, Request, Response } from "express"
2+
import { HttpError, NotFoundError } from "../errors.js"
3+
4+
export const errorHandler = (
5+
err: HttpError | Error,
6+
_req: Request,
7+
res: Response,
8+
next: NextFunction,
9+
): void => {
10+
// Render the error page
11+
res.status(("status" in err && err.status) || 500)
12+
res.render("error", {
13+
title: "status" in err ? err.status : err.name,
14+
message: err.message,
15+
})
16+
}
17+
18+
export const errorNotFoundHandler = (
19+
_req: Request,
20+
_res: Response,
21+
next: NextFunction,
22+
): void => {
23+
next(new NotFoundError("Not Found"))
24+
}

‎src/server.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import "./globals.js"
2+
import * as dotenv from "dotenv"
3+
4+
// Load environment variables
5+
dotenv.config({ path: __dirname + "/../.env" })
6+
7+
// Import app after environment variables are set
8+
const { app } = await import("./app.js")
9+
10+
const port = app.get("port")
11+
12+
const server = app.listen(port, () => {
13+
console.log(`Listening on port ${port}`)
14+
})
15+
16+
export default server

‎tailwind.config.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** @type {import('tailwindcss').Config} */
2+
export default {
3+
content: ["./src/*.{html,js,css}", "./views/*.pug"],
4+
theme: {
5+
extend: {},
6+
},
7+
plugins: [],
8+
}

0 commit comments

Comments
 (0)