Web Development

API Security Best Practices: Auth, Rate Limits and Validation

The checks we apply when building or reviewing an API: the right authentication for each caller, per-object authorization, scopes, rate limits, input validation and logging, mapped to the OWASP API Security Top 10.

API Security Best Practices: Auth, Rate Limits and Validation

The API incidents we are asked to clean up rarely involve broken encryption. They involve an endpoint that returns another customer's invoice when the ID in the URL is changed, a key with full access pasted into a mobile app, or a login route that accepts unlimited password guesses. API security best practices mostly come down to four questions asked on every request: who is calling, what may they touch, how much may they do, and can their input be trusted.

The sections below follow that order. The code examples use Laravel, and the rules hold on any stack.

Base your API security best practices on the OWASP list

OWASP publishes a list of the ten most serious API risks, separate from its better-known list for web applications. The current edition is the OWASP API Security Top 10 (2023). Several entries are about authorization, which no firewall or scanner can fix for you.

RiskIn plain terms
API1 Broken Object Level AuthorizationA caller changes an ID and reaches a record that belongs to someone else
API2 Broken AuthenticationWeak logins, guessable or long-lived tokens, keys that never expire
API3 Broken Object Property Level AuthorizationYou can read or set fields you should not, such as a role or a price
API4 Unrestricted Resource ConsumptionNo limits on request rate, page size, upload size or expensive operations
API5 Broken Function Level AuthorizationOrdinary users can reach admin or internal endpoints
API6 Unrestricted Access to Sensitive Business FlowsBuying, booking or sign-up automated at a harmful scale
API7 Server Side Request ForgeryThe API fetches a caller-supplied URL and can be pointed at internal systems
API8 Security MisconfigurationDebug mode, open CORS, default credentials, verbose errors
API9 Improper Inventory ManagementOld versions, staging hosts and forgotten endpoints that are still live
API10 Unsafe Consumption of APIsTrusting third-party API data more than you would trust user input

Match the authentication method to the caller

API keys for server-to-server calls

An API key is a long random secret that identifies an application, not a person. Issue a separate key per integration, show it once, store only a hash of it (Laravel Sanctum does this for its tokens), give it an expiry date and make it easy to revoke. Send it in a header and never in the URL, because URLs end up in access logs and browser history.

A key must never ship inside a mobile app or a JavaScript bundle. Anything delivered to a user's device can be extracted from it.

OAuth 2.0 when a user grants access

When an application acts on behalf of a user, use OAuth 2.0, so the user's password never reaches the third party. For web, mobile and single-page apps that means the authorization code flow with PKCE. For machine-to-machine access, the client credentials grant. The IETF's best current practice for OAuth 2.0 security (RFC 9700) says the password grant must not be used, advises against the implicit grant, and requires exact matching of redirect URIs. If an older integration still relies on either grant, plan its replacement, and use a maintained library or identity provider instead of writing your own OAuth server.

Tokens that expire and are checked properly

Keep access tokens short-lived and pair them with refresh tokens that rotate on use. If you issue JWTs, verify the signature with an algorithm fixed in your configuration (never the one named in the token's own header), and check the iss, aud and exp claims on every request. A JWT cannot be withdrawn before it expires unless you add a denylist, so opaque tokens stored in your database are often simpler for a first-party API.

Authorize every object, not only every route

The first risk on the OWASP list is forgetting to check whether this caller may see this record. A route behind a login check is not protected if the handler then loads whatever ID was sent.

// Any logged-in user can read any invoice
$invoice = Invoice::findOrFail($id);

// The lookup is limited to the caller's own invoices
$invoice = $request->user()->invoices()->findOrFail($id);

Build the ownership rule into the query, or into a policy that every controller method calls, so it is hard to skip. Then apply the same thinking one level down and one level up:

  • Fields. Accept only an explicit list of writable fields, so a request body containing "role": "admin" is ignored. Return responses through a resource class that names each field, so a new database column is never exposed by accident.
  • Functions. Put admin and internal endpoints behind their own role check, and test them with an ordinary user's token. Hiding a route from the menu does not protect it.

Scopes keep a leaked token small

A token that can do everything turns one leak into a full breach. Scopes limit a token to named actions: orders:read, orders:write, refunds:create. The reporting tool gets read scopes only, and the warehouse integration never sees payment data.

Laravel Sanctum calls them token abilities, set when the token is created and checked with $request->user()->tokenCan('orders:write'). Start each integration with the smallest set and add scopes on request.

Rate limits and resource limits

Rate limiting protects against brute force, scraping and accidents such as a partner's retry loop with no delay. Limit per key or per user as well as per IP address, because many clients share one address and attackers rotate through many. In Laravel a limiter is a few lines in the boot method of AppServiceProvider:

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});

Attach it to routes with the throttle:api middleware. A caller over the limit receives 429 Too Many Requests, and a Retry-After header tells well-behaved clients when to come back.

Then cover what a request count does not:

  • much stricter limits on login, password reset, one-time codes and sign-up;
  • a maximum page size, request body size and upload size, enforced on the server;
  • timeouts and quotas on expensive work such as exports and report generation;
  • per-account limits on flows that cost money or stock, such as checkout, booking and SMS sending.

Validate input as if a stranger sent it

Every field arriving at an API should pass a schema before any business logic runs: type, length, range, format and, for fixed choices, an allowed list. Reject unknown fields. In Laravel that is a form request class, and on other stacks a JSON Schema or OpenAPI validator.

Validation does not replace safe handling further in. Use parameterized queries or the ORM for every database call, including sort and filter parameters, where SQL injection often survives. For uploads, check the real file type, store files outside the web root and generate your own file names. If an endpoint accepts a URL to fetch (a webhook target, an avatar, an import source), check it against an allowlist and block private network ranges, or it becomes a way into your internal systems.

Treat data coming back from third-party APIs the same way. A partner's response is input too.

Log enough to investigate, never enough to leak

When something goes wrong you have to answer "which key did what, and when". Log the caller's user or key ID, the route, the status code and a request ID that you also return in a response header. Log every failed login, every 403 and every 429, and alert when they spike. A burst of 403 responses against sequential IDs is what an attack on object-level authorization looks like in a log.

Keep secrets out: no tokens, passwords, card numbers or full request bodies containing personal data. Return generic error messages to the caller and keep the stack trace on the server, with APP_DEBUG=false in production.

Finally, keep an inventory of every API version, host and environment, and switch off what is no longer used. The forgotten /v1 that predates your current checks is a common way in. Serve everything over HTTPS, and treat CORS as a browser rule, not as access control.

Review it yourself or ask for a second pair of eyes

The team that wrote an API can run a useful first review with the OWASP list and two test accounts: sign in as user A, then try to read, change and delete user B's records on every endpoint, internal ones included. Add each case as an automated test so it stays fixed. Our PHP application security checklist covers the application around the API.

Ask for an outside review before a public launch, before opening the API to partners, or when it handles payments, health or student records. We build and review APIs against OWASP practices when a client asks for it, as part of our API development and integration work, on Laravel API projects and in mobile app API integration. A code review does not replace a formal penetration test or a compliance audit, and where regulation applies, confirm the requirements with your own auditor.

Frequently asked questions

Are API keys or OAuth more secure?

They solve different problems. An API key identifies an application and suits server-to-server calls. OAuth lets a user grant limited access without sharing a password. Wherever a third party acts on behalf of your users, use OAuth.

Is HTTPS enough to secure an API?

No. HTTPS protects data in transit and proves the client is talking to your server. It does nothing about a caller who is properly connected and asks for someone else's data, the risk at the top of the OWASP list. You still need authentication, per-object authorization, limits and validation.

How often should API keys be rotated?

Rotate on a schedule you can actually keep, and immediately when a person with access leaves, a key appears in a repository or a log, or a partner reports a breach. Support two active keys per integration, so rotation does not cause downtime.

Add WorldWin Coder to your preferred sources

Add worldwincoder.com as a preferred source on Google, or open this article in your AI assistant to use it as a source.

Swati Priyadrshni
· Founder & CEO at WorldWin Coder

I'm Swati Priyadarshani, Founder & CEO of LMS Advisor and Co-Founder of WorldWin Coder Pvt. Ltd. (est. 2019). Over the last 10+ years, I've helped enterprises build learning platforms that actually get used - not just deployed.

Keep reading

Have something to build, fix or move?

Tell us where you are. A developer reads every inquiry, and you get next steps and a free quote within one business day.