it-swarm.com.de

Laravel Passport-Bereiche

Ich bin etwas verwirrt über den Laravel-Bereich.

Ich habe ein Benutzermodell und eine Tabelle.

Wie kann ich einem Benutzer die Rolle Benutzer, Kunde und/oder Administrator zuweisen?.

Ich habe ein SPA mit Vue und Laravel Api Backend. Ich benutze https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript

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

Wie kann ich festlegen, welches Benutzermodell welchen Geltungsbereich hat?

Oder sind Bereiche nicht gleich Rollen?

Wie würdest du das umsetzen?

Danke im Voraus!

21
Jeroen Herczeg

Oder sind Bereiche nicht gleich Rollen?

Der größte Unterschied zwischen den beiden ist der Kontext, für den sie gelten. Die rollenbasierte Zugriffssteuerung (Role-based Access Control, RBAC) steuert die Zugriffssteuerung eines Benutzers bei Verwendung der Webanwendung direkt, während der Oauth-2-Bereich den Zugriff auf die API-Ressourcen für einen externen Client im Namen eines Benutzers.

Wie kann ich festlegen, welches Benutzermodell welchen Geltungsbereich hat?

Im Allgemeinen wird ein Benutzer (als Ressourceneigentümer) aufgefordert, einen Client für Dinge zu autorisieren, die er in seinem Namen tun kann und nicht tun kann. Dies haben Sie als scope bezeichnet. Bei erfolgreiche Autorisierung wird der vom Client angeforderte Bereich dem generierten Token nicht dem Benutzer per se zugewiesen.

Abhängig davon, welchen Oauth-Genehmigungsfluss Sie auswählen, sollte der Client den Bereich in seine Anfrage einschließen. Beim Berechtigungscode-Genehmigungsfluss muss der Gültigkeitsbereich in den HTTP-GET-Abfrageparameter einbezogen werden, wenn der Benutzer auf die Berechtigungsseite umgeleitet wird, während der Gültigkeitsbereich für den Kennwortberechtigungsfluss im HTTP-Parameter POST enthalten sein muss, um ein Token anzufordern.

Wie würdest du das umsetzen?

Dies ist ein Beispiel für den Ablauf der Kennwortgewährung, wobei davon ausgegangen wird, dass Sie zuvor die Einstellung laravel/passport abgeschlossen haben

Definieren Sie Bereiche für die Administrator- und Benutzerrolle. Seien Sie beispielsweise so genau wie Sie können: admin kann order-order verwalten und der Benutzer kann es nur lesen.

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

Bereiten Sie den REST - Controller vor

// 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
    }
}

Weisen Sie die Route mit dem API-Schutz und dem Umfang zu

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

Wenn Sie ein Token ausstellen, überprüfen Sie zuerst die Benutzerrolle und geben Sie den Gültigkeitsbereich basierend auf dieser Rolle an. Um dies zu erreichen, benötigen wir einen zusätzlichen Controller, der die AuthenticatesUsers-Eigenschaft verwendet, um den Anmeldeendpunkt bereitzustellen.

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);
    }
}

Route für API-Login-Endpunkt hinzufügen

//in api.php
Route::group('namespace' => 'Auth', function () {
    Route::post('login', '[email protected]');
});

Anstatt POST nach/oauth/token routen, POST zum api-Login-Endpunkt, den wir zuvor bereitgestellt haben

// 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);

Bei erfolgreicher Autorisierung werden für die Client-Anwendung ein access_token und ein refresh_token basierend auf dem zuvor definierten Bereich ausgegeben. Behalten Sie das irgendwo und fügen Sie das Token in den HTTP-Header ein, wenn Sie eine Anforderung an die API stellen.

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

Die API sollte jetzt zurückkehren

{"error":"unauthenticated"}

immer dann, wenn ein Token mit Unterprivileg verwendet wird, um einen eingeschränkten Endpunkt zu verwenden.

43
Raymond Lagonda

Implementieren Sie die Raymond Lagonda-Antwort, und es funktioniert sehr gut, nur um auf Folgendes zu achten: Sie müssen einige Methoden von AuthenticatesUsers-Eigenschaften in ApiLoginController überschreiben:

    /**
     * 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'),
                                    ]
                                ]
                            ]);
    }

Wenn Sie das Feld login: username in ein benutzerdefiniertes Benutzername-Feld geändert haben, z. B. e_mail. Sie müssen die Methode für den Benutzernamen wie in Ihrem LoginController ..__ verfeinern. Außerdem müssen Sie die Methoden neu definieren und bearbeiten: validateLogin, tryLogin, Anmeldeinformationen, da nach der Überprüfung der Anmeldung die Anforderung an den Pass weitergeleitet wird und der Benutzername aufgerufen werden muss.

4

Ich habe es geschafft, dies mit @RaymondLagonda zu erreichen, für Laravel 5.5 mit Sentinel , aber es sollte auch ohne Sentinel funktionieren.

Für die Lösung müssen einige Klassenmethoden überschrieben werden (berücksichtigen Sie dies bitte für zukünftige Aktualisierungen) und fügen Sie einen gewissen Schutz für Ihre API-Routen hinzu (z. B. client_secret nicht).

Der erste Schritt besteht darin, Ihre ApiLoginController zu ändern, um die Konstruktfunktion hinzuzufügen:

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]);
    }

In diesem Beispiel müssen Sie var ('PASSPORT_CLIENT_ID') in Ihrer .env definieren und das OauthClients-Modell erstellen. Sie können dies jedoch überspringen, indem Sie hier die richtigen Testwerte angeben.

Zu beachten ist, dass wir $request->email value auf username setzen, nur um die Oauth2-Konvention einzuhalten.

Der zweite Schritt besteht darin, die sendLoginResponse-Methode zu überschreiben, die Fehler wie Session storage not set verursacht. Wir brauchen hier keine Sitzungen, da es sich um eine API handelt.

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

        $this->clearLoginAttempts($request);

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

Der dritte Schritt besteht darin, Ihre authentifizierten Methoden gemäß den Empfehlungen von @RaymondLagonda zu ändern. Sie müssen hier Ihre eigene Logik schreiben und insbesondere Ihre Bereiche konfigurieren.

Der letzte Schritt (falls Sie Sentinel verwenden) besteht darin, AuthServiceProvider zu ändern. Hinzufügen 

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

gleich nach $this->registerPolicies(); in der Boot-Methode.

Nach diesen Schritten sollten Sie in der Lage sein, Ihr API-System zum Laufen zu bringen, indem Sie den Benutzernamen ("Dies ist immer E-Mail in dieser Implementierung"), Kennwort und Zuschussart = "Kennwort" angeben.

An dieser Stelle können Sie den Middlewares-Bereichen scopes:... oder scope:... hinzufügen, um Ihre Routen zu schützen.

Ich hoffe, es wird wirklich helfen ...

2
Bart

Ich weiß, dass dies etwas spät ist, aber wenn Sie eine Backend-API in einem SPA verwenden, die CreateFreshApiToken in der Web-Middleware verwendet, können Sie Ihrer App einfach eine 'Admin'-Middleware hinzufügen:

php artisan make:middleware Admin

Führen Sie dann in \App\Http\Middleware\Admin Folgendes aus:

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);
}

Stellen Sie sicher, dass Sie die Methode role zu \App\User hinzugefügt haben, um die Benutzerrolle abzurufen.

Jetzt müssen Sie nur noch Ihre Middleware in app\Http\Kernel.php$routeMiddleware registrieren.

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

Und fügen Sie das zu Ihrer Route in routes/api.php hinzu.

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

Wenn Sie nun versuchen, ohne Erlaubnis auf das API zuzugreifen, erhalten Sie die Fehlermeldung "401 Unauthorized", die Sie in Ihrer App überprüfen und bearbeiten können.

1
craig_h

Mit der @ RaymondLagonda-Lösung. Wenn Sie einen Fehler erhalten, bei dem Klassenbereiche nicht gefunden werden, fügen Sie der Eigenschaft $routeMiddleware Ihrer app/Http/Kernel.php-Datei die folgende Middleware hinzu: 

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

Wenn Sie den Fehler Type error: Too few arguments to function erhalten, sollten Sie in der Lage sein, den $user aus der folgenden Anforderung zu erhalten. 

(Ich benutze Laratrust zum Verwalten von Rollen)

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

Vielen Dank dafür, diese Frage hat mich eine Weile beschäftigt! Ich nahm Raymond Lagondas Lösung und passte sie ein wenig an Laravel 5.6) an, indem ich die eingebaute Ratenbegrenzung verwendete und einen einzelnen thirdparty -Client verwendete (oder, falls erforderlich, kundenspezifischer vorging) Geben Sie dennoch jedem Benutzer eine Liste der Berechtigungen (Bereiche).

  • Verwendet Laravel Passport password Grant und folgt Oauth Flow
  • Ermöglicht das Festlegen von Rollen (Bereichen) für verschiedene Benutzer
  • geben Sie keine Kunden-ID oder kein Kundengeheimnis preis, nur den Benutzernamen (E-Mail) und das Passwort des Benutzers, so ziemlich das Passwort , abzüglich des Kunden-/Gewährungsmaterials

Beispiele unten

routes/api.php

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

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 Handwerker machen: Migration add_scope_to_users_table --table = Benutzer

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

(Hinweis: api_access ist ein Flag, das entscheidet, ob sich ein Benutzer auf der Website/im Frontend der App anmelden kann, um Dashboards/Datensätze usw. anzuzeigen.)

routes/api.php

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

MySQL - Benutzerumfänge

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 Client

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 - Anmelden/Anfordern eines Tokens

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..."
}

Verwenden Sie wie gewohnt langlebiges access_token/refresh_token!

Zugriff auf verbotenen Bereich

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