Protecting APIs on the Backend

While frontend security in Angular is essential for user experience and navigation control, it is not sufficient to fully protect an application. A determined attacker can bypass the frontend, manipulate requests, or directly call backend endpoints. Therefore, securing backend APIs is critical to prevent unauthorized access, data breaches, and manipulation.

This post explores how to protect backend APIs, focusing on JWT validation, role-based access control, and best practices to implement secure backend endpoints.

1. Introduction

Backend API protection ensures that only authorized users can access specific resources or perform certain operations. Frontend controls like route guards and token storage are helpful, but the server must enforce security since it ultimately controls the data.

Common backend security measures include:

  • JWT validation: Verify the authenticity of tokens sent by clients.
  • Role and permission checks: Restrict access based on user roles or specific permissions.
  • Rate limiting and throttling: Prevent abuse or DDoS attacks.
  • Input validation and sanitization: Prevent injection attacks.

2. JWT Validation on the Backend

When the frontend sends a JWT with an API request, the backend must verify it before providing access.

Example using Node.js and Express

  1. Install dependencies
npm install express jsonwebtoken
  1. Create middleware to verify JWT
const jwt = require('jsonwebtoken');

function verifyToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  if (!authHeader) return res.status(401).send('Access denied');

  const token = authHeader.split(' ')[1];
  if (!token) return res.status(401).send('Access denied');

  try {
const secretKey = process.env.JWT_SECRET || 'secret123';
const payload = jwt.verify(token, secretKey);
req.user = payload; // Attach user info to request
next();
} catch (err) {
return res.status(403).send('Invalid token');
} }
  • This middleware extracts the token from the Authorization header.
  • It verifies the token’s signature and expiration.
  • The user information from the token is attached to req.user for downstream use.

3. Role-Based Access Control

Many applications require restricting certain routes to specific roles (e.g., admin, editor, user).

Middleware for Role Checking

function checkRole(role) {
  return (req, res, next) => {
if (!req.user || req.user.role !== role) {
  return res.status(403).send('Forbidden: insufficient permissions');
}
next();
}; }
  • req.user is populated by the JWT verification middleware.
  • The middleware checks if the user has the required role before allowing access.

4. Protecting Routes

Combine JWT verification and role checks to secure API endpoints.

Example: Admin Route

const express = require('express');
const app = express();

app.get('/admin', verifyToken, checkRole('admin'), (req, res) => {
  res.send('Admin data');
});

app.get('/user', verifyToken, (req, res) => {
  res.send(User data for ${req.user.username});
});

app.listen(3000, () => console.log('Server running on port 3000'));
  • /admin route requires a valid JWT and admin role.
  • /user route requires a valid JWT but no specific role.

5. Generating JWT Tokens

On the login endpoint, generate a JWT containing user information and role.

login.js

const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  
  // Example: validate credentials (replace with real DB check)
  if (username === 'admin' && password === 'password') {
const payload = { username, role: 'admin' };
const token = jwt.sign(payload, process.env.JWT_SECRET || 'secret123', { expiresIn: '1h' });
return res.json({ token });
} res.status(401).send('Invalid credentials'); });
  • The token contains user info and role.
  • Expiration ensures tokens are only valid for a limited time.

6. Refresh Tokens for Longer Sessions

To prevent frequent logins, use refresh tokens.

  • Access token: short-lived JWT for API access.
  • Refresh token: long-lived token to request new access tokens.

Example Refresh Endpoint

app.post('/refresh', (req, res) => {
  const { refreshToken } = req.body;
  if (!refreshToken) return res.status(401).send('No refresh token');

  // Verify refresh token logic here
  const newToken = jwt.sign({ username: 'admin', role: 'admin' }, process.env.JWT_SECRET || 'secret123', { expiresIn: '1h' });
  res.json({ token: newToken });
});

This approach ensures session continuity without compromising security.


7. Input Validation and Sanitization

Backend APIs must validate input to prevent injection attacks. Use libraries like express-validator or joi.

Example with express-validator

const { body, validationResult } = require('express-validator');

app.post('/create-user', 
  body('username').isString().notEmpty(),
  body('password').isLength({ min: 6 }),
  (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors: errors.array() });
res.send('User created successfully');
});

8. Rate Limiting and Throttling

To prevent abuse or brute-force attacks, use rate limiting.

Example with express-rate-limit

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // max requests per IP
  message: 'Too many requests, please try again later.'
});

app.use('/api/', apiLimiter);

9. Logging and Monitoring

Monitoring API usage and failed access attempts is crucial. Use logging libraries like winston or integrate with monitoring tools such as Prometheus or ELK stack.

app.use((req, res, next) => {
  console.log(${req.method} ${req.url} - User: ${req.user?.username || 'guest'});
  next();
});

10. Best Practices for Backend API Security

  1. Always Validate JWTs – Never trust tokens from the client.
  2. Use Role-Based Access Control – Limit sensitive routes to authorized roles.
  3. Use HTTPS – Encrypt all communication between client and server.
  4. Short-Lived Tokens – Reduce exposure if tokens are compromised.
  5. Implement Refresh Tokens – Maintain sessions securely.
  6. Input Validation – Prevent SQL injection, XSS, and other attacks.
  7. Rate Limiting – Protect against brute force or DDoS attacks.
  8. Logging and Monitoring – Detect suspicious activity.

11. Combining Frontend and Backend Security

  • Frontend guards (e.g., CanActivate, CanDeactivate) enhance user experience.
  • Backend security ensures actual enforcement of permissions and authentication.
  • Never rely solely on frontend security, as it can be bypassed.

Example Flow:

  1. User logs in → receives JWT.
  2. Frontend stores token and uses route guards.
  3. API requests include token in headers.
  4. Backend verifies token and role before executing logic.
  5. Response is returned only if user is authorized.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *