In this article, we’re going to explore how you could set up a fully fledged OAuth2 server in Laravel using the Laravel Passport library. We’ll go through the necessary server configurations along with a real-world example to demonstrate how you could consume OAuth2 APIs.
I assume that you’re familiar with the basic OAuth2 concepts and flow as we’re going to discuss them in the context of Laravel. In fact, the Laravel Passport library makes it pretty easy to quickly set up an OAuth2 server in your application. Thus, other third-party applications are able to consume APIs provided by your application.
In the first half of the article, we’ll install and configure the necessary libraries, and the second half goes through how to set up demo resources in your application and consume them from third-party applications.
Server Configurations
In this section, we're going to install the dependencies that are required in order to make the Passport library work with Laravel. After installation, there's quite a bit of configuration that we'll need to go through so that Laravel can detect the Passport library.
Let's go ahead and install the Passport library using composer.
$composer require laravel/passport
That's pretty much it as far as the Passport library installation is concerned. Now let's make sure that Laravel knows about it.
Working with Laravel, you're probably aware of the concept of a service provider that allows you to configure services in your application. Thus, whenever you want to enable a new service in your Laravel application, you just need to add an associated service provider entry in the config/app.php
.
If you're not aware of Laravel service providers yet, I would strongly recommend that you do yourself a favor and go through this introductory article that explains the basics of service providers in Laravel.
In our case, we just need to add the PassportServiceProvider
provider to the list of service providers in config/app.php
as shown in the following snippet.
... ... 'providers' => [ /* * Laravel Framework Service Providers... */ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, Illuminate\Filesystem\FilesystemServiceProvider::class, Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, /* * Package Service Providers... */ Laravel\Tinker\TinkerServiceProvider::class, /* * Application Service Providers... */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, Laravel\Passport\PassportServiceProvider::class, ], ... ...
Next, we need to run the migrate
artisan command, which creates the necessary tables in a database for the Passport library.
$php artisan migrate
To be precise, it creates following the tables in the database.
oauth_access_tokens oauth_auth_codes oauth_clients oauth_personal_access_clients oauth_refresh_tokens
Next, we need to generate a pair of public and private keys that will be used by the Passport library for encryption. As expected, the Passport library provides an artisan command to create it easily.
$php artisan passport:install
That should have created keys at storage/oauth-public.key
and storage/oauth-private.key
. It also creates some demo client credentials that we'll get back to later.
Moving ahead, let's oauthify the existing User model class that Laravel uses for authentication. To do that, we need to add the HasApiTokens
trait to the User
model class. Let's do that as shown in the following snippet.
<?php namespace App; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'email', 'password', ]; /** * The attributes that should be hidden for arrays. * * @var array */ protected $hidden = [ 'password', 'remember_token', ]; }
The HasApiTokens
trait contains helper methods that are used to validate tokens in the request and check the scope of resources being requested in the context of the currently authenticated user.
Further, we need to register the routes provided by the Passport library with our Laravel application. These routes will be used for standard OAuth2 operations like authorization, requesting access tokens, and the like.
In the boot method of the app/Providers/AuthServiceProvider.php
file, let's register the routes of the Passport library.
... ... /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); } ... ...
Last but not least, we need to change the api
driver from token to passport in the config/auth.php
file, as we're going to use the Passport library for the API authentication.
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
So far, we've done everything that's required as far as the OAuth2 server configuration is concerned.
Set Up the Demo Resources
In the previous section, we did all the hard work to set up the OAuth2 authentication server in our application. In this section, we'll set up a demo resource that could be requested over the API call.
We will try to keep things simple. Our demo resource returns the user information provided that there's a valid uid
parameter present in the GET
request.
Let's create a controller file app/Http/Controllers/UserController.php
with the following contents.
<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use Illuminate\Http\Request; use App\User; class UserController extends Controller { public function get(Request $request) { $user_id = $request->get("uid", 0); $user = User::find($user_id); return $user; } }
As usual, you need to add an associated route as well, which you are supposed to add in the routes/web.php
file. But what we are talking about is the API route, and thus it needs special treatment.
The API routes are defined in the routes/api.php
file. So, let's go ahead and add our custom API route as shown in the following snippet.
<?php use Illuminate\Http\Request; /* |-------------------------------------------------------------------------- | API Routes |-------------------------------------------------------------------------- | | Here is where you can register API routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | is assigned the "api" middleware group. Enjoy building your API! | */ Route::middleware('auth:api')->get('/user', function (Request $request) { return $request->user(); }); // custom API route Route::middleware('auth:api')->get('/user/get', 'UserController@get');
Although we've defined it as /user/get
, the effective API route is /api/user/get
, and that's what you should use when you request a resource over that route. The api
prefix is automatically handled by Laravel, and you don't need to worry about that!
In the next and last section, we'll discuss how you could create client credentials and consume the OAuth2 API.
How to Consume OAuth2 APIs
Now that we've set up the OAuth2 server in our application, any third party can connect to our server with OAuth and consume the APIs available in our application.
First of all, third-party applications must register with our application in order to be able to consume APIs. In other words, they are considered as client applications, and they will receive a client id and client secret upon registration.
The Passport library provides an artisan command to create client accounts without much hassle. Let's go ahead and create a demo client account.
$php artisan passport:client Which user ID should the client be assigned to?: > 1 What should we name the client?: > Demo OAuth2 Client Account Where should we redirect the request after authorization? [http://localhost/auth/callback]: > http://localhost/oauth2_client/callback.php New client created successfully. Client ID: 1 Client secret: zMm0tQ9Cp7LbjK3QTgPy1pssoT1X0u7sg0YWUW01
When you run the artisan passport:client
command, it asks you a few questions before creating the client account. Out of those, there's an important one that asks you the callback URL
.
The callback URL
is the one where users will be redirected back to the third-party end after authorization. And that's where the authorization code that is supposed to be used in exchange for the access token will be sent. We are about to create that file in a moment.
Now, we're ready to test OAuth2 APIs in the Laravel application.
For demonstration purposes, I'll create the oauth2_client
directory under the document root in the first place. Ideally, these files will be located at the third-party end that wants to consume APIs in our Laravel application.
Let's create the oauth2_client/auth_redirection.php
file with the following contents.
<?php $query = http_build_query(array( 'client_id' => '1', 'redirect_uri' => 'http://localhost/oauth2_client/callback.php', 'response_type' => 'code', 'scope' => '', )); header('Location: http://your-laravel-site-url/oauth/authorize?'.$query);
Make sure to change the client_id
and redirect_uri
parameters to reflect your own settings—the ones that you used while creating the demo client account.
Next, let's create the oauth2_client/callback.php
file with the following contents.
<?php // check if the response includes authorization_code if (isset($_REQUEST['code']) && $_REQUEST['code']) { $ch = curl_init(); $url = 'http://your-laravel-site-url/oauth/token'; $params = array( 'grant_type' => 'authorization_code', 'client_id' => '1', 'client_secret' => 'zMm0tQ9Cp7LbjK3QTgPy1pssoT1X0u7sg0YWUW01', 'redirect_uri' => 'http://localhost/oauth2_client/callback.php', 'code' => $_REQUEST['code'] ); curl_setopt($ch,CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $params_string = ''; if (is_array($params) && count($params)) { foreach($params as $key=>$value) { $params_string .= $key.'='.$value.'&'; } rtrim($params_string, '&'); curl_setopt($ch,CURLOPT_POST, count($params)); curl_setopt($ch,CURLOPT_POSTFIELDS, $params_string); } $result = curl_exec($ch); curl_close($ch); $response = json_decode($result); // check if the response includes access_token if (isset($response->access_token) && $response->access_token) { // you would like to store the access_token in the session though... $access_token = $response->access_token; // use above token to make further api calls in this session or until the access token expires $ch = curl_init(); $url = 'http://your-laravel-site-url/api/user/get'; $header = array( 'Authorization: Bearer '. $access_token ); $query = http_build_query(array('uid' => '1')); curl_setopt($ch,CURLOPT_URL, $url . '?' . $query); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); $result = curl_exec($ch); curl_close($ch); $response = json_decode($result); var_dump($result); } else { // for some reason, the access_token was not available // debugging goes here } }
Again, make sure to adjust the URLs and client credentials according to your setup in the above file.
How It Works Altogether
In this section, we'll test it altogether from the perspective of an end user. As an end user, there are two applications in front of you:
- The first one is the Laravel application that you already have an account with. It holds your information that you could share with other third-party applications.
- The second one is the demo third-party client application,
auth_redirection.php
andcallback.php
, that wants to fetch your information from the Laravel application using the OAuth API.
The flow starts from the third-party client application. Go ahead and open the http://localhost/oauth2_client/auth_redirection.php URL in your browser, and that should redirect you to the Laravel application. If you're not already logged into the Laravel application, the application will ask you to do so in the first place.
Once the user is logged in, the application displays the authorization page.
If the user authorizes that request, the user will be redirected back to the third-party client application at http://localhost/oauth2_client/callback.php along with the code
as the GET
parameter that contains the authorization code.
Once the third-party application receives the authorization code, it could exchange that code with the Laravel application to get the access token. And that's exactly what it has done in the following snippet of the oauth2_client/callback.php
file.
$ch = curl_init(); $url = 'http://your-laravel-site-url/oauth/token'; $params = array( 'grant_type' => 'authorization_code', 'client_id' => '1', 'client_secret' => 'zMm0tQ9Cp7LbjK3QTgPy1pssoT1X0u7sg0YWUW01', 'redirect_uri' => 'http://localhost/oauth2_client/callback.php', 'code' => $_REQUEST['code'] ); curl_setopt($ch,CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $params_string = ''; if (is_array($params) && count($params)) { foreach($params as $key=>$value) { $params_string .= $key.'='.$value.'&'; } rtrim($params_string, '&'); curl_setopt($ch,CURLOPT_POST, count($params)); curl_setopt($ch,CURLOPT_POSTFIELDS, $params_string); } $result = curl_exec($ch); curl_close($ch); $response = json_decode($result);
Next, the third-party application checks the response of the CURL request to see if it contains a valid access token in the first place.
As soon as the third-party application gets the access token, it could use that token to make further API calls to request resources as needed from the Laravel application. Of course, the access token needs to be passed in every request that's requesting resources from the Laravel application.
We've tried to mimic the use-case in that the third-party application wants to access the user information from the Laravel application. And we've already built an API endpoint, http://your-laravel-site-url/api/user/get, in the Laravel application that facilitates it.
// check if the response includes access_token if (isset($response->access_token) && $response->access_token) { // you would like to store the access_token in the session though... $access_token = $response->access_token; // use above token to make further api calls in this session or until the access token expires $ch = curl_init(); $url = 'http://your-laravel-site-url/api/user/get'; $header = array( 'Authorization: Bearer '. $access_token ); $query = http_build_query(array('uid' => '1')); curl_setopt($ch,CURLOPT_URL, $url . '?' . $query); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); $result = curl_exec($ch); curl_close($ch); $response = json_decode($result); var_dump($result); }
So that's the complete flow of how you're supposed to consume the OAuth2 APIs in Laravel.
And with that, we’ve reached the end of this article.
Conclusion
Today, we explored the Passport library in Laravel, which allows us to set up an OAuth2 server in an application very easily.
For those of you who are either just getting started with Laravel or looking to expand your knowledge, site, or application with extensions, we have a variety of things you can study in Envato Market.
Don't hesitate to share your thoughts and queries using the feed below!
No comments:
Post a Comment