A minimal demo app showing JWT authentication in Node.js using Express.
- Backend: Express API that issues JWTs on login and protects routes with
express-jwt. - Frontend: A single
index.htmlpage that logs in, stores the token inlocalStorage, and calls protected endpoints.
.
├── server.js # Express server + JWT auth middleware
├── index.html # Simple UI (Axios) to login + call protected routes
├── package.json
└── package-lock.json
- Node.js + npm
npm install
node server.jsThen open:
The server listens on port 3000 and serves index.html from /. fileciteturn1file0L18-L19 fileciteturn1file0L88-L90
Users are hard-coded in server.js: fileciteturn1file0L25-L36
fabio/123nolasco/456
Note: due to the current login loop implementation, only the first matching check reliably works (see Known issues). fileciteturn1file0L41-L61
POST /api/login expects JSON:
{ "username": "fabio", "password": "123" }If credentials match, the server signs a JWT with payload { id, username } and an expiry of 3 minutes. fileciteturn1file0L38-L47
Example:
curl -s -X POST http://localhost:3000/api/login -H "Content-Type: application/json" -d '{"username":"fabio","password":"123"}'Response:
{
"success": true,
"err": null,
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}The app uses express-jwt middleware configured with HS256 and the same secret used to sign tokens. fileciteturn1file0L19-L23
Protected endpoints:
GET /api/dashboardfileciteturn1file0L64-L70GET /api/pricesfileciteturn1file0L72-L78GET /api/settingsfileciteturn1file0L80-L86
Example:
TOKEN="<paste token here>"
curl -s http://localhost:3000/api/dashboard -H "Authorization: Bearer $TOKEN"If the token is missing/invalid/expired, the server returns 401 Unauthorized. fileciteturn1file0L92-L99
- On login, the browser saves the token to
localStorageunder the keyjwt, then loads the dashboard. fileciteturn1file1L38-L50 - Protected API calls include the JWT via the
Authorization: Bearer ...header. fileciteturn1file1L53-L59 - The page decodes the JWT payload and checks for expiry periodically, redirecting back to
/when it detects expiration. fileciteturn1file2L60-L89
These are present in the current code as uploaded:
-
Login loop returns 401 too early
Theelseclause inside theforloop sends a 401 response on the first non-match, which prevents checking the rest of the users list. fileciteturn1file0L41-L61 -
CORS header typo
Access-Control-Allow-HeadersincludesAythorization(typo). If you call this API cross-origin, you'll wantAuthorizationinstead. fileciteturn1file0L12-L15 -
Token expires in 3 minutes, but UI checks every 4 minutes
Token expiry is set to3m. fileciteturn1file0L43-L47
The UI checks for expiry every240000ms (4 minutes). fileciteturn1file2L81-L90
- Move users to a database and hash passwords (
bcrypt) - Store the JWT secret in environment variables (
process.env.JWT_SECRET) - Add refresh tokens + logout
- Add tests (Jest / Supertest)
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/login |
No | Returns a JWT if credentials match |
| GET | /api/dashboard |
Yes | Example protected resource |
| GET | /api/prices |
Yes | Example protected resource |
| GET | /api/settings |
Yes | Example protected resource |
| GET | / |
No | Serves index.html |