API Security Essentials: OAuth2, JWT, and Rate Limiting Explained
Introduction
APIs are the backbone of modern software. They connect applications, enable integrations, and power everything from mobile apps to AI workflows. But with this power comes risk: exposed APIs are a prime target for abuse, data leaks, and attacks.
This article explores three essential pillars of API security: OAuth2, JWT (JSON Web Tokens), and Rate Limiting. We'll discuss how each works, why they matter, and how to implement them in Laravel, one of the most popular PHP frameworks for building backends.
OAuth2: Delegated Authorization Made Easy
OAuth2 is a widely adopted protocol for delegated authorization. Instead of sharing credentials directly, clients obtain access tokens that represent permissions to act on behalf of a user.
In Laravel, Laravel Passport makes implementing OAuth2 straightforward.
Setup
Install the Laravel installer via Composer:
composer global require laravel/installer
If you don't have PHP and Composer installed on your local machine, follow the instructions on php.new
Create a new Laravel project without using a starter kit:
laravel new --database sqlite --pest security-essentials
cd security-essentials/
Install Laravel Passport with one command via the install:api
Artisan command. The command will set up Passport,
enable the API routes, run the database migrations, generate the
encryption keys Passport needs in order to generate access tokens, and configure the necessary files.
php artisan install:api --passport
You can also set up Passport step by step:
composer require laravel/passport
php artisan migrate
php artisan passport:install
php artisan passport:keys
In case you want to re-generate the encryption keys, run:
php artisan passport:keys
Update the User model
The User
model must implement the OAuthenticatable
contract. The HasApiTokens
trait contains the methods required
by the interface:
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\Contracts\OAuthenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable implements OAuthenticatable
{
use HasApiTokens, HasFactory, Notifiable;
}
Set up an API authentication guard
Define an api
authentication guard and set the driver option to passport
, so that Laravel uses Passport's TokenGuard
for incoming API requests:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
Create a test user
php artisan tinker --execute="\\App\\Models\\User::create(['name' => 'Test User', 'email' => 'test@example.com', 'password' => bcrypt('password')]);"
Create a Client Credentials Grant
php artisan passport:client --client
Serve the application on the PHP development server
php artisan serve
Request an access token
curl --location --request POST 'http://localhost:8000/oauth/token' \
--header 'Content-Type: application/json' \
--data-raw '{
"grant_type": "client_credentials",
"client_id": "<your-client-id>",
"client_secret": "<your-client-secret>",
"scope": ""
}'
Create an endpoint that requires authentication
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner;
Route::get('/client', function (Request $request) {
$client = auth()->guard('api')?->client();
if (! $client) {
return response()->json(['message' => 'Unauthorized'], 401);
}
return [
'id' => $client->getKey(),
'name' => $client->name,
'grant_types' => $client->grant_types,
];
})->middleware(EnsureClientIsResourceOwner::class);
Fetch a protected resource
curl --location --request GET 'http://localhost:8000/api/client' \
--header 'Authorization: Bearer <your-access-token>'

Client credentials tokens are not associated with a user. Therefore, calling GET /api/user
with such a
token will return 401 Unauthorized
by design.
With Passport, tokens can also include scopes, letting you fine-tune which API routes a client can access. Check out the Token Scopes documentation for more details.
JWT: Lightweight and Stateless Authentication
While OAuth2 is powerful, sometimes you just need a lightweight way to verify users. This is where JWT (JSON Web Tokens) shine. A JWT encodes claims (like user ID and roles) and is signed to prevent tampering. APIs can validate the token without storing session state.
For Laravel, the go-to package is tymon/jwt-auth.
Installation
composer require tymon/jwt-auth
php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider"
php artisan jwt:secret
Example: Issue a JWT at Login
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (! $token = auth()->attempt($credentials)) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return response()->json(['token' => $token]);
}
Example: Protect Routes with JWT Middleware
Route::middleware(['jwt.auth'])->group(function () {
Route::get('/user', function () {
return auth()->user();
});
});
Now your routes require a valid token, keeping unauthorized requests out.
Rate Limiting: Prevent Abuse and Overload
Even with solid authentication, APIs are vulnerable to brute-force attacks, scraping, or accidental overload. Rate limiting helps protect your service by restricting how often a client can call your API.
Laravel provides this out of the box with the ThrottleRequests middleware.
Simple Example
Route::middleware(['throttle:60,1'])->group(function () {
Route::get('/api-data', [ApiController::class, 'index']);
});
This allows 60 requests per minute per IP address.
Per-User Rate Limits
You can also configure smarter rules in RouteServiceProvider:
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(30)->by(optional($request->user())->id ?: $request->ip());
});
This ensures authenticated users each get their own quota, while unauthenticated requests fall back to IP-based limiting.
Best Practices
Securing APIs is more than just plugging in tools. Keep these best practices in mind:
- Use short-lived tokens and refresh tokens for long sessions.
- Never store tokens in localStorage on the frontend — prefer HTTP-only cookies.
- Combine multiple layers: authentication + authorization + rate limiting.
- Log and monitor failed requests and suspicious patterns.
- Keep dependencies updated and patch vulnerabilities quickly.
Conclusion
API security is non-negotiable in 2025. By combining OAuth2, JWT, and Rate Limiting, you can build APIs that are both powerful and resilient against abuse.
Laravel makes it easier than ever to implement these patterns without reinventing the wheel. Whether you're exposing APIs for mobile apps, microservices, or AI agents, these tools should be part of your default stack.
Security is a journey, not a checkbox — but with the right foundations, your APIs can scale safely and sustainably.