DEV Community

1xApi
1xApi

Posted on • Originally published at 1xapi.com

How to Secure Your API: Authentication & Authorization Guide

Every API you expose is a door. Leave it unlocked, and it's only a matter of time before someone walks in uninvited. In 2024 alone, API-related breaches accounted for a significant share of data leaks — from exposed customer records to hijacked payment flows. The stakes are high: regulatory fines, reputational damage, and lost user trust.

Authentication answers "Who are you?" while authorization answers "What are you allowed to do?" Getting both right is non-negotiable. Let's break down the most common approaches and how to implement them properly.

API Keys: Simple but Limited

An API key is a unique string passed with each request, typically in a header or query parameter. The server checks the key against a database of valid keys and grants access accordingly.

How it works:

  1. Developer registers and receives a key (e.g., x-api-key: sk_live_abc123)
  2. Every request includes this key
  3. Server validates the key and maps it to permissions

Pros:

  • Dead simple to implement
  • Easy to rotate and revoke
  • Great for server-to-server communication

Cons:

  • No built-in user identity — a key represents an application, not a user
  • If leaked, anyone can use it until revoked
  • No expiration by default

When to use: Internal services, rate limiting by client, simple third-party integrations where user context isn't needed.

OAuth 2.0: The Industry Standard for Delegated Access

OAuth 2.0 solves a fundamental problem: letting users grant limited access to their resources without sharing credentials. Instead of handing over a password, users authorize an application through a consent flow and receive scoped tokens.

Authorization Code Flow (for web/mobile apps)

This is the most secure flow for user-facing applications:

  1. App redirects user to the authorization server
  2. User logs in and consents
  3. Authorization server redirects back with a code
  4. App exchanges the code for an access token (server-side)
  5. App uses the access token to call the API

The code-to-token exchange happens server-side, so the access token never touches the browser.

Client Credentials Flow (for machine-to-machine)

No user involved — one service authenticates directly with another:

  1. Service sends its client_id and client_secret to the token endpoint
  2. Receives an access token
  3. Uses it to call the API

When to use OAuth 2.0: Any scenario involving user data, third-party integrations, or when you need scoped, time-limited access.

JWT: Stateless, Portable Tokens

JSON Web Tokens (JWTs) are the most common token format used with OAuth 2.0 and custom auth systems. A JWT has three Base64-encoded parts separated by dots:

header.payload.signature
Enter fullscreen mode Exit fullscreen mode
  • Header: Algorithm and token type ({"alg": "HS256", "typ": "JWT"})
  • Payload: Claims — user ID, roles, expiration ({"sub": "user_123", "role": "admin", "exp": 1735689600})
  • Signature: HMAC or RSA signature ensuring the token hasn't been tampered with

The server can validate the token without hitting a database — that's the beauty of JWTs. But you must verify the signature and check expiration on every request.

Code Examples in Node.js

JWT Validation Middleware

const jwt = require('jsonwebtoken');

function authenticateJWT(req, res, next) {
  const authHeader = req.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing or invalid token' });
  }

  const token = authHeader.split(' ')[1];

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET, {
      algorithms: ['HS256'],
      issuer: 'your-api.com',
    });
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(403).json({ error: 'Invalid token' });
  }
}

// Usage
app.get('/api/profile', authenticateJWT, (req, res) => {
  res.json({ userId: req.user.sub, role: req.user.role });
});
Enter fullscreen mode Exit fullscreen mode

API Key Authentication Middleware

const crypto = require('crypto');

// Store hashed keys, never plaintext
const validKeys = new Map([
  [hashKey('sk_live_abc123'), { name: 'Acme Corp', rateLimit: 1000 }],
]);

function hashKey(key) {
  return crypto.createHash('sha256').update(key).digest('hex');
}

function authenticateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];

  if (!apiKey) {
    return res.status(401).json({ error: 'API key required' });
  }

  const hashedKey = hashKey(apiKey);
  const client = validKeys.get(hashedKey);

  if (!client) {
    return res.status(403).json({ error: 'Invalid API key' });
  }

  req.client = client;
  next();
}
Enter fullscreen mode Exit fullscreen mode

Security Best Practices

Following the right authentication pattern is only half the battle. These practices apply regardless of which method you choose:

  • Always use HTTPS. Tokens and keys sent over plain HTTP can be intercepted trivially. There's no excuse in 2026 — TLS certificates are free.
  • Validate all input. Never trust client data. Sanitize parameters, validate types, and reject unexpected fields. Use a schema validator like Zod or Joi.
  • Implement rate limiting. Protect against brute-force attacks and abuse. Libraries like express-rate-limit make this straightforward.
  • Configure CORS properly. Don't use Access-Control-Allow-Origin: * in production. Whitelist specific origins.
  • Apply least privilege. Tokens and keys should have the minimum permissions needed. An analytics dashboard doesn't need write access to user data.
  • Set token expiration. Short-lived access tokens (15–60 minutes) with refresh tokens strike the right balance between security and UX.
  • Store secrets securely. Use environment variables or a secrets manager. Never commit keys to version control.
  • Log and monitor. Track failed authentication attempts, unusual access patterns, and token usage. Alerting on anomalies catches breaches early.

Common Mistakes to Avoid

  1. Storing API keys in plaintext. Always hash keys in your database. If your DB is compromised, plaintext keys give attackers immediate access.
  2. Not validating JWT signatures. Accepting a JWT without verifying the signature is like checking someone's ID without looking at the photo.
  3. Using alg: none. Some JWT libraries accept unsigned tokens if the algorithm is set to none. Always specify allowed algorithms explicitly.
  4. Putting sensitive data in JWT payloads. JWTs are encoded, not encrypted. Anyone can decode the payload. Don't include passwords, SSNs, or other secrets.
  5. Ignoring token revocation. JWTs are stateless, which means a compromised token is valid until it expires. Implement a token blocklist or use short expiration times.
  6. Hardcoding secrets in source code. It ends up in Git history, CI logs, and error reports. Use environment variables.
  7. Over-scoping permissions. Granting admin access "just to get it working" and forgetting to restrict it later is how privilege escalation happens.

Wrapping Up

API security isn't a feature you bolt on at the end — it's a foundation you build from day one. Start with the right authentication method for your use case, layer in authorization controls, and follow battle-tested practices. Your future self (and your users) will thank you.


Published by 1xAPI

Top comments (0)