DEV Community

Pranav Bakare
Pranav Bakare

Posted on

Implement Authentication with Node.js using JWTs

Here's a simple example of how to implement authentication with Node.js using JWTs (JSON Web Tokens). This example will cover:

  1. Registering a user.
  2. Logging in a user to get a JWT token.
  3. Protecting routes with the JWT token.

Step 1: Setup and Install Required Packages

Create a new Node.js project and install the following dependencies:

npm init -y
npm install express bcryptjs jsonwebtoken mongoose
Enter fullscreen mode Exit fullscreen mode
  • express: To set up the server and routes.
  • bcryptjs: To hash passwords.
  • jsonwebtoken: To sign and verify JWT tokens.
  • mongoose: To connect to MongoDB for storing user data.

Step 2: Create a Simple User Model with Mongoose

We'll use MongoDB to store user details. Set up the MongoDB connection and create a user model.

// models/User.js

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true,
    unique: true
  },
  password: {
    type: String,
    required: true
  }
});

// Hash the password before saving
UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

const User = mongoose.model('User', UserSchema);
module.exports = User;
Enter fullscreen mode Exit fullscreen mode

Step 3: Set Up the Server and Authentication Routes

Now, set up the Express server and create routes for registration, login, and a protected route.

// server.js

const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const User = require('./models/User');
const app = express();

app.use(express.json());

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/jwt-auth', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
  useCreateIndex: true
}).then(() => console.log('MongoDB connected'));

// Secret for JWT signing
const JWT_SECRET = 'supersecretjwtkey';

// Register User
app.post('/register', async (req, res) => {
  const { name, email, password } = req.body;

  try {
    const user = new User({ name, email, password });
    await user.save();
    res.status(201).json({ message: 'User registered successfully!' });
  } catch (error) {
    res.status(400).json({ message: 'Error registering user' });
  }
});

// Login User and generate JWT
app.post('/login', async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await User.findOne({ email });
    if (!user) return res.status(400).json({ message: 'User not found' });

    const isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' });

    // Create and sign the JWT token
    const token = jwt.sign({ userId: user._id }, JWT_SECRET, { expiresIn: '1h' });

    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: 'Server error' });
  }
});

// Middleware to protect routes
const authenticateJWT = (req, res, next) => {
  const token = req.header('Authorization')?.split(' ')[1];

  if (!token) return res.status(401).json({ message: 'No token, authorization denied' });

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    req.user = decoded.userId;
    next();
  } catch (err) {
    res.status(403).json({ message: 'Invalid token' });
  }
};

// Protected route
app.get('/protected', authenticateJWT, (req, res) => {
  res.json({ message: 'You have access to the protected route!', userId: req.user });
});

// Start the server
app.listen(5000, () => console.log('Server running on port 5000'));
Enter fullscreen mode Exit fullscreen mode

Step 4: Test the Application

You can now test the JWT authentication workflow using tools like Postman.

1. Register a User

  • URL: POST http://localhost:5000/register
  • Body (JSON):
  {
    "name": "John Doe",
    "email": "john@example.com",
    "password": "password123"
  }
Enter fullscreen mode Exit fullscreen mode

This will register the user in MongoDB with the hashed password.

2. Login to Get the JWT Token

  • URL: POST http://localhost:5000/login
  • Body (JSON):
  {
    "email": "john@example.com",
    "password": "password123"
  }
Enter fullscreen mode Exit fullscreen mode

The server will return a JWT token:

{
  "token": "your.jwt.token"
}
Enter fullscreen mode Exit fullscreen mode

3. Access the Protected Route

  • URL: GET http://localhost:5000/protected
  • Headers:
  Authorization: Bearer your.jwt.token
Enter fullscreen mode Exit fullscreen mode

If the token is valid, you will receive:

{
  "message": "You have access to the protected route!",
  "userId": "some-user-id"
}
Enter fullscreen mode Exit fullscreen mode

If the token is missing or invalid, you will get an error message.

Summary

  • User Registration: Users register with their name, email, and password, which is hashed and stored in the database.
  • User Login: The user logs in by sending their email and password. If valid, the server returns a JWT.
  • JWT Token: The JWT is stored on the client-side (usually in localStorage or cookies) and is sent with each request to protected routes.
  • Protected Routes: The server verifies the JWT token on each request to ensure the user is authenticated.

Let me know if you'd like help with any specific part of the JWT authentication process!

Top comments (1)

Collapse
 
alex_mwangi_ed435ae1dde3b profile image
Mwangi Traders

const express = require('express');
const { Pool } = require('pg');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

const app = express();
app.use(express.json());

const JWT_SECRET = process.env.JWT_SECRET || 'super_secret_jwt_key_change_in_production';

const pool = new Pool({
user: process.env.DB_USER || 'postgres',
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'betting_db',
password: process.env.DB_PASSWORD || 'password',
port: process.env.DB_PORT || 5432,
});

// ==========================================
// AUTHENTICATION MIDDLEWARE
// ==========================================
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Expected format: "Bearer "

if (!token) {
return res.status(401).json({ error: 'Access token required.' });
}

jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token.' });
}
req.user = user; // Attach payload { userId, username } to request object
next();
});
}

// ==========================================
// AUTH ROUTES
// ==========================================

/**

  • POST /api/auth/register
  • Body: { username, password } */ app.post('/api/auth/register', async (req, res) => { const { username, password } = req.body;

if (!username || !password || password.length < 6) {
return res.status(400).json({ error: 'Username and password (min 6 chars) required.' });
}

try {
// Hash password with salt round of 10
const hashedPassword = await bcrypt.hash(password, 10);

const newUser = await pool.query(
  `INSERT INTO users (username, password_hash, balance) 
   VALUES ($1, $2, 100.00) 
   RETURNING id, username, balance`,
  [username, hashedPassword]
);

const user = newUser.rows[0];

// Generate JWT Token
const token = jwt.sign({ userId: user.id, username: user.username }, JWT_SECRET, { expiresIn: '24h' });

res.status(201).json({
  message: 'User registered successfully',
  user: { id: user.id, username: user.username, balance: user.balance },
  token,
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
if (error.code === '23505') { // Postgres duplicate key error code
return res.status(409).json({ error: 'Username already taken.' });
}
console.error('Registration error:', error);
res.status(500).json({ error: 'Server error during registration.' });
}
});

/**

  • POST /api/auth/login
  • Body: { username, password } */ app.post('/api/auth/login', async (req, res) => { const { username, password } = req.body;

if (!username || !password) {
return res.status(400).json({ error: 'Username and password required.' });
}

try {
const userRes = await pool.query('SELECT * FROM users WHERE username = $1', [username]);
if (userRes.rows.length === 0) {
return res.status(401).json({ error: 'Invalid credentials.' });
}

const user = userRes.rows[0];

// Verify password hash
const isValidPassword = await bcrypt.compare(password, user.password_hash);
if (!isValidPassword) {
  return res.status(401).json({ error: 'Invalid credentials.' });
}

// Generate JWT Token
const token = jwt.sign({ userId: user.id, username: user.username }, JWT_SECRET, { expiresIn: '24h' });

res.json({
  message: 'Login successful',
  user: { id: user.id, username: user.username, balance: user.balance },
  token,
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Server error during login.' });
}
});

// ==========================================
// PROTECTED BET PLACEMENT ROUTE
// ==========================================

/**

  • POST /api/bets/place
  • Protected by authenticateToken middleware
  • Body: { matchId, selection, odds, stake } */ app.post('/api/bets/place', authenticateToken, async (req, res) => { // Extract authenticated userId securely from the JWT token const userId = req.user.userId; const { matchId, selection, odds, stake } = req.body;

if (!matchId || !selection || !odds || !stake || stake <= 0) {
return res.status(400).json({ error: 'Invalid payload or stake.' });
}

const client = await pool.connect();

try {
await client.query('BEGIN');

// Fetch user balance
const userRes = await client.query(
  'SELECT balance FROM users WHERE id = $1 FOR UPDATE',
  [userId]
);

if (userRes.rows.length === 0) {
  throw new Error('USER_NOT_FOUND');
}

const currentBalance = parseFloat(userRes.rows[0].balance);

if (currentBalance < stake) {
  throw new Error('INSUFFICIENT_FUNDS');
}

// Deduct stake
const newBalance = currentBalance - stake;
await client.query('UPDATE users SET balance = $1 WHERE id = $2', [newBalance, userId]);

// Record bet
const potentialPayout = parseFloat((stake * odds).toFixed(2));
const betRes = await client.query(
  `INSERT INTO bets (user_id, match_id, selection, odds, stake, potential_payout)
   VALUES ($1, $2, $3, $4, $5, $6)
   RETURNING id, status, created_at`,
  [userId, matchId, selection, odds, stake, potentialPayout]
);

await client.query('COMMIT');

return res.status(201).json({
  message: 'Bet placed successfully',
  bet: {
    id: betRes.rows[0].id,
    matchId,
    selection,
    odds,
    stake,
    potentialPayout,
    status: betRes.rows[0].status,
  },
  remainingBalance: newBalance,
});
Enter fullscreen mode Exit fullscreen mode

} catch (error) {
await client.query('ROLLBACK');

if (error.message === 'INSUFFICIENT_FUNDS') {
  return res.status(400).json({ error: 'Insufficient balance to place this bet.' });
}

console.error('Bet processing error:', error);
return res.status(500).json({ error: 'Failed to process bet.' });
Enter fullscreen mode Exit fullscreen mode

} finally {
client.release();
}
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(Server running on port ${PORT});
});