If you’ve ever tried to implement authentication in a Laravel application and found yourself stuck trying to manage user sessions and permissions across multiple routes and controllers, you’re not alone. I’ve been there too, and it’s frustrating.
However, what if you could securely authenticate users without having to worry about storing their passwords or managing session tokens? With OAuth 2.0, you can offload this complexity to a central authentication server, freeing up your application to focus on its core functionality. By the end of this tutorial, You’ll build an OAuth-enabled Laravel app that protects sensitive routes with client credentials and uses refresh tokens to handle long-lived sessions.
Configuring Laravel to Use OAuth 2.0
To start implementing OAuth 2.0 in your Laravel application, you’ll need to configure it properly.
First, install the laravel/passport package along with Laravel’s API scaffolding by running the following command in your terminal:
php artisan install:api --passport
This command installs the package, publishes and runs the migrations that create the OAuth tables in your database, and generates the encryption keys Passport uses to sign access tokens. Next, add the HasApiTokens trait and the OAuthenticatable interface to your App\Models\User model:
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;
// ...
}
Next, configure Passport by publishing its configuration file and setting up the default settings. Open a terminal and navigate to your project root directory, then use the following command to publish the configuration file:
php artisan vendor:publish --tag=passport-config
The published config/passport.php file holds settings such as the authentication guard and the encryption keys. Token expiration times are configured in code: open app/Providers/AppServiceProvider.php in your favorite text editor and update the tokens’ expiration times according to your application’s requirements.
Here is a sample configuration:
use Carbon\CarbonInterval;
use Laravel\Passport\Passport;
public function boot(): void
{
Passport::tokensExpireIn(CarbonInterval::minutes(60));
Passport::refreshTokensExpireIn(CarbonInterval::days(30));
Passport::personalAccessTokensExpireIn(CarbonInterval::months(6));
}
Configure Passport as the driver for your API guard by opening the config/auth.php file and updating the guards section. Add an api guard that uses the passport driver and the existing users provider.
return [
// ...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
];
That’s it for this section. With the laravel/passport package installed and configured, you’re ready to move on to implementing OAuth clients in your database.
Next, run the database migrations to create the necessary tables in your database:
php artisan migrate
With these steps complete, you should now have the laravel/passport package installed and configured in your Laravel application.
Note that this is just one of many packages available for implementing OAuth 2.0 in Laravel. If you prefer to use a different package or roll your own implementation, feel free to do so – but laravel/passport is a great choice due to its simplicity and ease of use.
Generating API Keys for Clients
In a typical OAuth implementation, clients (e.g., mobile apps, web applications) need credentials to authenticate and obtain an access token. We’ll use Passport’s built-in client credentials grant to generate API keys for our clients.
First, we need to create a new client in the database using the following command:
php artisan passport:client --client --name="My Web App"
This will create a new client with an ID and secret. We’ll use these credentials later to authenticate our client. The secret is only displayed once, so store it somewhere safe.
Now, let’s protect a machine-to-machine route with Passport’s EnsureClientIsResourceOwner middleware, which only accepts valid client credentials tokens:
// routes/api.php
use Illuminate\Http\Request;
use Laravel\Passport\Http\Middleware\EnsureClientIsResourceOwner;
Route::get('/reports', function (Request $request) {
return response()->json(['status' => 'ok']);
})->middleware(EnsureClientIsResourceOwner::class);
Next, the client exchanges its ID and secret for an access token at Passport’s /oauth/token endpoint:
curl -X POST http://localhost:8000/oauth/token \
-d grant_type=client_credentials \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET
The client then sends the returned access token as a Bearer token when calling the protected route:
curl http://localhost:8000/api/reports \
-H 'Accept: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
With this setup, any client created using the passport:client --client command can use its ID and secret as API credentials, and only valid client credentials tokens can reach the protected route.
Creating an OAuth Client in the Database
To use OAuth 2.0 with Laravel Passport, we need to create a client that will be responsible for authenticating our users. Passport’s migrations have already created the oauth_clients table, so we only need to enable the password grant and create a password grant client in it.
First, enable the password grant, which is disabled by default, by adding the following line to the boot method of app/Providers/AppServiceProvider.php:
Passport::enablePasswordGrant();
You can then create a password grant client directly from the command line:
php artisan passport:client --password --name="My Password Client"
If you’d rather create the client automatically, for example in local and testing environments, use a seeder instead. Run the following command to generate a new seeder:
php artisan make:seeder OAuthClientsTableSeeder
In the generated OAuthClientsTableSeeder file, add the following code:
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Laravel\Passport\ClientRepository;
class OAuthClientsTableSeeder extends Seeder
{
public function run(ClientRepository $clients): void
{
$client = $clients->createPasswordGrantClient('My Password Client', 'users', true);
$this->command->info('Client ID: ' . $client->getKey());
$this->command->info('Client secret: ' . $client->plainSecret);
}
}
Run the seeder to add the client to the oauth_clients table:
php artisan db:seed --class=OAuthClientsTableSeeder
This will create a new OAuth password grant client and print its ID and secret. We’ll use this client when testing the OAuth login flow later in this tutorial.
Implementing Login and Registration with OAuth
To enable users to log in and register using OAuth, we’ll create two routes that will handle these operations. After authentication, we’ll return a Passport access token to the client in a JSON response.
// routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\OAuthController;
Route::post('/login', [OAuthController::class, 'login']);
Route::post('/register', [OAuthController::class, 'register']);
Next, we’ll create a controller to handle the OAuth login and registration logic. This controller will use Laravel’s Auth facade to authenticate users.
// app/Http/Controllers/OAuthController.php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\Request;
class OAuthController extends Controller
{
public function login(Request $request)
{
// Validate the request
$validatedData = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
// Attempt to authenticate the user
if (!Auth::attempt($validatedData)) {
return response()->json(['error' => 'Invalid credentials'], 401);
}
// If authenticated, generate an access token for the user
$accessToken = Auth::user()->createToken('oauth-access-token')->accessToken;
return response()->json(['access_token' => $accessToken]);
}
public function register(Request $request)
{
// Validate the request
$validatedData = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'unique:users,email'],
'password' => ['required', 'min:8'],
]);
// Create a new user
$user = User::create($validatedData);
// Generate an access token for the newly created user
$accessToken = $user->createToken('oauth-access-token')->accessToken;
return response()->json(['access_token' => $accessToken]);
}
}
This is a basic implementation of OAuth login and registration using Laravel’s built-in Auth facade. The createToken method issues a Passport personal access token, so your application needs a personal access client; if you don’t have one yet, create it with php artisan passport:client --personal.
Protecting Routes with OAuth Authentication
Now that our users can register and log in using OAuth, we need to protect routes that require authentication.
To do this, we’ll use Laravel’s built-in auth:api middleware. This middleware will check if the incoming request has a valid access token for the client making the request.
First, let’s create a new route group to define our protected routes:
// routes/api.php
Route::group(['middleware' => 'auth:api'], function () {
// Protected routes go here...
});
Next, we’ll add the auth:api middleware to the route that requires OAuth authentication. For example, let’s say we have a /users endpoint that returns a user’s information:
// routes/api.php
use App\Http\Controllers\UserController;
Route::get('/users', [UserController::class, 'show'])->middleware('auth:api');
When an unauthenticated client makes a request to the /users endpoint, it will return a 401 Unauthorized response. If a valid access token is present in the request, the middleware will authenticate the client and allow the request to proceed.
That’s it! With this setup, any route that uses the auth:api middleware will require OAuth authentication before allowing access.
Handling Refresh Tokens and Revoking Access
When implementing OAuth 2.0, it’s crucial to handle refresh tokens and revoke access properly to prevent unauthorized access and maintain security.
Refresh Tokens
Passport issues a refresh token alongside every access token obtained through the password grant. Their lifetimes are controlled by the tokensExpireIn and refreshTokensExpireIn methods in the boot method of your AppServiceProvider:
Passport::tokensExpireIn(CarbonInterval::minutes(60)); // access tokens
Passport::refreshTokensExpireIn(CarbonInterval::days(30)); // refresh tokens
This configuration affects how long the access and refresh tokens are valid. When an access token expires, the client exchanges its refresh token for a new access token by sending a refresh_token grant request to /oauth/token:
curl -X POST http://localhost:8000/oauth/token \
-d grant_type=refresh_token \
-d refresh_token=YOUR_REFRESH_TOKEN \
-d client_id=YOUR_CLIENT_ID \
-d client_secret=YOUR_CLIENT_SECRET
Revoking Access
To revoke an access token or its corresponding refresh token, use the revoke method on Passport’s Token and RefreshToken models:
use App\Models\User;
use Laravel\Passport\Passport;
use Laravel\Passport\Token;
// Revoke an access token by its ID
$token = Passport::token()->find($tokenId);
$token->revoke();
$token->refreshToken?->revoke();
// Revoke all tokens for a user by their ID
User::find($userId)->tokens()->each(function (Token $token) {
$token->revoke();
$token->refreshToken?->revoke();
});
To handle revocation properly, consider implementing a scheduled task to clean up revoked tokens periodically. Passport’s passport:purge command removes revoked and expired tokens, and you can schedule it in routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('passport:purge')->hourly();
By handling refresh tokens and revoking access correctly, you can ensure your OAuth implementation remains secure.
Testing Your OAuth 2.0 Implementation
Now that you have a working OAuth 2.0 implementation in place, it’s time to test it thoroughly. This will ensure that your application behaves as expected and catches any potential issues before they reach production.
To test the login functionality using OAuth, send a POST request to http://localhost:8000/oauth/token (adjust the URL according to your Laravel project path) with a REST client like Postman or cURL. You’ll need to provide the required parameters:
curl -X POST \
http://localhost:8000/oauth/token \
-H 'Content-Type: application/json' \
-d '{"grant_type":"password","client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","username":"john.doe@example.com","password":"password"}'
Replace the client_id and client_secret with the password grant client’s ID and secret you saved when creating it (the secret is stored hashed, so it can’t be read back from the database). The response should include an access token and a refresh token that you can use to authenticate subsequent requests.
To test protected routes, send a request to a route like http://localhost:8000/api/users (the route we protected with auth:api earlier) in your REST client. If everything is set up correctly, a request without a token returns a 401 Unauthorized response. Then, send the access token in the Authorization header as a Bearer token:
curl -X GET \
http://localhost:8000/api/users \
-H 'Accept: application/json' \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Verify that you’re able to access protected routes without any issues. With these tests, you should have confidence in your OAuth 2.0 implementation.
Your application is now more secure than ever, thanks to the added layer of authentication provided by OAuth 2.0.
Frequently Asked Questions
What is OAuth 2.0, and how does it differ from traditional authentication methods?
OAuth 2.0 is an authorization framework that allows users to grant third-party applications limited access to their resources on another service provider without sharing their login credentials. Unlike traditional authentication methods, OAuth 2.0 offloads the complexity of user session management and password storage to a central authentication server.
I’m getting a ‘Personal access client not found’ error when calling createToken. What’s causing this?
This error occurs when your application doesn’t have a personal access client for the user provider you’re issuing tokens for. To resolve the issue, run php artisan passport:client --personal, and if you use a custom user provider, pass it with the --provider option so it matches the providers section in your config/auth.php file.
How does OAuth 2.0 handle long-lived sessions using refresh tokens?
OAuth 2.0 uses refresh tokens to handle long-lived sessions by allowing clients to obtain a new access token when the original one expires, without requiring users to re-authenticate.
What’s the difference between OAuth 2.0 and JWT (JSON Web Tokens) for authentication?
They solve different problems. OAuth 2.0 is an authorization framework that defines how clients obtain access tokens and use them to access specific resources on behalf of users, whereas JWT is a token format for transmitting signed claims, such as a user ID and an expiry time. The two are often used together: the access tokens Passport issues are themselves JWTs.
I’m comparing Passport with another popular authentication package, Sanctum. What are the key differences?
Sanctum and Passport (used in this tutorial) both provide robust authentication functionality for Laravel applications. However, Sanctum is a lightweight package for SPA authentication and simple API tokens, whereas Passport provides a full OAuth 2.0 server implementation for applications that need OAuth grants such as client credentials and refresh tokens.
