A Laravel REST API that is functionally excellent but inadequately secured is a liability. The problem is that security failures are often invisible until they become catastrophic. A data breach, a scraped customer list, or an abused endpoint that generates thousands of unwanted charges does not announce itself during development. It surfaces in production, usually at the worst possible time.
API security is not a single task you complete at the end of a project. It is a set of design decisions made throughout the development process. This article covers the decisions that matter most for Laravel REST APIs serving SaaS products and mobile applications.
Token Management: Sanctum for Most, Passport for OAuth
Laravel Sanctum is the right authentication package for the vast majority of REST APIs. It handles personal access tokens for mobile apps and first-party SPA authentication using a cookie-based session for web frontends. The setup is lightweight and the security model is straightforward.
Personal access tokens issued by Sanctum are hashed before storage. The plaintext token is shown once at creation and never again. This is the correct behavior. If a token is lost, it must be revoked and a new one issued. This pattern protects against database compromise: even if an attacker obtains your tokens table, they cannot use the stored hashes as tokens.
Token expiry is a frequently skipped configuration. By default, Sanctum tokens do not expire. For any API that handles sensitive user data or financial operations, tokens should have an expiry. Shorter-lived access tokens paired with longer-lived refresh tokens is the standard pattern for mobile applications. The refresh token is used to obtain a new access token when the current one expires, without requiring the user to log in again.
Ability Scopes: The Correct Level of Access
Sanctum supports token abilities, which are essentially scopes that restrict what a specific token is permitted to do. This is one of the most underused security features in Laravel APIs.
Without scopes, every token grants full access to every API endpoint the user can reach. If a mobile app's token is stolen, the attacker can perform any action the user is allowed to perform, including deleting accounts, changing email addresses, or exporting all data.
With scopes, a mobile app token might be issued with abilities like read:profile, read:orders, and create:orders. Even if this token is compromised, the attacker cannot change the user's password or delete their account, because those actions require abilities that were not granted to the mobile token.
In practice, define token abilities for each consumer type before writing any endpoints. Mobile app tokens, admin dashboard tokens, webhook receiver tokens, and third-party integration tokens should each receive only the abilities required for their specific function. In your controllers, check abilities using $request->user()->tokenCan('ability-name') before performing sensitive actions.
Authorization Beyond Authentication
Authentication verifies who a user is. Authorization verifies what that user is allowed to do. Both are necessary, and both are frequently incomplete in API implementations.
The most common authorization failure in Laravel APIs is not missing authentication, it is missing object-level authorization. A user is authenticated. They request an order record with ID 4521. The API returns the order without checking whether order 4521 actually belongs to the authenticated user.
This class of vulnerability, known as Broken Object Level Authorization or BOLA, is consistently the top-ranked API security risk according to the OWASP API Security Project. It is also one of the easiest to fix: use Laravel Policies to define who can read or modify each model, and call the policy in every controller action.
$this->authorize('view', $order);
This single line checks whether the authenticated user is allowed to view the specific order being requested. If not, Laravel throws an AuthorizationException that becomes a clean 403 response. Without this check, any authenticated user can access any order by guessing the ID.
Rate Limiting: Tiered and Per-User
Rate limiting protects your API from both malicious abuse and accidental overuse. Laravel's throttle middleware and named rate limiters allow flexible configuration at the route level.
The standard configuration applies a simple per-IP rate limit to all API routes. For a basic API, this is sufficient. For a SaaS API with multiple plan tiers, you want per-authenticated-user rate limits that reflect the user's plan level. Laravel's RateLimiter::for method in the service provider supports this pattern cleanly.
When a rate limit is exceeded, return a 429 Too Many Requests response with a Retry-After header specifying the number of seconds until the limit resets. Well-behaved API clients, including properly built mobile apps, will read this header and back off automatically.
Consider separate rate limits for different endpoint categories. Read endpoints (GET) can typically be more generous than write endpoints (POST, PUT, DELETE). Endpoints that trigger expensive operations like report generation or bulk exports should have very conservative limits regardless of plan level.
Input Validation as a Security Layer
Validation in a REST API is usually thought of as a user experience feature: helping users fix incorrect input. But validation is also a security layer that prevents malformed or malicious data from reaching your database and business logic.
SQL injection is largely eliminated by Eloquent's use of prepared statements. But other injection risks remain. Mass assignment vulnerabilities occur when an API endpoint allows a user to set model fields they should not be able to control. In Laravel, this is prevented by defining the fillable array on each model and never passing raw request input directly to Model::create() or $model->fill() without filtering through validation first.
File upload endpoints carry additional risks. Validate MIME types server-side rather than trusting the extension or the client-provided MIME type. Store uploaded files outside the public web root. Never serve uploaded files directly through the web server without authentication checks.
CORS Configuration for SPA Frontends
If your Laravel API serves a React, Vue, or Angular frontend on a different domain, you need to configure CORS correctly. Laravel's CORS handling through the fruitcake/laravel-cors package (now included in Laravel core) allows you to specify exactly which origins are allowed to make requests, which HTTP methods are permitted, and which headers are allowed.
Avoid the temptation to configure CORS to allow any origin with a wildcard. This makes cross-origin requests possible from any website on the internet, including malicious ones. Specify your frontend domains explicitly. If you are using cookie-based authentication for your SPA, set supports_credentials: true in the CORS config and ensure your frontend sends credentials with every request.
Secrets Management: No Credentials in Code
API keys, payment gateway secrets, SMS provider credentials, and database passwords should never appear in source code, configuration files committed to version control, or deployment scripts. In Laravel, all secrets are stored in the .env file which is excluded from version control via .gitignore.
For production deployments, secrets should be injected through environment variables provided by the hosting platform, a secrets manager service, or a CI/CD pipeline that reads from a secure vault. The application code reads these values through env() or config() helpers and never stores them in a place that could be committed to a repository.
If you are building a production Laravel REST API and want to ensure the security architecture is correct from the foundation up, review my Laravel API development services to understand how I approach security planning in API projects.