web-dev-qa-db-ja.com

Laravel Passport Scopes

laravel= scopes部分については少し混乱しています。

ユーザーモデルとテーブルがあります。

ユーザー、ユーザー、管理者の役割をユーザーに割り当てるにはどうすればよいですか。

vue and and laravel api APIバックエンド。私は https://laravel.com/docs/ 5.3/passport#consuming-your-api-with-javascript

    Passport::tokensCan([
        'user' => 'User',
        'customer' => 'Customer',
        'admin' => 'Admin',
    ]);

どのユーザーモデルにどのスコープを割り当てることができますか?

または、スコープはロールと同じではありませんか?

これをどのように実装しますか?

前もって感謝します!

28
Jeroen Herczeg

または、スコープはロールと同じではありませんか?

2つの最大の違いは、適用されるコンテキストです。ロールベースのアクセス制御(RBAC)は、Webアプリケーションを直接使用する場合のユーザーのアクセス制御を管理します(= /// =)。Oauth-2スコープは、外部クライアントユーザーに代わって。

どのユーザーモデルにどのスコープを割り当てることができますか?

一般的なOauthフローでは、ユーザー(リソース所有者)が、クライアントに代わって実行できることと実行できないことについてクライアントを承認するように要求されます。これらは、scopeと呼ばれるものです成功した​​認証では、クライアントによって要求されているスコープは生成されたトークンに割り当てられますユーザー自体。

選択したOauth許可フローに応じて、クライアントは要求にスコープを含める必要があります。許可コード許可フローでは、ユーザーを許可ページにリダイレクトするときにHTTP GETクエリパラメーターにスコープを含める必要があります。一方、パスワード許可フローでは、トークンを要求するためにHTTP POST bodyパラメーターにスコープを含める必要があります。

これをどのように実装しますか?

これは、事前にlaravel/passportセットアップを完了したことを前提とした、パスワード付与フローの例です

管理者ロールとユーザーロールの両方のスコープを定義します。たとえば、管理者は注文を管理でき、ユーザーはそれを読むだけです。

// in AuthServiceProvider boot
Passport::tokensCan([
    'manage-order' => 'Manage order scope'
    'read-only-order' => 'Read only order scope'
]);

RESTコントローラーを準備します

// in controller
namespace App\Http\Controllers;

class OrderController extends Controller
{   
    public function index(Request $request)
    {
        // allow listing all order only for token with manage order scope
    }

    public function store(Request $request)
    {
        // allow storing a newly created order in storage for token with manage order scope
    }

    public function show($id)
    {
        // allow displaying the order for token with both manage and read only scope
    }
}

APIガードとスコープでルートを割り当てます

// in api.php
Route::get('/api/orders', 'OrderController@index')
    ->middleware(['auth:api', 'scopes:manage-order']);
Route::post('/api/orders', 'OrderController@store')
    ->middleware(['auth:api', 'scopes:manage-order']);
Route::get('/api/orders/{id}', 'OrderController@show')
    ->middleware(['auth:api', 'scopes:manage-order, read-only-order']);

トークンを発行するときは、最初にユーザーロールを確認し、そのロールに基づいてスコープを付与します。これを実現するには、AuthenticatesUsers特性を使用してログインエンドポイントを提供する追加のコントローラーが必要です。

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

class ApiLoginController extends Controller
{
    use AuthenticatesUsers;

    protected function authenticated(Request $request, $user)
    {               
        // implement your user role retrieval logic, for example retrieve from `roles` database table
        $role = $user->checkRole();

        // grant scopes based on the role that we get previously
        if ($role == 'admin') {
            $request->request->add([
                'scope' => 'manage-order' // grant manage order scope for user with admin role
            ]);
        } else {
            $request->request->add([
                'scope' => 'read-only-order' // read-only order scope for other user role
            ]);
        }

        // forward the request to the oauth token request endpoint
        $tokenRequest = Request::create(
            '/oauth/token',
            'post'
        );
        return Route::dispatch($tokenRequest);
    }
}

APIログインエンドポイントのルートを追加

//in api.php
Route::group('namespace' => 'Auth', function () {
    Route::post('login', 'ApiLoginController@login');
});

/ oauth/tokenルートへのPOSTを行う代わりに、以前に提供したAPIログインエンドポイントへのPOST

// from client application
$http = new GuzzleHttp\Client;

$response = $http->post('http://your-app.com/api/login', [
    'form_params' => [
        'grant_type' => 'password',
        'client_id' => 'client-id',
        'client_secret' => 'client-secret',
        'username' => '[email protected]',
        'password' => 'my-password',
    ],
]);

return json_decode((string) $response->getBody(), true);

認証が成功すると、クライアントアプリケーションに対して、前に定義したスコープに基づいたaccess_tokenおよびrefresh_tokenが発行されます。それをどこかに保管し、APIにリクエストを行うたびにHTTPヘッダーにトークンを含めます。

// from client application
$response = $client->request('GET', '/api/my/index', [
    'headers' => [
        'Accept' => 'application/json',
        'Authorization' => 'Bearer '.$accessToken,
    ],
]);

APIが返されるようになりました

{"error":"unauthenticated"}

制限されたエンドポイントを使用するために、権限のないトークンが使用される場合。

59
Raymond Lagonda

Raymond Lagondaのレスポンスを実装すると、次の点に注意するだけで非常にうまく機能します。 ApiLoginControllerのAuthenticatesUsers特性からいくつかのメソッドをオーバーライドする必要があります。

    /**
     * Send the response after the user was authenticated.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    protected function sendLoginResponse(Request $request)
    {
        // $request->session()->regenerate(); // coment this becose api routes with passport failed here.

        $this->clearLoginAttempts($request);

        return $this->authenticated($request, $this->guard()->user())
                ?: response()->json(["status"=>"error", "message"=>"Some error for failes authenticated method"]);

    }

    /**
     * Get the failed login response instance.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\RedirectResponse
     */
    protected function sendFailedLoginResponse(Request $request)
    {
        return response()->json([
                                "status"=>"error", 
                                "message"=>"Autentication Error", 
                                "data"=>[
                                    "errors"=>[
                                        $this->username() => Lang::get('auth.failed'),
                                    ]
                                ]
                            ]);
    }

Login:ユーザー名フィールドをカスタムユーザー名フィールドに変更した場合:例:e_mail。 LoginControllerのように、ユーザー名メソッドを改良する必要があります。また、メソッドを再定義して編集する必要があります:validateLogin、attemptLogin、資格情報。ログインが検証されると、リクエストはパスポートに転送され、ユーザー名と呼ばれる必要があるためです。

4

Laravel 5.5 with Sentinelの場合、@ RaymondLagondaソリューションを使用してこれを機能させることができましたが、Sentinelなしでも機能するはずです。

このソリューションでは、いくつかのクラスメソッドをオーバーライドする必要があり(したがって、今後の更新のために留意してください)、APIルートに保護を追加します(たとえば、client_secretを公開しません)。

最初のステップは、構成関数を追加するためにApiLoginControllerを変更することです。

public function __construct(Request $request){
        $oauth_client_id = env('PASSPORT_CLIENT_ID');
        $oauth_client = OauthClients::findOrFail($oauth_client_id);

        $request->request->add([
            'email' => $request->username,
            'client_id' => $oauth_client_id,
            'client_secret' => $oauth_client->secret]);
    }

この例では、.envでvar( 'PASSPORT_CLIENT_ID')を定義し、OauthClientsモデルを作成する必要がありますが、適切なテスト値をここに入力することでこれを安全にスキップできます。

注目すべきことの1つは、$request->email値をユーザー名に設定していることです。これは、Oauth2の規則に準拠するためです。

2番目のステップは、Session storage not setなどのエラーを引き起こしているsendLoginResponseメソッドをオーバーライドすることです。ここではセッションは必要ありません。これはapiであるためです。

protected function sendLoginResponse(Request $request)
    {
//        $request->session()->regenerate();

        $this->clearLoginAttempts($request);

        return $this->authenticated($request, $this->guard()->user())
            ?: redirect()->intended($this->redirectPath());
    }

3番目のステップは、@ RaymondLagondaによって提案された認証済みメソッドを変更することです。ここに独自のロジックを記述し、特にスコープを構成する必要があります。

最後のステップ(Sentinelを使用している場合)は、AuthServiceProviderを変更することです。追加する

$this->app->rebinding('request', function ($app, $request) {
            $request->setUserResolver(function () use ($app) {
                 return \Auth::user();
//                return $app['sentinel']->getUser();
            });
        });

ブートメソッドの$this->registerPolicies();の直後。

これらの手順の後、ユーザー名( 'この実装では常にこれは電子メールになります')、パスワード、およびgrant_type = 'password'を提供することにより、APIを機能させることができます。

この時点で、ミドルウェアスコープscopes:...またはscope:...に追加して、ルートを保護できます。

私はそれが本当に役立つことを願っています...

3
Bart

これは少し遅いことはわかっていますが、WebミドルウェアでCreateFreshApiTokenを使用してSPAでバックエンドAPIを使用している場合は、単に「管理」ミドルウェアをアプリに追加できます。

php artisan make:middleware Admin

次に、\App\Http\Middleware\Adminで以下を実行します。

public function handle($request, Closure $next)
{
    if (Auth::user()->role() !== 'admin') {
        return response(json_encode(['error' => 'Unauthorised']), 401)
            ->header('Content-Type', 'text/json');
    }

    return $next($request);
}

roleメソッドを\App\Userに追加して、ユーザーロールを取得していることを確認してください。

これで、ミドルウェアをapp\Http\Kernel.php$routeMiddlewareに登録するだけで、次のようになります。

protected $routeMiddleware = [
    // Other Middleware
    'admin' => \App\Http\Middleware\Admin::class,
];

routes/api.phpでルートに追加します

Route::middleware(['auth:api','admin'])->get('/customers','Api\CustomersController@index');

これで、許可なくAPIにアクセスしようとすると、「401 Unauthorized」エラーが表示されます。このエラーはアプリで確認して処理できます。

2
craig_h

@RaymondLagondaソリューションを使用。クラススコープが見つからないというエラーが発生する場合は、次のミドルウェアを$routeMiddlewareあなたのプロパティapp/Http/Kernel.phpファイル:

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

また、エラーが発生している場合はType error: Too few arguments to function$user以下のようなリクエストから。

(ロールの管理にlaratrustを使用しています)

public function login(Request $request)
{

    $email = $request->input('username');
    $user = User::where('email','=',$email)->first();

    if($user && $user->hasRole('admin')){
        $request->request->add([
            'scope' => 'manage-everything'
        ]);
    }else{
        return response()->json(['message' => 'Unauthorized'],403);
    }

    $tokenRequest = Request::create(
      '/oauth/token',
      'post'
    );

    return Route::dispatch($tokenRequest);

}
1
Amal Ajith

このおかげで、この質問はしばらく頭を悩ませていました!レイモンド・ラゴンダのソリューションを少しカスタマイズして、Laravel 5.6、組み込みのレート制限を使用し、単一のthirdpartyクライアントを使用する(または必要に応じてよりカスタム化する)、各ユーザーに権限のリスト(スコープ)を引き続き提供します。

  • Laravel Passport password grantを使用して、Oauth flow
  • さまざまなユーザーにロール(スコープ)を設定する機能を提供します
  • クライアントIDやクライアントシークレットを公開/解放しないでください。ユーザーのユーザー名(メール)とパスワードのみ、ほとんど パスワード 付与、クライアント/付与物を除く

下の例

routes/api.php

    Route::group(['namespace' => 'ThirdParty', 'prefix' => 'thirdparty'], function () {
        Route::post('login', 'ApiLoginController@login');
    });

ThirdParty/ApiLoginController.php

<?php

namespace App\Http\Controllers\ThirdParty;

use Hash;
use App\User;
use App\ThirdParty;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;

class ApiLoginController extends Controller
{
    use AuthenticatesUsers;

    /**
     * Thirdparty login method to handle different
     * clients logging in for different reasons,
     * we assign each third party user scopes
     * to assign to their token, so they
     * can perform different API tasks
     * with the same token.
     *
     * @param  Request $request
     * @return Illuminate\Http\Response
     */
    protected function login(Request $request)
    {
        if ($this->hasTooManyLoginAttempts($request)) {
            $this->fireLockoutEvent($request);

            return $this->sendLockoutResponse($request);
        }

        $user = $this->validateUserLogin($request);

        $client = ThirdParty::where(['id' => config('thirdparties.client_id')])->first();

        $request->request->add([
            'scope' => $user->scopes,
            'grant_type' => 'password',
            'client_id' => $client->id,
            'client_secret' => $client->secret
        ]);

        return Route::dispatch(
            Request::create('/oauth/token', 'post')
        );
    }

    /**
     * Validate the users login, checking
     * their username/password
     *
     * @param  Request $request
     * @return User
     */
    public function validateUserLogin($request)
    {
        $this->incrementLoginAttempts($request);

        $username = $request->username;
        $password = $request->password;

        $user = User::where(['email' => $username])->first();

        abort_unless($user, 401, 'Incorrect email/password.');

        $user->setVisible(['password']);

        abort_unless(Hash::check($password, $user->password), 401, 'Incorrect email/password.');

        return $user;
    }
}

config/thirdparties.php

<?php

return [
    'client_id' => env('THIRDPARTY_CLIENT_ID', null),
];

ThirdParty.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class ThirdParty extends Model
{
    protected $table = 'oauth_clients';
}

.env

## THIRDPARTIES
THIRDPARTY_CLIENT_ID=3

php artisan make:migration add_scope_to_users_table --table = users

        // up
        Schema::table('users', function (Blueprint $table) {
            $table->text('scopes')->nullable()->after('api_access');
        });
        // down
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('scopes');
        });

(注意: api_accessは、ユーザーがアプリのWebサイト/フロントエンド部分にログインしてダッシュボード/レコードなどを表示できるかどうかを決定するフラグです。

routes/api.php

Route::group(['middleware' => ['auth.client:YOUR_SCOPE_HERE', 'throttle:60,1']], function () {
    ...routes...
});

MySQL-ユーザーの範囲

INSERT INTO `users` (`id`, `created_at`, `updated_at`, `name`, `email`, `password`, `remember_token`, `api_access`, `scopes`)
VALUES
    (5, '2019-03-19 19:27:08', '2019-03-19 19:27:08', '', '[email protected]', 'YOUR_HASHED_PASSWORD', NULL, 1, 'YOUR_SCOPE_HERE ANOTHER_SCOPE_HERE');

MySQL-ThirdParty Oauthクライアント

INSERT INTO `oauth_clients` (`id`, `user_id`, `name`, `secret`, `redirect`, `personal_access_client`, `password_client`, `revoked`, `created_at`, `updated_at`)
VALUES
    (3, NULL, 'Thirdparty Password Grant Client', 'YOUR_SECRET', 'http://localhost', 0, 1, 0, '2019-03-19 19:12:37', '2019-03-19 19:12:37');

cURL-ログイン/トークンのリクエスト

curl -X POST \
  http://site.localhost/api/v1/thirdparty/login \
  -H 'Accept: application/json' \
  -H 'Accept-Charset: application/json' \
  -F [email protected] \
  -F password=YOUR_UNHASHED_PASSWORD
{
    "token_type": "Bearer",
    "expires_in": 604800,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciO...",
    "refresh_token": "def502008a75cd2cdd0dad086..."
}

寿命の長いaccess_token/refresh_tokenを通常どおり使用してください!

禁止されたスコープへのアクセス

{
    "data": {
        "errors": "Invalid scope(s) provided."
    },
    "meta": {
        "code": 403,
        "status": "FORBIDDEN"
    }
}
0
Kingsley