Getting Started
Last updated
This page walks from installation to a first login, verification, and logout in a runnable Express app.
Prerequisites
| Item | Requirement |
|---|---|
| Node.js | 20 or higher (package.json engines) |
| Express | 4.18 or higher (peer dependency) |
| Redis | A reachable Redis server |
| Cookie parsing | cookie-parser or equivalent; the package reads tokens from req.cookies |
| Keys | An EC P-256 key pair; the signing algorithm is fixed to ES256 |
Installation
npm install @pardnchiu/jwt-auth express cookie-parser
Generate ES256 Keys
mkdir -p keys
openssl ecparam -name prime256v1 -genkey -noout -out keys/private.pem
openssl ec -in keys/private.pem -pubout -out keys/public.pem
RSA or HMAC keys cannot sign with ES256, so CreateJWT() throws.
First App
import express, { Request, Response, NextFunction } from "express";
import cookieParser from "cookie-parser";
import { JWTAuth } from "@pardnchiu/jwt-auth";
const app = express();
app.use(express.json());
app.use(cookieParser());
async function requireAuth(req: Request, res: Response, next: NextFunction) {
try {
const result = await JWTAuth.VerifyJWT(req, res);
if (!result.isAuth) {
// isError true means a suspicious request; otherwise a plain guest
return res.status(result.isError ? 400 : 401).json({ error: "unauthorized" });
}
res.locals.user = result.data;
next();
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
}
app.post("/login", async (req, res) => {
try {
// Issue tokens after verifying credentials
const result = await JWTAuth.CreateJWT(req, res, {
id: "user123",
name: "John",
email: "john@example.com",
});
res.json(result);
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}
});
app.get("/me", requireAuth, (req, res) => {
res.json({ user: res.locals.user });
});
app.post("/logout", async (req, res) => {
await JWTAuth.RevokeJWT(req, res);
res.json({ ok: true });
});
async function main() {
await JWTAuth.init({
privateKeyPath: "./keys/private.pem",
publicKeyPath: "./keys/public.pem",
accessTokenExpires: 900,
refreshTokenExpires: 604800,
isProd: false,
AccessTokenCookieKey: "access_token",
RefreshTokenCookieKey: "refresh_id",
redis: { host: "localhost", port: 6379 },
checkUserExists: async (userId) => true,
});
app.listen(3000);
}
main().catch((err) => {
// Key loading failed or Redis is unreachable
console.error(err);
process.exit(1);
});
Verify It Works
curl -i -c jar.txt -X POST http://localhost:3000/login
curl -i -b jar.txt http://localhost:3000/me
curl -i -b jar.txt -X POST http://localhost:3000/logout
curl -i -b jar.txt http://localhost:3000/me
With isProd: false the cookie domain is fixed to localhost, so use http://localhost rather than 127.0.0.1; otherwise neither browsers nor curl send the cookie back.
Next Steps
- When tokens refresh and when IDs rotate: Refresh Token Rotation
- Every config field: Configuration
- Mobile apps and other non-browser clients: Client Integration