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

中文