A complete guide and implementation of JWT-based authentication in Node.js + Express, covering Access Tokens, Refresh Tokens, and Role-Based Access Control (RBAC) with best practices.
A: JWT stands for JSON Web Token. It is a compact, URL-safe way of securely transmitting information between two parties (like client and server) as a JSON object.
A: A JWT has 3 parts separated by dots (.
):
π Header β contains metadata (e.g., algorithm & token type).
π Payload β contains claims (e.g., user id, roles, expiration).
π Signature β created using a secret key (or private key) to verify the tokenβs authenticity.
π These three parts are Base64Url-encoded and combined like:
header.payload.signature
A: (using jsonwebtoken
library)
π jwt.sign(payload, secret, options)
β Creates a new token.
π jwt.verify(token, secret)
β Verifies and decodes a token.
π jwt.decode(token)
β Decodes a token without verification (use carefully).
A: When creating the token, you pass claims (like id, email, or role) inside the payload:
const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET, {
expiresIn: "1h",
});
You can also pass more options:
const token = jwt.sign(
{ id: 1, role: "admin" }, // payload
process.env.JWT_SECRET, // secret
{
expiresIn: "1h", // token expires in 1 hour
issuer: "myapp.com", // who issued the token
audience: "myapp-users", // who the token is for
subject: "user-authentication", // subject of the token
algorithm: "HS256", // signing algorithm
}
);
A: Yes β
.
When creating a token, you can include the userβs role inside the payload claims (e.g., "role": "admin"
).
Later, when verifying the token, you check the role to determine if the user has authorization for that route.
A: A refresh token is a special token with a longer expiry time than an access token.
π Access Token β short-lived (e.g., 15 minutes β 1 hour).
π Refresh Token β long-lived (e.g., days, weeks, or months).
π We use the refresh token to get a new access token without forcing the user to log in again. Typically, the refresh token is stored securely (e.g., in an HTTP-only cookie or database).
Flow:
π User logs in β receives access token + refresh token.
π When the access token expires β client sends refresh token to the server.
π Server verifies the refresh token β issues a new access token.
A: If the refresh token has expired, the client must log in again because thereβs no valid way to request a new access token.
A: By default, JWTs do not expire unless you explicitly set the exp
claim.
β’ This means the token will be valid forever, which is not secure.
β’ β
Best practice β always set an expiration time (expiresIn
) when signing a token.
// Access Token: 15 minutes
const accessToken = jwt.sign({ id: user.id }, process.env.JWT_SECRET, {
expiresIn: "15m",
});
// Refresh Token: 7 days
const refreshToken = jwt.sign({ id: user.id }, process.env.JWT_REFRESH_SECRET, {
expiresIn: "7d",
});
β’ Q: What is refresh token and how we use it? A: Refresh token is same token as access token but it has expiry time more than normal token. We use it to get a new token without going through the login process again.
β’ Q: What happens if refresh token expired? A: If refresh token expired, then no way to access the API. The client has to login again.
β’ Q: What happens if we donβt mention expiry time in token? A: The default expiry time of JWT token is never expired.
β’ Q: What is the difference between access token and refresh token? A: Access tokens are short-lived and used to access APIs. Refresh tokens are long-lived and used to get new access tokens when the old one expires.
β’ Q: Where should I store JWT in frontend?
A: Preferably in httpOnly
cookies for security. Storing in localStorage
or sessionStorage
is possible but more vulnerable to XSS attacks.
β’ Q: Can JWT be revoked? A: JWTs are stateless, so once issued they cannot be revoked unless you keep a blacklist or use short expiration times with refresh tokens.
β’ Q: Is JWT secure? A: JWT is secure if you use a strong secret/private key, HTTPS, short expiration times, and donβt store sensitive data inside the payload.
β’ Q: What is the difference between JWT and sessions? A: Sessions store data on the server (stateful), while JWT stores claims in the token itself (stateless).
β’ Q: What are JWT aud
, iss
, sub
, exp
claims?
A: These are registered claims:
iss
: issuer (who issued the token)sub
: subject (whom the token refers to)aud
: audience (intended recipient)exp
: expiration time
A:
π Use short-lived access tokens β Keep access tokens valid only for a few minutes (e.g., 15 mins) to reduce risk if stolen.
π Use refresh tokens securely β Keep refresh tokens long-lived but store them securely (preferably in httpOnly
cookies). Rotate them frequently.
π Always use HTTPS β Never send JWTs over plain HTTP. Use HTTPS to protect against man-in-the-middle attacks.
π Store tokens in httpOnly
cookies β This protects against XSS attacks. Avoid localStorage if possible.
π Do not store sensitive data in JWT payload β Payload is Base64Url encoded (not encrypted). Anyone can decode it, so only store minimal info (e.g., id
, role
).
π Validate token signature and claims β Always check signature, issuer (iss
), audience (aud
), and expiration (exp
) before trusting a token.
π Revoke tokens when needed β JWTs are stateless, so implement blacklists/whitelists or use short expirations + refresh tokens to handle revocation.
π Rotate signing keys β Use key rotation strategies (like JWKS). Donβt keep the same secret forever.
π Handle token expiry properly β When access token expires, issue a new one using the refresh token. If refresh token also expires, force login again.
π Protect against CSRF β If using cookies, add CSRF tokens or use SameSite=strict
/ lax
cookie flags.
git clone https://github.com/your-username/jwt-auth-node-express.git
cd jwt-auth-node-express
npm install
Create a .env
file in the project root with the following:
PORT=5000
JWT_SECRET=your_secret_key
JWT_EXPIRES_IN=15m # Access Token expiry
JWT_REFRESH_EXPIRES_IN=7d # Refresh Token expiry
npm start
or for development with auto-restart (if you use nodemon):
npm run dev
- POST
/api/auth/signup
β Register a new user - POST
/api/auth/login
β Login with email & password (returns Access + Refresh token) - POST
/api/auth/refresh
β Get a new access token using refresh token - POST
/api/auth/logout
β (Optional) Invalidate refresh token
- GET
/api/protected
β Example protected route (requires valid JWT Access Token) - GET
/api/admin
β Example admin-only route (requires JWT + role = admin)
Use the Authorization
header in requests to protected routes:
Authorization: Bearer <your_access_token>
π User signs up β logs in β receives Access Token + Refresh Token.
π User calls protected APIs with Access Token in headers.
π When Access Token expires β user calls /refresh
with Refresh Token.
π Server verifies Refresh Token and issues a new Access Token.
π If Refresh Token also expires β user must log in again.
jwt-auth-node-express/
βββ services # Services for controllers (auth, user etc.)
βββ controllers/ # Route controllers (auth, user, etc.)
βββ errors/ # For custom errors and validation
βββ middlewares/ # Auth & role-based middleware
βββ models/ # Database models (User, Token, etc.)
βββ routes/ # API routes (auth.js, user.js, etc.)
βββ config/ # JWT/DB config
βββ server.js # Entry point
βββ package.json
βββ .env
- Node.js β Runtime
- Express.js β Web framework
- jsonwebtoken β JWT handling
- bcrypt β Password hashing
- dotenv β Environment variables
- (Optional: Sequelize/Mongoose if using DB)
β‘ With these steps, you can set up JWT authentication with Access Tokens, Refresh Tokens, and Role-Based Access Control in Node.js + Express.