Documentation Index Fetch the complete documentation index at: https://mintlify.com/Celaya55/app-cr/llms.txt
Use this file to discover all available pages before exploring further.
This is an implementation guide, not current functionality. JWT authentication is not yet active in the codebase. This page provides the code needed to implement secure authentication.
Overview
This guide shows how to add JSON Web Tokens (JWT) for stateless authentication to App CR. The dependencies are installed but the authentication logic needs to be implemented.
Dependencies
The JWT implementation relies on these packages (from package.json):
{
"dependencies" : {
"jsonwebtoken" : "^9.0.3" ,
"bcryptjs" : "^3.0.3" ,
"express" : "^5.2.1" ,
"@prisma/client" : "^6.19.2"
},
"devDependencies" : {
"dotenv" : "^17.3.1"
}
}
JWT Configuration
Environment Setup
The JWT secret is loaded from environment variables in backend/index.js:11:
require ( 'dotenv' ). config ();
const jwt = require ( 'jsonwebtoken' );
const TOKEN = process . env . JWT_SECRET ; // Secret key for signing tokens
Security Critical: The JWT_SECRET must be:
At least 256 bits (32 characters) long
Randomly generated
Stored securely in .env file
Never committed to version control
Generate a Secure Secret
# Generate a cryptographically secure secret
node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
Then add to your .env file:
JWT_SECRET = your_generated_secret_here
PORT = 3000
DATABASE_URL = postgresql://user:password@localhost:5432/appcrdb
User Registration with Password Hashing
The current implementation in backend/index.js:24-36 needs to be updated to hash passwords:
Current Implementation (Insecure)
app . post ( '/usuarios' , async ( req , res ) => {
try {
const nuevoUsuario = await prisma . user . create ({
data: {
email: req . body . email ,
password: req . body . password // ❌ Plain text password
}
});
res . status ( 201 ). json ( nuevoUsuario );
} catch ( error ) {
console . log ( "DETALLE DEL ERROR:" , error );
res . status ( 500 ). json ({ error: "Error al insertar en Postgres" });
}
});
Recommended Secure Implementation
const bcrypt = require ( 'bcryptjs' );
app . post ( '/usuarios' , async ( req , res ) => {
try {
const { email , password } = req . body ;
// Validate input
if ( ! email || ! password ) {
return res . status ( 400 ). json ({ error: "Email and password are required" });
}
// Hash password with salt rounds of 10
const hashedPassword = await bcrypt . hash ( password , 10 );
const nuevoUsuario = await prisma . user . create ({
data: {
email ,
password: hashedPassword // ✅ Hashed password
}
});
// Don't send password back in response
const { password : _ , ... userWithoutPassword } = nuevoUsuario ;
res . status ( 201 ). json ( userWithoutPassword );
} catch ( error ) {
console . log ( "DETALLE DEL ERROR:" , error );
// Handle unique constraint violation
if ( error . code === 'P2002' ) {
return res . status ( 409 ). json ({ error: "Email already exists" });
}
res . status ( 500 ). json ({ error: "Error al insertar en Postgres" });
}
});
What is bcrypt and why use it?
bcrypt is a password hashing function designed to be slow and computationally expensive, making it resistant to brute-force attacks.
Salt rounds (10) : Each increment doubles the time needed to hash. 10 rounds is a good balance between security and performance.
Automatic salting : bcrypt generates a unique salt for each password
One-way function : Passwords cannot be reversed from the hash
Example hash output: $2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
$2a$ - bcrypt algorithm identifier
10$ - cost factor (number of rounds)
Next 22 chars - the salt
Remaining chars - the actual password hash
User Login with JWT Token Generation
Implement a login endpoint that verifies credentials and returns a JWT token:
app . post ( '/login' , async ( req , res ) => {
try {
const { email , password } = req . body ;
// Validate input
if ( ! email || ! password ) {
return res . status ( 400 ). json ({ error: "Email and password are required" });
}
// Find user by email
const user = await prisma . user . findUnique ({
where: { email }
});
if ( ! user ) {
return res . status ( 401 ). json ({ error: "Invalid credentials" });
}
// Verify password
const isValidPassword = await bcrypt . compare ( password , user . password );
if ( ! isValidPassword ) {
return res . status ( 401 ). json ({ error: "Invalid credentials" });
}
// Generate JWT token
const token = jwt . sign (
{
userId: user . id ,
email: user . email
},
process . env . JWT_SECRET ,
{
expiresIn: '24h' // Token expires in 24 hours
}
);
res . json ({
token ,
user: {
id: user . id ,
email: user . email
}
});
} catch ( error ) {
console . log ( "DETALLE DEL ERROR:" , error );
res . status ( 500 ). json ({ error: "Error during login" });
}
});
Security Best Practice: Always return the same generic error message (“Invalid credentials”) whether the email doesn’t exist or the password is wrong. This prevents attackers from enumerating valid email addresses.
JWT Middleware for Protected Routes
Create middleware to verify JWT tokens on protected endpoints:
// Middleware to verify JWT tokens
const authenticateToken = ( req , res , next ) => {
// Get token from Authorization header
const authHeader = req . headers [ 'authorization' ];
const token = authHeader && authHeader . split ( ' ' )[ 1 ]; // Format: "Bearer TOKEN"
if ( ! token ) {
return res . status ( 401 ). json ({ error: "No token provided" });
}
try {
// Verify token
const decoded = jwt . verify ( token , process . env . JWT_SECRET );
// Attach user info to request object
req . user = decoded ;
next ();
} catch ( error ) {
if ( error . name === 'TokenExpiredError' ) {
return res . status ( 401 ). json ({ error: "Token expired" });
}
if ( error . name === 'JsonWebTokenError' ) {
return res . status ( 403 ). json ({ error: "Invalid token" });
}
return res . status ( 403 ). json ({ error: "Token verification failed" });
}
};
Using the Middleware
Protect routes by adding the middleware:
// Public route - no authentication needed
app . post ( '/usuarios' , async ( req , res ) => { /* ... */ });
app . post ( '/login' , async ( req , res ) => { /* ... */ });
// Protected route - requires valid JWT token
app . get ( '/me' , authenticateToken , async ( req , res ) => {
try {
const user = await prisma . user . findUnique ({
where: { id: req . user . userId },
select: {
id: true ,
email: true ,
tasks: true
}
});
res . json ( user );
} catch ( error ) {
res . status ( 500 ). json ({ error: "Error fetching user" });
}
});
// Get user's tasks - protected
app . get ( '/tasks' , authenticateToken , async ( req , res ) => {
try {
const tasks = await prisma . task . findMany ({
where: { userId: req . user . userId }
});
res . json ( tasks );
} catch ( error ) {
res . status ( 500 ). json ({ error: "Error fetching tasks" });
}
});
Complete Authentication Flow Examples
Register a New User
curl -X POST http://localhost:3000/usuarios \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"password": "SecurePass123!"
}'
Response: {
"id" : 1 ,
"email" : "alice@example.com"
}
Login to Get JWT Token
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{
"email": "alice@example.com",
"password": "SecurePass123!"
}'
Response: {
"token" : "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ,
"user" : {
"id" : 1 ,
"email" : "alice@example.com"
}
}
Access Protected Endpoint
curl -X GET http://localhost:3000/me \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response: {
"id" : 1 ,
"email" : "alice@example.com" ,
"tasks" : []
}
JWT Token Structure
A JWT token consists of three parts separated by dots:
You can decode (but not verify) a JWT token at jwt.io or programmatically: const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ;
const decoded = jwt . decode ( token );
console . log ( decoded );
// {
// userId: 1,
// email: "alice@example.com",
// iat: 1709755200, // Issued at
// exp: 1709841600 // Expires at
// }
jwt.decode() only decodes the token without verifying the signature. Always use jwt.verify() in production to ensure the token is valid and hasn’t been tampered with.
Token Expiration and Refresh
Setting Token Expiration
Tokens should have a reasonable expiration time:
const token = jwt . sign (
payload ,
process . env . JWT_SECRET ,
{
expiresIn: '24h' // Options: '15m', '1h', '7d', '30d'
}
);
Recommended Expiration Times
Use Case Expiration Reason Web App 1-24 hours Balance between security and UX Mobile App 7-30 days Less frequent logins acceptable API Keys No expiration Use refresh tokens instead High Security 15-60 minutes Minimal exposure window
Error Handling
Common JWT errors and how to handle them:
try {
const decoded = jwt . verify ( token , process . env . JWT_SECRET );
} catch ( error ) {
switch ( error . name ) {
case 'TokenExpiredError' :
// Token has expired - user needs to login again
return res . status ( 401 ). json ({
error: "Token expired" ,
code: "TOKEN_EXPIRED"
});
case 'JsonWebTokenError' :
// Token is malformed or invalid
return res . status ( 403 ). json ({
error: "Invalid token" ,
code: "INVALID_TOKEN"
});
case 'NotBeforeError' :
// Token not active yet (nbf claim)
return res . status ( 403 ). json ({
error: "Token not active yet" ,
code: "TOKEN_NOT_ACTIVE"
});
default :
return res . status ( 403 ). json ({
error: "Token verification failed" ,
code: "VERIFICATION_FAILED"
});
}
}
Security Best Practices
Use HTTPS Always transmit JWT tokens over HTTPS in production to prevent interception.
Short Expiration Use short-lived tokens (1-24 hours) to minimize the impact of token theft.
Secure Storage Store tokens securely on the client (httpOnly cookies or secure storage, not localStorage).
Strong Secret Use a cryptographically secure JWT_SECRET (at least 256 bits).
Common Security Mistakes to Avoid:
Storing sensitive data in the JWT payload (it’s base64 encoded, not encrypted)
Using weak or default JWT_SECRET values
Not validating tokens on every protected request
Storing tokens in localStorage (vulnerable to XSS attacks)
Not implementing token expiration
Testing JWT Implementation
Test your authentication flow:
// test-auth.js
const axios = require ( 'axios' );
const BASE_URL = 'http://localhost:3000' ;
async function testAuth () {
try {
// 1. Register user
console . log ( '1. Registering user...' );
const registerRes = await axios . post ( ` ${ BASE_URL } /usuarios` , {
email: 'test@example.com' ,
password: 'TestPass123!'
});
console . log ( '✓ User registered:' , registerRes . data );
// 2. Login
console . log ( ' \n 2. Logging in...' );
const loginRes = await axios . post ( ` ${ BASE_URL } /login` , {
email: 'test@example.com' ,
password: 'TestPass123!'
});
console . log ( '✓ Login successful' );
console . log ( 'Token:' , loginRes . data . token . substring ( 0 , 50 ) + '...' );
const token = loginRes . data . token ;
// 3. Access protected route
console . log ( ' \n 3. Accessing protected route...' );
const meRes = await axios . get ( ` ${ BASE_URL } /me` , {
headers: { Authorization: `Bearer ${ token } ` }
});
console . log ( '✓ Protected route accessed:' , meRes . data );
// 4. Try with invalid token
console . log ( ' \n 4. Testing with invalid token...' );
try {
await axios . get ( ` ${ BASE_URL } /me` , {
headers: { Authorization: 'Bearer invalid_token' }
});
} catch ( error ) {
console . log ( '✓ Invalid token rejected:' , error . response . data );
}
} catch ( error ) {
console . error ( '✗ Error:' , error . response ?. data || error . message );
}
}
testAuth ();
Next Steps
Authentication Overview Review the complete authentication architecture
API Reference Explore all authentication endpoints
Database Schema Learn about the database structure
Quickstart Get started with App CR in minutes