راه اندازی سرور OAuth2 با استفاده از لاراول پسپورت

لاراول یکی از فریمورک های مورد علاقه منه، نه فقط به خاطر اینکه بهترینه بلکه تجربه توسعه بهتری هم نسبت به فریمورک های دیگه ارائه میده.
لاراول 5.3 با تعدادی ویژگی های جدید مانند Passport, Scout, Notification و… اومده که کار توسعه دهنده رو واقعا آسون می کنن.

پسپورت یک پکیج لاراول است که OAuth2 رو بصورت آماده در اختیار توسعه دهنده قرار داده.
این پکیج بر روی کتابخانه OAuth 2.0 Server ساخته شده.
اگه قبلا تجربه استفاده از OAuth رو داشته بوده باشید پس حتما میدونید که پیاده سازی اون به درستی کار چندان آسونی نیست.

حال ما می تونیم پسپورت رو نصب کنیم و با مقداری پیکربندی، اپلیکیشن ما واسه استفاده از OAuth آماده است.
ولی قبل از اینکه بریم سر اصل مطلب، بهتره در مورد یکسری مفاهیم مربوط به OAuth بیشتر بدونیم.
مخصوصا در مورد گرنت و انواع آن.

زمانی که یک کلاینت بخواد به یک منبع محافظت شده از API سرور دسترسی داشته باشه، برای تایید هویت باید یک توکن دسترسی (Access Token) همراه درخواست ارائه بده.
در اینجا، گرنت راهی واسه گرفتن توکن دسترسی از سرور است.
به صورت آماده پنج نوع گرنت وجود دارن که چهار تاشون واسه گرفتن توکن دسترسی و یکیشون واسه تازه کردن یک توکن دسترسی از قبل گرفته شده است.
تعدادشون کم نیست، پس یکی یکی توضیحشون میدم:

Client credentials

این گرنت مناسبه واسه ارتباطات ماشین به ماشین که میتونه واسه کران جاب ها (یا همون Job توی لاراول) قابل استفاده باشه، جایی که انجام وظایف مورد نظر توسط یک API خواهد بود.

Authorization Code

استفاده از این گرنت واسه وقیته که بخوایم به توسعه دهنده های اپلیکیشن های شخص ثالث اجازه دسترسی به اطلاعات محافظت شده در داخل اپلیکیشن خودمون رو بدیم.
به این صورت که کلاینت برای دسترسی به داده های کاربر، باید اجازه دسترسی کاربر رو از ایشون بگیره.

اگه قبلا از سایت هایی مانند فیسبوک، توییتر، گوگل و… واسه لاگین کردن به وبسایت مورد نظرتون استفاده کردید پس حتما با این نوع گرنت آشنایی دارید.
بیشتر وبسایت های معروف این نوع گرنت رو پیاده سازی کردن.
برای مثال اگه کاربری بخواد روی وبسایت ما لاگین بکنه، ما کاربر رو به سایت گیت هاب ریدایرکت میکنیم و میگیم که ابتدا روی گیت هاب لاگین کن.
بعد از لاگین، گیت هاب از کاربر میپرسه که آیا اجازه میده که اپلیکیشن ما به یکسری از اطلاعات ایشون دسترسی داشته باشه یا خیر.
اگه تایید شد، پس کاربر همراه با یک کد مجوز ریدایرکت داده میشه به اپلیکیشن ما.
حال اپلیکیشن ما از کد مجوز واسه گرفتن توکن دسترسی از گیت هاب استفاده میکنه، که میشه ازش واسه گرفتن اطلاعات کاربر شامل ایمیل، نام کاربری و… استفاده کرد.

Password credentials

بهترین حالت استفاده از این گرنت برای کلاینت قابل اعتماد شخص اول است.
این گرنت واسه هر دوی اپلیکیشن موبایل و وب قابلیت استفاده رو داره، به طوری که لازم نیست کاربرانتون ریداریکت بشن به سرور OAuth2 برای گرفتن کد اعتبارسنجی.

Implicit grant

این گرنت مشابه گرنت authorization code است، البته با دو تفاوت مجزا.

اول اینکه کاربرد این گرنت برای اپلیشکن های وب تک صفحه ای است که نمیتونن کد مخفی کلاینت (یا client secret) رو امن نگه دران چونکه کدهای اپلیکیشن و حافظه به راحتی قابل دسترسی است.

دوم اینکه سرور اعتبارسنج به جای بازگشت دادن کد اعتبار سنجی که برای مبادله کردن با توکن دسترسی استفاده میشه، بصورت مستقیم همون توکن دسترسی رو بازگشت میده.

Resource Owner Password Credentials

منظور از Resource Owner همون کاربر شخص اول است.
این نوع گرنت واسه زمانی خوبه که ما با یک کلاینت مورد اعتماد سروکار داریم، مثل یک اپلیکشن موبایل برای وبسایت خودمون.
در این حالت، کلاینت گواهی های کاربر که جهت لاگین استفاده میشه رو میفرسته سمت سرور و سرور بصورت مستقیم یک توکن دسترسی به کلاینت میفرسته.

Refresh token

هر توکن دسترسی در نهایت منقضی میشه. با این حال تعدادی از گرنت ها یک تازه کننده توکن به سمت کلاینت میفرستن که واسه گرفتن توکن دسترسی جدید از سرور قابل استفاده است و همچنین نیازی به اجازه کاربر نداره.

این مقاله بصورت کوتاه شده و مرحله به مرحله برای طریقه استفاده از لاراول پسپورت است و به شما طریقه راه اندازی سریع سرور رو نشون میده، بدون وارد شدن به جزئیات مستندات لاراول. برای اطلاعات بیشتر به مستندات لاراول مراجعه کنید.

راه اندازی سرور OAuth2

برای اندازی سرور OAuth، ما ابتدا لاراول 5.3 یا بالاتر رو نصب می کنیم.

1
$ laravel new passport

گواهی های دیتابیس که توی env. هستن رو بروزرسانی میکنیم:

1
2
3
4
5
6
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=passport
DB_USERNAME=root
DB_PASSWORD=secret

بزارید سیستم احراز هویت رو هم داربست کنیم:

1
$ php artisan make:auth

حالا نوبت میرسه به نصب پکیج پسپورت:

1
$ composer require laravel/passport

بعد از نصب پکیج، ما باید سرویس دهنده پسپورت رو به آرایه Providers توی config/app.php اضافه کنیم:

1
Laravel\Passport\PassportServiceProvider::class,

حالا که سرویس دهنده پسپورت هم اضافه شد، وقتشه که جداول مربوط به پسپورت رو به دیتابیس اضافه کنیم.

1
$ php artisan migrate

بعد از اینکار ما باید دستور passport:install رو اجرا کنیم. این دستور کلید های رمزگذاری رو ایجاد میکنه که برای تولید توکن دسترسی امن قابل استفاده است. این دستور همچنین کلاینت های “personal access” و “password grant” رو ایجاد میکنه که برای ایجاد توکن دسترسی قابل استفاده است:

1
$ php artisan passport:install

بعد از اجرای دستور بالا، حال باید Laravel\Passport\HasApiTokens رو به مدل App/User اضافه کرد:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
}

بعد از اینکار، متد Passport::routes رو داخل متد boot در AuthServiceProvider صدا میزنیم:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
...
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
}
}

در آخر، در فایل پیکربندی config/auth.php مقدار گزینه driver مربوط به گارد api رو passport میزاریم:

1
2
3
4
5
6
7
8
9
10
11
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],

خودشه! حال ما با موفقیت یک سرور OAuth2 رو پیاده کردیم و الان آماده است واسه ایجاد اولین کلاینت برای وبسایت ما. ولی قبل از اینکه کلاینت رو بسازیم، باید اول یک کاربر رو روی وبسایت ثبت نام کنیم، که بتونیم کلاینت رو بهش اختصاص بدیم. بعد از ثبت نام کاربر، توی دایرکتوری پروژه دستور زیر رو اجرا میکنیم:

php artisan passport:client

با اینکار، به صورت پیشفرض یک گرانت از نوع “Authorization Code” ساخته میشه. ما باید یک نام واسه این کلاینت ارائه بدیم (My Third-Party App)، یک آدرس Callback و همچنین یک user_id واسه کاربری که این کلاینت بهش تعلق داره. آدرس Callback استفاده میشه واسه ریدایرکت کردن کاربران از سمت سرور به اپلیکیشن کلاینت بعد از اجازه دادن دسترسی کاربر به اپلیکیشن.

بعد از اتمام این مراحل، یک user_id و یک secret برگردانده میشه که واسه گرفتن توکن دسترسی قابل استفاده است.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/code/passport php artisan passport:client
Which user ID should the client be assigned to?:
> 1
What should we name the client?:
> Consumer
Where should we redirect the request after authorization? [http://localhost/auth/callback]:
> http://consumer.dev/auth/callback
New client created successfully.
Client ID: 1
Client secret: uQzjMhrxQwOhgvOCpjmnJxjnknblZ0j6pUN54hQr

حال برای تست سرو، ما میخوایم که یک پروژه لاراول جدید برای کلاینت ایجاد کنیم، که دو تا مسیر هم داره:

1
2
3
4
5
6
7
8
9
10
Route::get('/redirect', function () {
$query = http_build_query([
'client_id' => '1',
'redirect_uri' => 'http://consumer.dev/auth/callback',
'response_type' => 'code',
'scope' => '',
]);
return redirect('http://passport.dev/ouath/authorize?'.$query);
});

این مسیر کاربر رو به سرور OAuth2 ما ریدایرکت میکنه، که در حال حاضر روی آدرس http://passport.dev در حال اجراست. پارامترهای client_id وredirect_id و response_code از نیازمندی ها هستن.
پارامتر redirect_uri همون آدرسی هست که موقع اجرای دستور passport:client نوشتیم. در حالی که response_code به سرور میگه که ما میخوایم کد تاییدیه یا همون Authorization Code رو بگیریم.

حال سرور OAuth2 از کاربر میپرسه که آیا اجازه دسترسی به اپلیکیشن کلاینت داده شود یا خیر. اگر کاربر موافقت کرد، سرور کاربر رو همراه با کد تاییدیه به http://consumer.dev/auth/callback ریدایرکت میکنه و اگه موافقت نکرد، ایشون رو با خطای access_diend ریدایرکت میکنه.

زمانی که اپلیکیشن کلاینت به کد تاییدیه دسترسی پیدا کرد، میتونه یک درخواست بده برای توکن دسترسی:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Route::get('/auth/callback', function (Request $request) {
$http = new GuzzleHttp\Client;
$response = $http->post('http://passport.dev/oauth/token', [
'form_params' => [
'grant_type' => 'authorization_code',
'client_id' => '1',
'client_secret' => 'uQzjMhrxQwOhgvOCpjmnJxjnknblZ0j6pUN54hQr',
'redirect_uri' => 'http://consumer.dev/auth/callback',
'code' => $request->code,
],
]);
return json_decode((string) $response->getBody(), true);
});

اینجا ما مقدار پارامتر grant_type رو مساوی authorization_code میزاریم و redirect_uri باید با آدرسی که قبلا گذاشتیم یکی باشه. پارامتر code همون کد تاییدیه است که ما از سمت سرور OAuth2 میگیرم. اگه درخواست POST با موفقت انجام شد، پس باید صفت های access_token, refresh_token و expires_in رو برگردونه.

خوب بالاخره ما یک توکن دسترسی در اختیار داریم که میتونیم ازش واسه گرفتن اطلاعات کاربر استفاده کنیم. پس سریع در سمت سرور یک مسیر داخل routes/api.php میسازیم:

1
2
3
Route::get('/user/{user}', function (App\User $user) {
return $user->email;
})->middleware('auth:api');

پس میتونیم به این مسیر به صورت زیر دسترسی داشته باشیم. شما میتونید از هر دوی postman یا curl برای ارسال درخواست HTTP استفاده کنید:

1
curl -H "Authorization: Bearer long_access_token_string" http://consumer.dev/user/1

تا اینجای کار، ما تونستیم با استفاده از گرنت Authorization code یک توکن دسترسی رو با موفقیت دریافت کنیم. ولی اگه ما یک کلاینت شخص اول داشته باشیم، مثل اپلیکیشن موبایل اونوقت میشه از پسورد گرنت توکن استفاده کنیم که بالا در موردش توضیح دادم.

برای ایجاد یک کلاینت پسورد گرنت از دستور passport:client همراه با گزینه passport– استفاده میکنیم. ولی چون ما قبلا دستور passport:install رو زدیم پس نیازی به اجرای این دستور نیست.

1
php artisan passport:client --password

بعد از اینکه یک کلاینت پسورد گرنت ساختیم، میشه از طریق جدول oauth_clients در دیتابیس به مقادیر آن کلاینت دسترسی داشت. حال برای گرفتن توکن دسترسی میتونیم یک درخواست POST به مسیر oauth/token همراه با ایمیل و رمز عبور کاربر ارسال کنیم. اگه درخواست با موفقیت ارسال شد، پس ما یک access_token به همراه refresh_token دریافت میکنیم.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Route::get('/auth/token', function () {
$http = new GuzzleHttp\Client;
$response = $http->post('http://passport.dev/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => '3',
'client_secret' => 'iMG6MLRHrhovf36WptrPSse3OvWMVLtJYKX8C7QR',
'username' => 'me@miranrasulian.com',
'password' => 'secret',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);
});

حال اگه آدرس http://consumer.dev/auth/token رو در مرورگر، curl یا postman وارد کنیم، باید یک access_token به همراه refresh_token دریافت کنیم.

Proudly hosted on GitLab pages
© مهران رسولیان