Paulund
Subscribe

REST API security, authentication and rate limiting

24 October 2026, 5 min read

On this page
  1. HTTPS and security headers
  2. Bearer tokens
  3. Access and refresh tokens
  4. Least privilege
  5. Input validation
  6. Rate limits
  7. Audit logging

This is the security baseline for a REST API, in the order a request meets it: the connection, the credentials, the input, then the limits and logs around it. All of it belongs in place before the first endpoint ships.

HTTPS and security headers

Serve every request over TLS. Without it, anyone who can see the network traffic can read tokens and credentials. Enforce it at the infrastructure level, redirect plain HTTP to HTTPS, and send Strict-Transport-Security so browsers never fall back to HTTP:

Text
Strict-Transport-Security: max-age=31536000; includeSubDomains

Add these alongside it:

  • X-Content-Type-Options: nosniff stops browsers treating a response as a different MIME type.
  • X-Frame-Options: DENY stops responses being embedded in an iframe, which matters if you also serve browser pages.
  • Content-Security-Policy, where it applies, limits where scripts and styles can load from.

Bearer tokens

Authenticate each request with a Bearer token in the Authorization header:

Text
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

The server validates the token and reads the caller's identity from it. Every common HTTP client supports this header. For keys issued to developers, API key management in Next.js covers generating, storing and revoking them.

Access and refresh tokens

A long-lived token gives an attacker access until it expires. Use a short-lived access token with a longer-lived refresh token instead:

  1. The client signs in once, with a password or an OAuth code exchange, and receives an access token that lasts 15 minutes to an hour, plus a refresh token.
  2. The client sends the access token with every API call.
  3. When the access token expires, the client sends the refresh token to get a new one without signing in again.
  4. To revoke access, the server invalidates the refresh token. The access token stops working when it expires.

Then:

  • Rotate refresh tokens. Each time one is used, issue a new one and invalidate the old one.
  • If you see a sign-in from an unusual location or pattern, invalidate every refresh token for that account.
  • In a browser, keep tokens in httpOnly cookies or in memory. localStorage is readable by any script that gets onto the page through cross-site scripting.

Least privilege

Give each token, key and service account only the permissions it needs. A read-only analytics client should never hold a token that can write or delete. Review and rotate credentials regularly, and revoke ones nobody uses.

The Bullion API MCP server is one example. An agent signs in with OAuth 2.1 and the user approves exactly what it may read, and the server has no write tools at all. Building an MCP server for the Bullion API explains why it is read-only.

Input validation

Treat everything from the client as untrusted. Check each field's type, length and format before application code uses it. Reject a malformed request early with 400 Bad Request, and return 422 with field-level details when well-formed data fails validation. That keeps injection attempts and bad data away from the database and downstream services. The response format is in REST API response shape, pagination and errors.

The same page explains why every response should be a JSON object and never a bare array, which closes off JSON hijacking in older browsers.

Rate limits

Give each API key a request allowance per time window. Once a client goes over it, the server returns 429 Too Many Requests until the window resets. That stops one runaway script from using the capacity every other client shares. A common starting point is 60 requests per minute per key, tuned to your traffic and infrastructure.

Send the limit on every response, so a client can pace itself:

HeaderValue
X-RateLimit-LimitThe most requests allowed in the current window, such as 60.
X-RateLimit-RemainingHow many requests are left in the window, such as 42.
X-RateLimit-ResetThe Unix timestamp when the window resets, such as 1706000000.
Text
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1706000000

On a 429, add Retry-After with the number of seconds to wait:

Text
HTTP/1.1 429 Too Many Requests
Retry-After: 30

There are three common ways to count:

  • Fixed window resets at regular intervals. It is the simplest, but a client can use its whole allowance at the start of each window.
  • Sliding window counts over a rolling period, which smooths out those bursts.
  • Token bucket allows short bursts above the average rate while keeping a long-term cap, which suits traffic with occasional legitimate spikes.

Document the strategy and the limits so clients can plan their request patterns. For Laravel's throttle middleware and custom limiters, see rate limiting in Laravel.

Audit logging

Log every authentication event, permission change and failed request. These logs are what you search when investigating a breach or anything unusual. Do not log full request bodies in production, because they can hold tokens or personal data.

Next in APIs

24 October 2026Running a REST API: caching, versioning, deprecation and release

Tags

  • APIs
  • REST
  • Security
  • Authentication
  • Rate limiting