Managing (Laravel) API: Security Checklist

Mihailo Cvetkovic
14 min readDec 11, 2022

--

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. &lt; and &gt;), 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.

--

--