Skip to main content
Node Blueprint CLI includes production-ready JWT authentication with secure password hashing, refresh tokens, and role-based access control.

Overview

When you enable JWT authentication, the CLI generates:
  • Complete authentication system with signup/login/logout
  • Bcrypt password hashing
  • Access and refresh token management
  • Token storage in database
  • Role-based user system (admin/user)
  • Authentication middleware
  • Validation schemas with Zod
Authentication is optional. Select “jwt-auth” when prompted or choose “none” to skip authentication setup.

Features

JWT Tokens

Access tokens for API requests and refresh tokens for session management

Bcrypt Hashing

Secure password hashing with configurable salt rounds

Role-Based Access

Built-in admin and user roles for authorization

Token Storage

Refresh tokens stored in database with proper relations

Generated Files

Authentication adds these files to your project:
src/
├── routes/
│   └── auth-routes.ts        # Auth endpoints (signup, login, logout)
├── controllers/
│   └── auth-controller.ts    # Auth request handlers
├── services/
│   └── auth-services.ts      # JWT generation and verification
├── validations/
│   └── auth-validations.ts   # Zod schemas for validation
├── enums/
│   ├── role-enum.ts          # User roles (admin, user)
│   └── token-enum.ts         # Token types
└── db/schema/               # or models/ for Mongoose
    └── token-schema.ts       # Token storage schema

Authentication Flow

1. User Signup

Endpoint: POST /api/v1/auth/signup Request Body:
{
  "name": "John Doe",
  "email": "[email protected]",
  "password": "SecurePass123!"
}
Response:
{
  "success": true,
  "message": "User created successfully",
  "data": {
    "user": {
      "id": "uuid",
      "name": "John Doe",
      "email": "[email protected]",
      "role": "user"
    },
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

2. User Login

Endpoint: POST /api/v1/auth/login Request Body:
{
  "email": "[email protected]",
  "password": "SecurePass123!"
}
Response:
{
  "success": true,
  "message": "Login successful",
  "data": {
    "user": {
      "id": "uuid",
      "name": "John Doe",
      "email": "[email protected]",
      "role": "user"
    },
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

3. User Logout

Endpoint: POST /api/v1/auth/logout Headers:
Authorization: Bearer <access_token>
Response:
{
  "success": true,
  "message": "Logout successful"
}

Token Management

Access Tokens

  • Short-lived tokens for API authentication
  • Included in Authorization header
  • Default expiration: 15 minutes (configurable)

Refresh Tokens

  • Long-lived tokens for obtaining new access tokens
  • Stored in database with user relation
  • Default expiration: 7 days (configurable)
  • Can be revoked by logging out

Token Types

Token Enum (src/enums/token-enum.ts):
export enum TokenEnum {
    REFRESH_TOKEN = "refresh_token",
    RESET_PASSWORD_TOKEN = "reset_password_token",
    VERIFY_EMAIL_TOKEN = "verify_email_token"
}

Database Schema

Token Schema (src/db/schema/token-schema.ts):
import { pgTable, text, timestamp, uuid, pgEnum } from "drizzle-orm/pg-core";
import { userSchema } from "./user-schema.js";
import { TokenEnum } from "../../enums/token-enum.js";

// create token enum type
const tokenEnum = pgEnum("token", Object.values(TokenEnum) as [string, ...string[]]);

// schema definition
export const tokenSchema = pgTable("token", {
id: uuid("id").primaryKey().unique().notNull().defaultRandom(),
value: text("value").notNull(),
type: tokenEnum().notNull(),
userId: uuid("user_id").notNull().references(() => userSchema.id),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow()
});

User Roles

Role Enum (src/enums/role-enum.ts):
export enum RoleEnum {
    ADMIN = "admin",
    USER = "user"
}
Roles are stored in the user table and can be used for authorization:
// Example: Check if user is admin
if (user.role === RoleEnum.ADMIN) {
    // Admin-only logic
}

Validation Schemas

Auth Validations (src/validations/auth-validations.ts):
import { z } from "zod";

export const signupSchema = z.object({
    name: z.string().min(2).max(50).optional(),
    email: z.string().email(),
    password: z.string().min(8).max(100)
});

export const loginSchema = z.object({
    email: z.string().email(),
    password: z.string()
});

export type SignupInput = z.infer<typeof signupSchema>;
export type LoginInput = z.infer<typeof loginSchema>;
The default validation requires:
  • Minimum 8 characters
  • Maximum 100 characters
You can customize these requirements in auth-validations.ts:
password: z.string()
    .min(8, "Password must be at least 8 characters")
    .max(100, "Password must be less than 100 characters")
    .regex(/[A-Z]/, "Password must contain uppercase letter")
    .regex(/[a-z]/, "Password must contain lowercase letter")
    .regex(/[0-9]/, "Password must contain a number")
    .regex(/[^A-Za-z0-9]/, "Password must contain special character")

JWT Service

Auth Services (src/services/auth-services.ts):
import jwt from "jsonwebtoken";
import { env } from "../config/env.js";

export const generateAccessToken = (userId: string) => {
    return jwt.sign(
        { userId },
        env.JWT_ACCESS_SECRET,
        { expiresIn: "15m" }
    );
};

export const generateRefreshToken = (userId: string) => {
    return jwt.sign(
        { userId },
        env.JWT_REFRESH_SECRET,
        { expiresIn: "7d" }
    );
};

export const verifyAccessToken = (token: string) => {
    return jwt.verify(token, env.JWT_ACCESS_SECRET);
};

export const verifyRefreshToken = (token: string) => {
    return jwt.verify(token, env.JWT_REFRESH_SECRET);
};

Environment Variables

Authentication requires these environment variables in .env:
# JWT Secrets (generate secure random strings)
JWT_ACCESS_SECRET=your-super-secret-access-key-change-this
JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this

# Optional: Token expiration times
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

# Bcrypt salt rounds
BCRYPT_SALT_ROUNDS=10
Always use strong, randomly generated secrets in production. Never commit your .env file to version control.

Dependencies

{
  "dependencies": {
    "jsonwebtoken": "^9.0.2",
    "bcrypt": "^6.0.0",
    "zod": "^3.25.64"
  },
  "devDependencies": {
    "@types/jsonwebtoken": "^9.0.9",
    "@types/bcrypt": "^5.0.2"
  }
}

Security Best Practices

  • Passwords are hashed with bcrypt (10 salt rounds)
  • Original passwords never stored in database
  • Password field excluded from query results by default
  • Minimum 8 character requirement enforced
  • Access tokens are short-lived (15 minutes)
  • Refresh tokens stored in database for revocation
  • Separate secrets for access and refresh tokens
  • Tokens invalidated on logout
  • All auth endpoints validate input with Zod
  • Email addresses normalized (lowercase)
  • Rate limiting recommended for production
  • HTTPS required in production

Example Usage

Protecting Routes

Create an authentication middleware to protect routes:
import { Request, Response, NextFunction } from "express";
import { verifyAccessToken } from "../services/auth-services.js";

export const authMiddleware = async (
    req: Request,
    res: Response,
    next: NextFunction
) => {
    try {
        const token = req.headers.authorization?.split(" ")[1];
        
        if (!token) {
            return res.status(401).json({
                success: false,
                message: "No token provided"
            });
        }
        
        const decoded = verifyAccessToken(token);
        req.user = decoded; // Attach user to request
        next();
    } catch (error) {
        return res.status(401).json({
            success: false,
            message: "Invalid token"
        });
    }
};

Using Protected Routes

import { Router } from "express";
import { authMiddleware } from "../middlewares/auth-middleware.js";
import { getUserProfile } from "../controllers/user-controller.js";

const router = Router();

// Protected route
router.get("/profile", authMiddleware, getUserProfile);

export default router;

Quick Start

1

Enable JWT Auth

Select “jwt-auth” when running the CLI
npx create-node-blueprint my-app
2

Configure secrets

Update .env with secure JWT secrets
JWT_ACCESS_SECRET=$(openssl rand -base64 32)
JWT_REFRESH_SECRET=$(openssl rand -base64 32)
3

Test authentication

# Signup
curl -X POST http://localhost:8000/api/v1/auth/signup \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"password123"}'

# Login
curl -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"[email protected]","password":"password123"}'

Build docs developers (and LLMs) love