سرفصل‌های آموزشی
آموزش OAuth و Laravel Passport
Token Scopes

Token Scopes

Scope‌ها به کلاینت های API اجازه می‌دهند هنگام درخواست مجوز برای دسترسی به یک حساب، مجموعه خاصی از مجوزها را درخواست کنند. به عنوان مثال، اگر در حال ایجاد یک برنامه تجارت الکترونیک هستید، نیازی نیست که همه کلاینت‌های API  بتوانند سفارشی را ثبت کنند در عوض ممکن است اجازه دسترسی به وضعیت حمل و نقل سفارش را به تعدادی از کلاینت‌ها بدهید. به عبارت دیگر، Scope‌ها اجازه می‌دهند تا کاربران برنامه شما، عملیات یک برنامه شخص ثالث یا third-party را محدود کنند.  

نکته: بین کنترل دسترسی Role-Based با OAuth2 Scopes تفاوتی وجود دارد. بزرگ‌ترین تفاوت بین این دو، زمینه ای است که به آن اِعمال می‌شوند. کنترل دسترسی Role-Based  (RBAC) ، دسترسی یک کاربر را هنگام استفاده مستقیم از برنامه وب کنترل می‌کند، در حالی کهOAuth2 Scopes ، دسترسی به منابع API  را برای یک کلاینت خارجی به نمایندگی از یک کاربر کنترل می‌کند.

برای استفاده از Scope‌ها در پاسپورت نیاز است در دو بخش زیر کارهایی را انجام دهیم.

1-     سرور OAuth

·         تعریف Scope‌ها در سرور پاسپورت

·         انجام تنظیمات برای بررسی Scope موجود در یک درخواست برنامه کلاینت

·         تنظیم مسیر api

2-     برنامه کلاینت

·         تنظیم کردن مسیر‌ها

تنظیمات و پیاده سازی سرور OAuth

ابتدا باید Scope های یک API را در سرور OAuth خود تعریف کنیم. برای تعریف از متد Passport::tokensCan استفاده می‌کنیم. این متد حاوی یک آرایه از نام Scope‌ها و توضیحات آن‌ها می‌باشد. توضیحات scope می‌تواند هر چیزی که می‌خواهیم در صفحه تایید مجوز برای کاربران نمایش داده شود، باشد. کلاسapp/Providers/AuthServiceProvider  را باز کرده و در متد Boot آن کد زیر را قرار می‌دهیم:

// …

public function boot()
{
    $this->registerPolicies();

    Passport::routes();

    Passport::tokensCan([
    'read-tasks' => 'Read all task',
    'add-task' => 'Add new task',
    'edit-task' => 'Edit a task',
    'delete-task' => 'Delete a task',
    'done-tasks' => 'Done a task',
]);
}


اگر یک برنامه کلاینت درخواست Scope خاصی نداشته باشد، می‌توانیم سرور پاسپورت را طوری پیکربندی کنیم تا یک Scope پیش‌فرض را به token وصل کند. این کار با استفاده از متدsetDefaultScope  انجام می‌شود. برای این کار تکه کد زیر را به متد boot از کلاس AuthServiceProvider   بعد از متد Passport::tokensCan اضافه می‌کنیم:  

// …

Passport::setDefaultScope([
    'read-tasks'
]);

بعد از این که Scope های خود را تعریف کردیم، وارد بخش تنظیمات شده و به قسمت Create New Token می‌رویم. مانند شکل زیر در پنجره‌ای که باز می‌شود، Scope هایی را که تعریف کردیم، می‌بینیم.

حال باید بخشی که مربوط به بررسی Scopes در یک درخواست برنامه کلاینت است را پیاده سازی کنیم.

پاسپورت دو middleware برای بررسی scope‌های یک درخواست دارد. برای استفاده از این middleware ها، باید آن‌ها را به برنامه اضافه کنیم. فایل app/Http/Kernel.php را باز کرده و پروپرتی با نام  $routeMiddleware را پیدا می‌کنیم. سپس تکه کد زیر را به آن اضافه می‌کنیم. در بخش‌های بعد خواهیم دید که در درخواست یک برنامه کلاینت چه‌طور باید Scope را درج کنیم.

// …

protected $routeMiddleware = [
     // …

    'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,
    'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
];

 

 

برای بررسی scope های یک درخواست در مسیر ها، چند روش وجود دارد که قبل از انتخاب یک روش و پیاده سازی، آن‌ها را توضیح می‌دهیم:

1-     بررسی همه Scope‌ها: در این روش، middleware با نام scopes را به همراه یک لیست از همه scope‌ها به مسیر اختصاص می‌دهیم. در این حالت، access token ای که به آن مسیر درخواست داده، باید برای همه scope‌های موجود در لیست بررسی و تایید شود.

Route::get('/orders', function () {
    // Access token has both "check-status" and "place-orders" scopes...
})->middleware(['auth:api', 'scopes:read-tasks,add-task']);

 

2-     بررسی هر Scope: در این روش middleware به نام scope را به همراه یک لیست از همه scope‌ها به مسیر اختصاص می‌دهیم. در این حالت، access token ای که به آن مسیر درخواست داده، باید حداقل یکی از scope هایی که در ورودی لیست کرده‌ایم را داشته باشد.

Route::get('/orders', function () {
    // Access token has either "check-status" or "place-orders" scope...
})->middleware(['auth:api', 'scope: read-tasks,add-task']);

 

3-     بررسی scope‌ها در یک instance توکن: در این روش با استفاده از متد tokenCan می‌توانیم روی شی مدل user‌ای که احراز هویت شده، وجود scope خاصی را در توکن آن بررسی کنیم.

use Illuminate\Http\Request;

Route::get('/orders', function (Request $request) {
    if ($request->user()->tokenCan('read-tasks')) {
        //
    }
});

 

حالا که با روش‌های بررسی Scope‌ها آشنا شدیم، بر اساس یکی از آن‌ها کار خود را ادامه می‌دهیم. ما از روش سوم برای نوشتن مسیر api خود استفاده می‌کنیم. ناگفته نماند که متد‌های دیگری در رابطه با Scope ها و کار با آن‌ها نیز وجود دارد که در فصل مباحث پیشرفته بیان می‌شوند. 
فایل /routes/api.php را باز کرده و مسیر زیر را در آن می‌نویسیم. همان‌طور که مشاهده می‌کنید در این مسیر علاوه بر middleware اولیه (auth:api)، با استفاده از متد tokenCan مقدار scope نیز بررسی شده است.

 

Route::middleware(['auth:api'])->get('/todos', function (Request $request) {
    if ($request->user()->tokenCan('read-tasks')) {
        return $request->user()->tasks;
    } else {
        return response()->json(['error' => 'Unauthenticated']);
    }
});

پیاده سازی در سمت برنامه کلاینت (consumer)

هنگام درخواست یک access token با استفاده از authorization code grant، برنامه کلاینت باید scope مورد نظر خود را در Query String، مشخص کند. پارامتر scope باید یک لیست space-delimited از scope‌ها باشد که با فاصله از هم جدا شدند. در فصل قبل، برای برنامه consumer 3 مسیر ایجاد کرده بودیم. مسیر اول را به صورت زیر تغییر می‌دهیم:

// …

Route::get('/', function () {
    $query = http_build_query([
        'client_id' => 9,
        'redirect_uri' => 'http://127.0.0.1:8001/callback',
        'response_type' => 'code',
        'scope' => ['read-tasks'],
    ]);

    return redirect('http://127.0.0.1:8000/oauth/authorize?'.$query);
});

// …

 
 

همان‌طور که در کد بالا دیده می‌شود، ما برای پارامتر scope، مقدار مشخصی را قرار داده‌ایم.

 

تست استفاده از Scopes در درخواست برنامه کلاینت


در برنامه OAuth، وارد بخش تنظیمات شده و برای برنامه کلاینت consumer یک توکن با دسترسی خواندن تَسک‌ها مشابه شکل زیر ایجاد می‌کنیم.

 

 

مسیر http://127.0.0.1:8001 مربوط به برنامه consumer را در مرورگر باز می‌کنیم. به صفحه‌ای مانند شکل زیر منتقل می‌شویم. همان‌طور که مشاهده می‌کنید، scopes یا بهتر است بگوییم نوع دسترسی‌هایی که برنامه کلاینت در درخواست خود دارد، مشخص شده است.

  

از آن جایی که در درخواست کلاینت، scope در نظر گرفته شده با scope تعریف شده برای کلاینت در سرور OAuth یکی می‌باشد (در فایل api.php نیز همین scope بررسی شده است.)، با کلیک بر روی Authorize، دسترسی تایید شده و می‌توانیم تَسک‌های کاربر را مانند شکل زیر ببینیم.

 

 حال فرض کنید برنامه کلاینت، درخواست خود را با scope دیگری ارسال کند. مانند تکه کد زیر:

// …

Route::get('/', function () {
    $query = http_build_query([
        'client_id' => 9,
        'redirect_uri' => 'http://127.0.0.1:8001/callback',
        'response_type' => 'code',
        'scope' => ['edit-task'],
    ]);

    return redirect('http://127.0.0.1:8000/oauth/authorize?'.$query);
});

// …

 

مجددا مسیر http://127.0.0.1:8001 مربوط به برنامه consumer را در مرورگر باز می‌کنیم. همان‌طور که در شکل بعدی دیده می‌شود، نوع scope درخواستی کلاینت تغییر کرده است:

 

 

از آن جایی که scope درخواستی با scope در نظر گرفته شده برای کلاینت در سرور OAuth یکی نمی‌باشد، با کلیک بر روی Authorize با پیغامی مشابه شکل زیر مواجه می‌شویم:

 

این پیغام به معنی این است که scope درخواست شده توسط برنامه کلاینت در سرور ما تایید نشده است و همان پیغامی است که ما در بررسی Scope در فایل api.php نوشته بودیم.

online-support-icon