
Are you writing a API, finished testing? Need to deploy it to production? Now you need to worry about preventing unauthorised access to the API. Fear not, I will guide you with a number of ways to tighten your API.
API security depends on the type of application you are making. Making a public api? You don't need security mechanisms. Making a public api where you set values? You need a admin only api key access. Making a subscription based API service, now you need to make a user specific api key.
How will you authenticate to the API? Some good ways to authenticate include using post requests or headers.
We will now get to basics and explanation of terms used in this article
For most APIs, there are 2 types of routes
You need to protect these routes behind a logic wall. These routes typically consist of database requests and edit requests which for most APIs you won't want a user to access.
You don't need to protect these routes, typically routes like /ping where authentication is not really required, but still safeguards like ratelimiting, user access control are required.
Above given knowledge is only for most APIs, you should use whatever you feel is the best for your use-case
The protected routes are typically protected by a API key, which you can define in your .env files. This key shouldn't be shared to anyone.
You must be asking... Enough of knowledge, show me the code. Don't worry. I will be using ExpressJS as a base here.
I will explain every part with detail so the basics aren't just over yet (hehe)
Let's create a default express app that returns Hello World at /ping
// app.js
const express = require('express'); // Import Express
const app = express(); // App instance
const PORT = process.env.PORT || 3000; // Port variable
// Basic health check route
app.get('/ping', (req, res) => {
res.send('Hello World'); // Send response
}); // GET route handler
// Start server
app.listen(PORT, () => {
console.log(`API listening on http://localhost:${PORT}`); // Startup log
}); // Begin listening on the port
Here we are using ES5 syntax, you need to change it to ES6 if you are using ES6
Here now the route is unprotected. We will keep this route as unprotected (for now)
Now let's add a protected route at /protected
Replace the /ping get route with following:
// app.js
// Parse JSON bodies for POST
app.use(express.json()); // Built-in JSON parser
// Simple health check
app.get('/ping', (req, res) => {
res.send('Hello World'); // Plain text response
});
// API key auth middleware
function requireApiKey(req, res, next) {
const provided =
req.header('x-api-key') ||
req.query.api_key ||
(req.body && req.body.api_key); // Accept header, query, or body
const expected = process.env.API_KEY; // Read env var
if (!expected) {
return res.status(500).json({ error: 'Server misconfigured: API_KEY missing' }); // Guardrail
}
if (provided && provided === expected) return next(); // Match -> continue
return res.status(401).json({ error: 'Unauthorized: invalid or missing API key' }); // 401 on fail
}
// Protected GET
app.get('/protected', requireApiKey, (req, res) => {
res.json({ ok: true, method: 'GET', message: 'Access granted' }); // JSON reply
});
// Protected POST
app.post('/protected', requireApiKey, (req, res) => {
res.json({ ok: true, method: 'POST', data: req.body || null }); // Echo body
});
This is just a example. You should always use headers for sending request. They aren't logged in requests (mostly)
Congrats 🎉, now you have a protected route.
How it works?
API_KEY = "asecureapikeygoeshere"
The function will check if the provided API key, which here can be given in 3 ways, a header X-API-KEY, a query like /protected?api_key=asecureapikeygoeshere, or a post request with a body "api_key": "yoursecurekeygoeshere"
If the key matches our .env variable, the request is allowed, if not a 401 (Unauthorised) error code is sent
And that's it, now you have a protected route. Let's get to some common misconceptions
-You need a rate limit on the API, many Devs do not add a rate limit to the API, allowing brute force attacks, which even when you have a DDoS Protection is still a bad idea.
Here's a code example with ratelimit:
// at top of app.js, after: const app = express();
const rateLimit = require('express-rate-limit'); // npm i express-rate-limit
// define a limiter for protected endpoints (e.g., 60 reqs/min per IP)
const protectedLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute window
limit: 60, // allow 60 requests per IP per window
standardHeaders: true, // send RateLimit-* headers
legacyHeaders: false, // disable X-RateLimit-*
message: { error: 'Rate limit exceeded. Try again later.' }, // 429 body
statusCode: 429, // Too Many Requests
});
And update the below routes with
// replace your protected routes with these to add the limiter
app.get('/protected', protectedLimiter, requireApiKey, (req, res) => {
res.json({ ok: true, method: 'GET', message: 'Access granted' });
});
app.post('/protected', protectedLimiter, requireApiKey, (req, res) => {
res.json({ ok: true, method: 'POST', data: req.body || null });
});
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
openssl rand -base64 32
Or a python program like:
import secrets
key = secrets.token_urlsafe(32) # ~256-bit entropy
print(key)
to get a secure api key
Moving on to more ways for security..
We are not going any more in this guide (for now), but if you need more deep guide, please email me at email
I will tell some good ol ways to secure your API:
Hash the key while requests and compare against the hashed key to prevent request logging
Use latest version of all your dependencies to ensure remaining up to date
Try to see the requests coming, if you see any malicious requests, try to take action rather than seeing them try
For per-user API access, you will need to check for the API key (hash the key please) in a database of users, then check if that API Key has a valid usage. Charge the user proper amount for his usage.
This guide tells you to use get, post and header for sending API key but always prefer header.
The comparisons used aren't timing safe in this code. If you want to tighten even that, you should look into crypto.timingSafeEqual() method
Hope you have a good day and happy coding ;) 💕

TechSteer is a tech blog for your tech needs
We will try our best to provide you with quality content.
A Quick rundown on the blog and its expected quality is given below:
TechSteer is a quality blog brought by UnknownVPS. Most of our products are free for public use and most of them are adfree. We intend to continue providing these services but the blog may not continue to be adfree.
Unlike other blogs which are mostly using AI Generated content for views, we will try our best to give you the best.
The first blog post is short but there is always more to come.