Laravel Passport: How To Secure Your API Using Oauth
Last Updated on March 20, 2024
Introduction
Open Authorization(OAuth) is a way of accessing protected data from an application. It is secure in the sense that it does not require users to log in with passwords. Laravel Passport is a package that allows a developer to add OAuth to their API. It can be used as a token-based authentication package that generates an access token that can be attached to all requests when making calls to the server.
What is Laravel Passport
Laravel Passport provides an Oauth2 server implementation for your application. It provides an easy way of implementing authentication, creating tokens for applications that are interacting with the API. Data is protected from unauthorized access by having passport validate the access token. It provides one way of authenticating users to your application. Other ways include using Laravel Sanctum, Laravel Socialite and Session-based Authentication.
When to use Laravel Passport
Passport provides an easy implementation of the Oauth2 protocol for your application. Oauth2 allows users to grant permission to various third-party sites without the need to provide their passport. This ensures that a user controls which sites access their data from your application.
We might want to use passport in our application if we want to allow other sites to fetch account information from your application. An example of an OAuth implementation in a real-world scenario is the Sign in with Google, Facebook, Twitter functionality. This allows a third party site to access an account’s information and also have some liberty to interact with the account such as posting directly on the account, requesting all posts among other things.
User Authentication Using Laravel Passport
To demonstrate the functioning of passport, I am going to implement a simple authentication functionality using the package.
Prerequisites
Before we start, there are some requirements we will need in this tutorial.
- Composer to manage the dependencies
- Postman application for testing our application. We can use any other HTTP client such as Thunder Client.
Installation
To install the package, we can use this command:
composer require laravel/passport
New migration files will be generated in the process. These tables will store the clients and the access tokens generated by the application. We will then need to run the migrations.
php artisan migrate
Configuration
To generate secure access tokens, we will need to create encryption keys using the following command:
php artisan passport:install
After running the command, we will need to add the HasApiTokens trait to our User model. This will instruct Laravel to authenticate using API tokens and will also give access to various helper methods that will facilitate authentication. If your User model is already using the Laravel\Sanctum\HasApiTokens
trait, you may remove that trait and replace it with Laravel\Passort\HasApiTokens.
Next, we need to register our routes in the boot method of the AuthServiceProvider in the App\Providers\AuthServiceProvider file.
Passport::routes();
Finally, we need to instruct Laravel to use the passport as an authentication driver in the config/auth.php file. We will add the api authentication guard to passport.
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
We can customize how our OAuth server works by adding some configurations. One of them is adjusting the token lifetime. By default, passport creates access tokens that have a one-year lifespan. We can adjust this by adding the tokensExpireIn method in the boot method of the App\Providers\AuthServiceProvider.
Passport::tokensExpireIn(now()->addDays(15));
The token lifespan is adjusted from one year to just 15 days from the time the access token was created.
Basic Authentication with Laravel Passport
We can now use passport to authenticate users to our application. Let’s create a simple authentication logic.
We first need to create a controller that will hold the logic. The controller should validate the inputs before adding them to the database.
php artisan make:controller AuthController
To register a user, update the controller with the following:
public function register(Request $request)
{
$validatedEmail = Validator::make($request->all(), [
'email' => 'required|string|email|max:255|unique:users',
]);
if ($validatedEmail->fails()) {
return response()->json([
'error' => 'Duplicate Email found',
'status' => Response::HTTP_UNPROCESSABLE_ENTITY,
'message' => 'Duplicate Email detected. Please input another email'
], Response::HTTP_UNPROCESSABLE_ENTITY); //422
}
$validatedData = $request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|confirmed',
]);
$validatedData['password'] = bcrypt($request->password);
$user = User::create($validatedData);
// sends a verification email once registration is done
event(new Registered($user));
$accessToken = $user->createToken('authToken')->accessToken;
return response(['access_token' => $accessToken], Response::HTTP_OK); //200
}
To log in a user, update the controller with the following:
public function login(Request $request)
{
$loginData = $request->validate([
'email' => 'email|required',
'password' => 'required',
]);
if (!auth()->attempt($loginData)) {
return response(['message' => 'invalid credentials'], 401);
}
$accessToken = auth()->user()->createToken('authToken')->accessToken;
return response(['user' => auth()->user(), 'access_token' => $accessToken]);
}
We need to update the routes/api.php file with the login and register routes.
// Register Route
Route::post('/register', [AuthController::class, 'register']);
// Login Route
Route::post('/login', [AuthController::class, 'login']);
These methods will be used for registration and login respectively. An access token is created and attached to the response. Protected routes can be accessed using this token. An example of a protected route is /dashboard. We need to have a user be authorized to access the dashboard and as a result, we can attach the user’s access token to the request and this will return the results only if a user’s request has a valid access token attached to the request.
Logout using Laravel passport
If a user is logged in to a normal web application that uses session-based authentication, it is necessary to have a logout button that clears all the session data and protects the account from session hijacking. Having inactivity timeouts that automatically log out a user once the system detects inactivity over a period of time can also be another way of clearing session data. But what happens if a user is authenticated using an access token? Since access tokens created using Laravel passport have a lifespan of one year, they are susceptible to breaches leading to unauthorized access.
We can add logout functionality to our API by manually revoking access tokens rendering them useless.
To do so, we will need to update our AuthController with the following code:
public function logout(Request $request)
{
$request->user()->token()->revoke();
return response()->json([
'message' => 'Successfully logged out',
], Response::HTTP_OK); //Status 200
}
This will get the access token from the request headers and revoke it. We also need to update our routes/api.php file and add the logout route.
Route::group(['middleware' => 'auth:api'], function () {
//logout
Route::get('/logout', [AuthController::class, 'logout'])->name('logout.api');
});
The requirement is having the auth:api middleware that instructs Laravel to only serve the route if the request headers have the valid access token.
Laravel Passport Get User From Token
We can access a user’s details by querying the User Model using their access token. An example is using the following code:
public function books()
{
$user_id = auth()->guard('api')->user()->id;
$books = Books::where('user_id',$user_id)->get();
return response()->json(['data'=>$books],Response::HTTP_OK);
}
We can access the user books through their access token in this case.
Laravel Passport With Socialite
Laravel also provides other ways of authentication such as using the Laravel Socialite package that allows us to authenticate users through their social media accounts. Sometimes we may want to present the user with the liberty to authenticate using their social media platforms and have our Oauth2 server generate a standard access token once the authentication with a social media platform is successful. I am going to show you how to incorporate Social Logins into our API.
Installation
We will use the Laravel Passport Social Grant package:
composer require coderello/laravel-passport-social-grant
This package allows us to add the social grants to our Oauth2 server.
We then need to install Laravel Socialite to our application to have access to the Third-party drivers. We use the following command:
composer require laravel/socialite
Configuration
Socialite supports authentication with Facebook, Twitter, Github, Google, LinkedIn, Bitbucket and Gitlab. In this case, I will use Google as a social provider. To do so, we will need to register Google’s credentials to our config/services.php as shown:
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URL')
],
Setup
We need to manage the social accounts and link them to users. To do so, we will create a LinkedSocialAccount model together with its migration file. We can create the model and the migration file at the same time using the following command.
php artisan make:model LinkedSocialAccount -m
Next, we update the LinkedSocialAccount model and the migration file as shown:
For the migration, update with the following:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateLinkedSocialAccountsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('linked_social_accounts', function (Blueprint $table) {
$table->id();
$table->string('provider_id');
$table->string('provider_name');
$table->unsignedInteger('user_id');
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('linked_social_accounts');
}
}
We then have to update the create_users_table migration file and have the email and password fields nullable. This allows a user to have only email stored in the database with the absence of a password. We also need to update the User Model and add the relationship.
public function linkedSocialAccounts()
{
return $this->hasMany(LinkedSocialAccount::class);
}
We can now migrate the database using the following command.
php artisan migrate
Google responds with a token that we can use to access the user account information upon authentication. We can add the user’s information directly to our application using the token from Google. Passport creates an access token that the user can now use to interact with our application.
We can update the routes in routes/api.php.
Route::post('/auth/Oauth', [AuthController::class, 'oauth'])->name('oauth.simulate');
The next step is creating a SocialAccountsService that will be responsible for creating/finding a user instance from the provider; in this case, we want to add a user from the Google token.
<?php
namespace App\Services;
use App\Models\User;
use App\Models\LinkedSocialAccount;
use Laravel\Socialite\Two\User as ProviderUser;
class SocialAccountsService
{
/**
* Find or create user instance by provider user instance and provider name.
*
* @param ProviderUser $providerUser
* @param string $provider
*
* @return User
*/
public function findOrCreate(ProviderUser $providerUser, string $provider): User
{
$linkedSocialAccount = LinkedSocialAccount::where('provider_name', $provider)
->where('provider_id', $providerUser->getId())
->first();
if ($linkedSocialAccount) {
return $linkedSocialAccount->user;
} else {
$user = null;
if ($email = $providerUser->getEmail()) {
$user = User::where('email', $email)->first();
}
if (!$user) {
$user = User::create([
'first_name' => $providerUser->getName(),
'last_name' => $providerUser->getName(),
'email' => $providerUser->getEmail(),
]);
}
$user->linkedSocialAccounts()->create([
'provider_id' => $providerUser->getId(),
'provider_name' => $provider,
]);
return $user;
}
}
}
We will also need to create a SocialUserResolver service that implements SocialUserResolverInterface. This service will retrieve user details from an access token(using Laravel Socialite) and find/create a user instance using the SocialAccountsService.
<?php
namespace App\Services;
use Exception;
use Coderello\SocialGrant\Resolvers\SocialUserResolverInterface;
use Illuminate\Contracts\Auth\Authenticatable;
use Laravel\Socialite\Facades\Socialite;
class SocialUserResolver implements SocialUserResolverInterface
{
/**
* Resolve user by provider credentials.
*
* @param string $provider
* @param string $accessToken
*
* @return Authenticatable|null
*/
public function resolveUserByProviderCredentials(string $provider, string $accessToken): ?Authenticatable
{
$providerUser = null;
try {
$providerUser = Socialite::driver($provider)- >userFromToken($accessToken);
} catch (Exception $exception) {}
if ($providerUser) {
return (new SocialAccountsService())->findOrCreate($providerUser, $provider);
}
return null;
}
}
The last step is to bind the SocialUserResolverInterface to the implementation by adding the $bindings property in the AppServiceProvider.
Implementation
An ideal situation would include having a Single Page Application or a Mobile Application that would interact with the Google SDK and authenticate the user. The client applications would then pass the token back to the server from where we can retrieve information from Google using the token. The information would then be inserted into the database through the /auth/Oauth route.
We can add the oauth method that will be responsible for inserting a user into the database and creating a passport access token and map it to the /auth/Oauth route.
public function oauth(Request $request)
{
$data = [
'grant_type' => $request->input('grant_type'),
'client_id' => $request->input('client_id'),
'client_secret' => $request->input('client_secret'),
'provider' => $request->input('provider'),
'access_token' => $request->input('access_token')
];
$url = route('passport.token');
$request->request->add($data);
$tokenRequest = $request->create(
$url,
'post',
);
$instance = Route::dispatch($tokenRequest);
return $instance;
}
We are going to use the password grant client assigned by passport to register a user to the database. To find the credentials, we can view the oauth_clients table in MySQL to get the client id and the client secret. In my case, the client id is 2. The server inserts the user details into the database and generates an access token as shown.
Laravel Passport vs Laravel Sanctum
Both Passport and Sanctum provide ways of API authentication. The main difference between the two is that Passport uses the OAuth protocol to authenticate and authorize requests while Sanctum does not use OAuth Protocol. Also, Sanctum is easier to use and is lightweight. If you are creating a small application that is a Single Page Application or a Mobile Application you can use Sanctum for authentication. If the application you are building is massive, serves multiple clients and other third-party sites are to use the application for authentication purposes, you can use Passport.
Deploying Passport To Production
Once we have our API authentication ready, we may want to deploy the API to a live server. To do so, we want passport to generate encryption keys needed to create access tokens. We will use the passport:keys command which will generate these keys.
php artisan passport:keys
Conclusion
Laravel Passport offers a simple and clear implementation of OAuth authentication with minimum code and a guarantee of a secure API. It abstracts the boilerplate code required to create an OAuth server from scratch. This improves the productivity of developers, allowing them to ship code with as little effort and time as possible. I hope this article was able to show you how to implement simple authentication using Laravel Passport. If you have any questions, feel free to ask them in the comment section and thank you for reading.