Managing (Laravel) API: Security Checklist
Or — if you’re into click baits — “10 essential tips for securing your Laravel API — read this now to protect your business!”
This article is a part of my API series — Managing API
APIs are an essential part of modern web development, allowing different applications to communicate and exchange data with each other. However, like any other software, APIs can be vulnerable to security risks if they are not properly designed and implemented. In this article, we will discuss some best practices for securing APIs and protecting them from common vulnerabilities.
Unfortunately, APIs being an essential part of the internet also means they are often the primary point of entry for attackers, who can use various techniques to gain unauthorized access to an API and exploit its vulnerabilities. By following the best practices outlined in this article, you can improve the security of your APIs and reduce the risk of attacks.
In today’s digital landscape, businesses must ensure that their APIs are secure and protect their customers’ data from potential breaches. By implementing the best practices discussed in this article, you can safeguard your APIs and maintain the trust of your users.
Use HTTPS to encrypt all traffic between the client and the server.
Install an SSL Certificate on Your Server.
This will allow your server to use HTTPS for secure communication.
Update the Config File
In your Laravel project, open the config/app.php
file and set the force_https
option to true
. This will force Laravel to use HTTPS for all requests.
Specify HTTPS Port If Needed
If your application is not running on the default HTTPS port (443), you can specify the HTTPS port in the config/app.php
file by setting the https_port
option.
1. Redirect Using Middleware
To redirect all traffic to HTTPS in Laravel, you can use the forceSSL
middleware in your routes. For example, if you want to redirect all traffic to HTTPS for the routes in your api
middleware group, update in your app/Http/Kernel.php
file as follows:
protected $middlewareGroups = [
'api' => [
// Other middlewares...
\App\Http\Middleware\ForceSSL::class,
],
// Other middleware groups...
];
You can also apply the forceSSL
middleware directly to individual routes instead of using the web
middleware group if you only want to redirect specific routes to HTTPS.
Route::get('/users', [UserController::class, 'index'])
->middleware('forceSSL');
2. Forcing Scheme on Routes
If you want to specify which routes should be served over HTTPS, you can use the forceScheme()
method in your route definitions, like this:
Route::get('/users', [UserController::class, 'index'])
->forceScheme('https');
3. Using URL Facade
To enforce the HTTPS scheme in a Laravel API, you can use the forceScheme
method on the URL
facade. This method allows you to specify the scheme (e.g. https
) that should be used for all URLs generated by Laravel.
Before defining the routes, import the URL
facade at the top of your routes file and use its forceScheme
method.
use Illuminate\Support\Facades\URL;
URL::forceScheme('https');
This will enforce the HTTPS scheme for all URLs generated by Laravel. You can then define your routes as usual:
Route::get('/users', [UserController::class, 'index']);
Use Laravel’s built-in authentication and authorization mechanisms to restrict access to protected resources.
1. Make Laravel Auth
In your Laravel project, run the php artisan make:auth
command to generate the necessary authentication and authorization scaffolding. This will create several controllers, views, and routes that you can use to implement authentication and authorization in your Laravel application. This includes controllers for managing user registration, login, and logout, as well as views for displaying the login and registration forms.
In addition to the controllers and views, this command will also create routes for handling user authentication and registration. These routes are defined in the routes/web.php
file and use the auth
middleware, which is included in the default Laravel installation.
To use the authentication and authorization scaffolding generated by this command, you will need to create a database and run the migrations to create the necessary tables for storing user information. You can do this by running the php artisan migrate
command.
2. Add Middleware to the Routes
In your API routes file (typically routes/api.php
), add middleware to protect the routes that should be restricted. For example, to protect a route with the auth
middleware, which ensures that only authenticated users can access the route, you can use the following code:
Route::get('/users', [UserController::class, 'index'])
->middleware('auth');
You can use middleware for the Route Groups as well:
Route::group(['middleware' => 'auth'], function () {
Route::get('/users', [UserController::class, 'index']);
});
3. Create a Laravel Policy
To implement authorization, you can use Laravel’s policies. A policy is a class that determines whether a user is authorized to perform a certain action on a given resource.
To create a policy, you can use the php artisan make:policy
command. This command generates a new policy class and registers it with Laravel's service container:
php artisan make:policy UserPolicy
This will create a new UserPolicy
class in the app/Policies
directory and register it with the service container. The policy class will have a __construct
method and a before
method, which you can use to define the logic for determining whether a user is authorized to perform a certain action on a given resource.
4. Define the Policy
In our Policy file, we can define whatever methods you need. I'll create a manage()
method, which I’ll use to determine whether the user can create, update, delete, restore, or permanently delete the model.
<?php
namespace App\Policies;
use App\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function manage(User $user)
{
return $user->is_admin || $user->id === $model->id;
}
}
5. Register Policy to the Provider
To use the policy, we first need to register it with Laravel’s service container. You need to add it to the $policies
property of the AuthServiceProvider
class. The AuthServiceProvider
class is included in the default Laravel installation and is responsible for registering the application's policies.
Open the app/Providers/AuthServiceProvider.php
file and add your policy class to the $policies
property:
protected $policies = [
// Other policies...
\App\User::class => \App\Policies\UserPolicy::class,
];
In this example, we are registering the UserPolicy
policy class for the User
model. This means that the UserPolicy
class will be used to authorize actions on instances of the User
model.
6. Use Gate Facade and can() method
To use the manage
method that we defined in the previous example, you can call it in the same way as you would call any other policy method. For example, if you want to authorize a user to delete a user model, you can do the following:
// Get the authenticated user
$user = auth()->user();
// Get the user model that the user wants to delete
$model = User::findOrFail(request('id'));
// Authorize the user to delete the model
if (Gate::denies('manage', $model)) {
// The user is not authorized to delete the model
abort(403, 'Unauthorized action.');
}
// The user is authorized to delete the model, so delete it
$model->delete();
In this example, we first get the authenticated user and the user model that the user wants to delete. Then, we use Laravel’s Gate
class to authorize the user to delete the model. If the user is not authorized, we return a 403 Forbidden response. Otherwise, we delete the model.
You can also use the can
method instead of the denies
method to check if the user is authorized to perform an action. This method returns a boolean value, so you can use it in a conditional statement. For example:
if (Gate::can('manage', $model)) {
// The user is authorized to delete the model, so delete it
$model->delete();
} else {
// The user is not authorized to delete the model
abort(403, 'Unauthorized action.');
}
By following these steps, you can use Laravel’s built-in authentication and authorization mechanisms to restrict access to protected resources in your API.
Use strong passwords and regularly rotate them to prevent unauthorized access.
1. Update the Hashing Algorithm
In your Laravel project, open the config/auth.php
file and set the passwords
option to bcrypt
. This will ensure that all passwords are hashed using the bcrypt algorithm, which is considered to be a strong and secure hashing algorithm.
2. Create a Custom Validation Rule
To enforce the use of strong passwords, you can use the Validator
class to define a custom validation rule. For example, you can create a StrongPassword
rule that requires the password to have at least 8 characters, including at least one uppercase letter, one lowercase letter, and one number, like this:
Validator::extend('strong_password', function ($attribute, $value, $parameters, $validator) {
return preg_match('/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0–9]).{8,}$/', $value);
});
3. Apply the Custom Rule
Once you have defined the StrongPassword
rule, you can use it in your validation rules when creating or updating a user. For example:
$request->validate([
// ...other validation rules
'password' => 'required|confirmed|strong_password',
]);
4. Set password expiration period
To regularly rotate passwords, you can set a password expiration period in your application. For example, you can add a password_expires_at
field to the users
table, which will store the date and time when the password will expire. You can then use a scheduled task (e.g. using Laravel's Task Scheduling
feature) to check if any passwords have expired and to send a password reset email to the user.
Create a migration (php artisan make:migration
) and add the following to the generated file:
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->date('password_expires_at')->nullable();
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('password_expires_at');
});
}
Create a scheduled task that runs daily to check for expired passwords and send password reset emails to users whose passwords have expired.
Open app/Console/Kernel.php
and add this to its schedule()
method:
protected function schedule(Schema $schedule)
{
$schedule->call(function () {
// Get all users with expired passwords
$users = User::where('expire_at', '<', now())->get();
// Send password reset emails to users with expired passwords
foreach ($users as $user) {
$user->sendPasswordResetNotification();
}
})->daily();
}
In this example, we are using Laravel’s built-in sendPasswordResetNotification
method to send password reset emails to users with expired passwords. This method will generate a password reset token and send an email to the user with a link to reset their password.
When a user changes their password, update the expire_at
column with the new expiration date. Open app/Http/Controllers/Auth/ResetPasswordController.php
and update your reset()
method as follows:
public function reset(Request $request)
{
// Validate the password reset request and reset the user's password
$request->validate([
'email' => 'required|email',
'password' => 'required|confirmed|min:8',
'token' => 'required',
]);
// Get the user with the given email address
$user = User::where('email', $request->email)->firstOrFail();
// Set the user's password and password expiration date
$user->password = Hash::make($request->password);
$user->expire_at = now()->addDays(30);
$user->save();
// Log the user in and redirect to the dashboard
auth()->login($user);
return redirect()->route('dashboard');
}
In this example, we are using Laravel’s Hash
facade to securely hash the user's new password and save it to the users
table. We are also setting the expire_at
column to the current date plus 30 days, which will be the new expiration date for the user's password.
By following these steps, you can use strong passwords and regularly rotate them to prevent unauthorized access to your API backend in Laravel.
Use Laravel’s protection against SQL injection attacks by using parameterized queries.
To protect against SQL injection attacks in Laravel, you can use parameterized queries. Parameterized queries are a type of SQL query in which placeholders are used for values that are supplied at runtime. This allows the database to differentiate between actual SQL code and data, which can help prevent SQL injection attacks.
Laravel provides several ways to use parameterized queries, depending on the type of database query you are using.
1. Query Builder
In your Laravel project, you can use Laravel’s query builder to build parameterized queries. For example, to select all users from the users
table where the id
is equal to a given value, you can use the following code:
$users = User::where('id', '=', $id)->get();
In this example, the $id
variable is automatically escaped and enclosed within quotation marks, which prevents any malicious code from being injected into the query. You can also use direct DB queries, like this:
$users = DB::select('SELECT * FROM users WHERE id = :id', ['id' => $id]);
In this example, we are using the DB::select
method to select all users with the specified id
value. The :id
placeholder is used in the SQL query and the actual id
value is passed as the second argument to the DB::select
method. This ensures that the id
value is treated as data, rather than as part of the SQL query, which can help prevent SQL injection attacks.
2. Raw Expressions
You can also use parameterized queries with raw expressions. For example, to select all users from the users
table where the created_at
column is greater than a given date, you can use the following code:
$users = User::whereRaw('created_at > ?', [$date])->get();
In this example, the $date
variable is automatically escaped and enclosed within quotation marks, which prevents any malicious code from being injected into the query.
By using parameterized queries, you can take advantage of Laravel’s protection against SQL injection attacks in your API backend.
Use Laravel’s protection against cross-site scripting (XSS) attacks by escaping all user-generated data before displaying it.
To use Laravel’s protection against cross-site scripting (XSS) attacks by escaping all user-generated data before displaying it in an API backend, you can follow these steps:
Escaping Strings
In your Laravel project, you can use the htmlspecialchars()
function to escape user-generated data before displaying it. For example, to escape a user's name before displaying it in an API response, you can use the following code:
$user = User::find($id);
return response()->json([
'name' => htmlspecialchars($user->name),
]);
In this example, the htmlspecialchars()
function will convert any special characters in the user's name (such as <
and >
) to their corresponding HTML entities (e.g. <
and >
), which prevents any malicious code from being executed in the browser.
Shorter Alias
Alternatively, you can use Laravel’s e()
helper function, which is a shortcut for the htmlspecialchars()
function. For example:
$user = User::find($id);
return response()->json([
'name' => e($user->name),
]);
By escaping all user-generated data before displaying it, you can use Laravel’s protection against XSS attacks in your API backend.
Use Laravel’s protection against cross-site request forgery (CSRF) attacks by including a CSRF token with every form submission and verifying the token on the server.
To use Laravel’s protection against cross-site request forgery (CSRF) attacks by including a CSRF token with every form submission and verifying the token on the server in an API backend, you can follow these steps:
CSRF Field
In your Laravel project, you can use Laravel’s csrf_field()
function to generate a hidden input field with a CSRF token in your forms. For example, to include a CSRF token in a form that allows the user to update their profile, you can use the following code:
<form method="POST" action="{{ route('profile.update') }}">
@csrf
<label for="name">Name</label>
<input type="text" name="name" id="name" value="{{ old('name') }}">
<button type="submit">Update Profile</button>
</form>
Verifying the Token
When the form is submitted, the CSRF token will be sent along with the other form data. Laravel will automatically verify that the token is valid and matches the current session on the server.
To manually tell Laravel to validate the CSRF token for a specific route, add csrf
middleware in your routes file (routes/api.php
).
Route::get('/users', [UserController::class, 'index'])
->middleware('csrf');
Invalid Token
If the token is invalid or does not match the current session, Laravel will automatically reject the request and return a 419 (unknown status)
error.
By including a CSRF token with every form submission and verifying the token on the server, you can use Laravel’s protection against CSRF attacks in your API backend.
Use Laravel’s protection against session hijacking by regenerating the session ID whenever a user logs in or out.
To use Laravel’s protection against session hijacking by regenerating the session ID whenever a user logs in or out in an API backend, you can follow these steps:
Auth Facade
In your Laravel project, you can use the Auth
facade to regenerate the session ID whenever a user logs in or out. For example, to regenerate the session ID after the user has successfully logged in, you can use the following code in your LoginController
:
public function login(Request $request)
{
// Validate the request…
if (Auth::attempt($request->only('email', 'password'))) {
// Regenerate the session ID…
Auth::user()->regenerate();
// Redirect to the dashboard…
}
}
Regenerate ID
In this example, the regenerate()
method will generate a new session ID and discard the old one, which prevents an attacker from hijacking the user's session.
After Logging Out
Similarly, you can regenerate the session ID after the user has logged out by calling the regenerate()
method in your LogoutController
, like this:
public function logout()
{
// Logout the user…
Auth::logout();
// Regenerate the session ID…
Auth::user()->regenerate();
// Redirect to the homepage…
}
By regenerating the session ID whenever a user logs in or out, you can use Laravel’s protection against session hijacking in your API backend.
Use Laravel’s built-in rate limiting to prevent brute-force attacks on the login form or other sensitive endpoints.
To use Laravel’s built-in rate limiting to prevent brute-force attacks on the login form or other sensitive endpoints in an API backend, you can follow these steps:
Use Throttle Middleware
In your Laravel project, you can use the throttle
middleware to limit the number of requests that a user can make to a given endpoint within a specified time period. For example, to limit the number of login attempts to 5 per minute, you can use the following code in your LoginController
:
public function login(Request $request)
{
// Throttle the login attempts…
$this->middleware('throttle:5,1')->only('login');
// Validate the request…
if (Auth::attempt($request->only('email', 'password'))) {
// Login the user…
}
}
Rejecting exceeded number of attempts
In this example, the throttle
middleware will limit the number of login attempts to 5 per minute. If a user exceeds the allowed number of attempts within the specified time period, Laravel will automatically reject the request and return a 429 (too many requests)
error.
Error Response
You can also customize the error response by defining a throttle
key in the HTTP
section of your resources/lang/en/response.php
language file, like this:
"throttle" => "No, no, no. Please try again in :seconds seconds."
By following these steps, you can use Laravel’s built-in rate limiting to protect your API against brute-force attacks.
Keep the Laravel framework and all dependencies up to date to ensure that the latest security patches are applied.
To keep the Laravel framework and all dependencies up to date to ensure that the latest security patches are applied in an API backend, you can follow these steps:
Update Composer
In your Laravel project, you can use the composer
command-line tool to update the Laravel framework and all dependencies to their latest versions. For example, to update the Laravel framework and all dependencies to the latest versions, you can run the following command:
composer update
This command will check the composer.json
file in your project and update all dependencies to the latest versions that are compatible with your project's constraints.
Fix the Issues
After running this command, you should test your application to make sure that it still works as expected. If any issues are discovered, you can use the composer show
command to check the versions of the dependencies that were updated and troubleshoot the issues accordingly.
List Outdated Items
You can also use the composer outdated
command to check which dependencies are outdated and need to be updated. For example, to check which dependencies are outdated in your project, you can run the following command:
composer outdated
Update the Outdated
This command will display a list of all dependencies that are outdated and need to be updated. You can then update the outdated dependencies by running the composer update
command again, as described in step 1.
By keeping the Laravel framework and all dependencies up to date, you can ensure that the latest security patches are applied in.
Regularly test the API for vulnerabilities using tools such as Postman or cURL.
To regularly test an API for vulnerabilities using tools such as Postman or cURL, you can follow these steps:
Install the Right Tool
Install and set up Postman or cURL on your computer.
Send Requests
Use Postman or cURL to send various types of HTTP requests to different endpoints in your API and analyze the responses to check for potential vulnerabilities.
Common Vulnerabilities
Some common types of vulnerabilities that you should look for when testing your API include:
- SQL injection: where malicious code is injected into a SQL query to gain unauthorized access to the database.
- Cross-site scripting (XSS): where malicious code is injected into a web page to execute malicious scripts in the user’s browser.
- Cross-site request forgery (CSRF): where an attacker tricks a user into performing an unintended action in their browser (e.g. making a purchase, changing their password).
- Session hijacking: where an attacker gains unauthorized access to a user’s session and performs actions on their behalf.
Use Specific Requests
Use Postman or cURL to send specifically designed requests to exploit these vulnerabilities and see if the API can handle them properly.
Fix and Repeat
If any vulnerabilities are discovered, fix them and test the API again to make sure that the vulnerabilities have been properly addressed. Repeat this process regularly to ensure that your API is secure and free of vulnerabilities.