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
- Install dependencies
npm install express jsonwebtoken
- 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
- Always Validate JWTs – Never trust tokens from the client.
- Use Role-Based Access Control – Limit sensitive routes to authorized roles.
- Use HTTPS – Encrypt all communication between client and server.
- Short-Lived Tokens – Reduce exposure if tokens are compromised.
- Implement Refresh Tokens – Maintain sessions securely.
- Input Validation – Prevent SQL injection, XSS, and other attacks.
- Rate Limiting – Protect against brute force or DDoS attacks.
- 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:
- User logs in → receives JWT.
- Frontend stores token and uses route guards.
- API requests include token in headers.
- Backend verifies token and role before executing logic.
- Response is returned only if user is authorized.
Leave a Reply