español - Alcance del pasaporte Laravel
laravel passport tutorial (6)
Estoy un poco confundido en la parte de laravel scopes.
Tengo un modelo de usuario y una tabla.
¿Cómo puedo asignar a un usuario el rol de usuario, cliente y / o administrador?
Tengo un SPA con vue y laravel api backend. Yo uso https://laravel.com/docs/5.3/passport#consuming-your-api-with-javascript
Passport::tokensCan([
''user'' => ''User'',
''customer'' => ''Customer'',
''admin'' => ''Admin'',
]);
¿Cómo puedo asignar qué modelo de usuario tiene qué alcance (s)?
¿O los ámbitos no son lo mismo que los roles?
¿Cómo implementaría esto?
¡Gracias por adelantado!
¿O los ámbitos no son lo mismo que los roles?
La mayor diferencia entre los dos es el contexto al que se aplican. El control de acceso basado en roles (RBAC) gobierna el control de acceso de un usuario cuando usa la aplicación web directamente , mientras que el alcance Oauth-2 gobierna el acceso a los recursos API para un cliente externo en nombre de un usuario.
¿Cómo puedo asignar qué modelo de usuario tiene qué alcance (s)?
En general, el flujo de Oauth, se solicita a un usuario (como propietario de un recurso) que autorice a un cliente sobre las cosas que puede y no puede hacer en su nombre, esto es lo que usted llama alcance . En caso de autorización exitosa, el alcance solicitado por el cliente se asignará al token generado, no al usuario per se.
Según el flujo de concesión de Oauth que elija, el cliente debe incluir el alcance en su solicitud. En el flujo de concesión del código de autorización, el alcance debe incluirse en el parámetro de consulta HTTP GET al redirigir al usuario a la página de autorización, mientras que en el flujo de concesión de contraseña, el alcance debe incluirse en el parámetro del cuerpo HTTP POST para solicitar un token.
¿Cómo implementaría esto?
Este es un ejemplo con el flujo de concesión de contraseña, suponiendo que haya completado la configuración de laravel/passport antemano
Definir ámbitos para el rol de administrador y usuario. Sea lo más específico posible, por ejemplo: el administrador puede gestionar el pedido y el usuario solo lo lee.
// in AuthServiceProvider boot
Passport::tokensCan([
''manage-order'' => ''Manage order scope''
''read-only-order'' => ''Read only order scope''
]);
Prepare el controlador 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
}
}
Asigne la ruta con la protección de la API y el alcance
// 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'']);
Y al emitir un token, verifique primero el rol del usuario y otorgue el alcance en función de ese rol. Para lograr esto, necesitamos un controlador adicional que use el rasgo AuthenticatesUsers para proporcionar un punto final de inicio de sesión.
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);
}
}
Agregar ruta para el punto final de inicio de sesión de API
//in api.php
Route::group(''namespace'' => ''Auth'', function () {
Route::post(''login'', ''ApiLoginController@login'');
});
En lugar de hacer POST a / oauth / token route, POST al punto final de inicio de sesión de API que proporcionamos antes
// 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);
Tras una autorización exitosa, se emitirá un access_token y un refresh_token basado en el alcance que definimos antes para la aplicación cliente. Guarde eso en algún lugar e incluya el token en el encabezado HTTP cada vez que realice una solicitud a la API.
// from client application
$response = $client->request(''GET'', ''/api/my/index'', [
''headers'' => [
''Accept'' => ''application/json'',
''Authorization'' => ''Bearer ''.$accessToken,
],
]);
La API ahora debería volver
{"error":"unauthenticated"}
siempre que se use un token con privilegios inferiores para consumir puntos finales restringidos.
¡Gracias por esto, esta pregunta estuvo en mi mente por un tiempo!
Tomé la solución de Raymond Lagonda y la personalicé un poco para Laravel 5.6, usando la limitación de velocidad incorporada, usando un solo cliente de
thirdparty
(o más personalizado si es necesario), mientras le daba a cada usuario una lista de permisos (ámbitos).
-
Utiliza la concesión de
password
Laravel Passport y sigue el flujo de Oauth - Le brinda la capacidad de establecer roles (ámbitos) para diferentes usuarios
- no exponga / libere la identificación del cliente o el secreto del cliente, solo el nombre de usuario (correo electrónico) y la contraseña del usuario, más o menos una concesión de password , menos las cosas del cliente / concesión
Ejemplos en la parte inferior
rutas / 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 artesanal make: migración 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'');
});
(nota:
api_access
es un indicador que decide si un usuario puede iniciar sesión en el sitio web / parte frontend de la aplicación, para ver paneles / registros, etc.),
rutas / api.php
Route::group([''middleware'' => [''auth.client:YOUR_SCOPE_HERE'', ''throttle:60,1'']], function () {
...routes...
});
MySQL - Ámbitos de usuario
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: iniciar sesión / solicitar un token
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..."
}
¡Use access_token / refresh_token de larga duración como de costumbre!
Acceder al alcance prohibido
{
"data": {
"errors": "Invalid scope(s) provided."
},
"meta": {
"code": 403,
"status": "FORBIDDEN"
}
}
Con la solución @RaymondLagonda.
Si obtiene un error de alcance de clase no encontrado, agregue el siguiente middleware a la propiedad
$routeMiddleware
de su archivo
app/Http/Kernel.php
:
''scopes'' => /Laravel/Passport/Http/Middleware/CheckScopes::class,
''scope'' => /Laravel/Passport/Http/Middleware/CheckForAnyScope::class,`
Además, si recibe el error Error de
Type error: Too few arguments to function
, debería poder obtener el
$user
de la solicitud como se muestra a continuación.
(Estoy usando laratrust para gestionar roles)
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);
}
Implemente la respuesta Raymond Lagonda y funciona muy bien, solo para tener cuidado con lo siguiente. Debe anular algunos métodos de los rasgos AuthenticatesUsers en ApiLoginController:
/**
* 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''),
]
]
]);
}
Si cambió el campo de inicio de sesión: nombre de usuario a un campo de nombre de usuario personalizado, por ejemplo: correo electrónico. Debe refinar el método de nombre de usuario como en su LoginController. También debe redefinir y editar los métodos: validateLogin, intenté iniciar sesión, credenciales ya que una vez que se valida el inicio de sesión, la solicitud se reenvía al pasaporte y debe llamarse nombre de usuario.
Logré que esto funcione, con la solución @RaymondLagonda, para Laravel 5.5 con Sentinel , pero debería funcionar también sin Sentinel.
La solución necesita una alteración temporal de los métodos de clase (así que tenga esto en cuenta para futuras actualizaciones) y agrega cierta protección a sus rutas de API (no exponiendo client_secret, por ejemplo).
El primer paso es modificar su
ApiLoginController
para agregar la función de construcción:
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]);
}
En este ejemplo, debe definir var (''PASSPORT_CLIENT_ID'') en su .env y crear el Modelo OauthClients, pero puede omitir esto de manera segura colocando aquí los valores de prueba adecuados.
Una cosa a tener en cuenta es que estamos configurando
$request->email
value a username, solo para cumplir con la convención Oauth2.
El segundo paso es, para anular, el método
sendLoginResponse
que está causando errores como el
Session storage not set
, no necesitamos sesiones aquí, porque es api.
protected function sendLoginResponse(Request $request)
{
// $request->session()->regenerate();
$this->clearLoginAttempts($request);
return $this->authenticated($request, $this->guard()->user())
?: redirect()->intended($this->redirectPath());
}
El tercer paso es modificar sus métodos autenticados como lo sugiere @RaymondLagonda. Debe escribir su propia lógica aquí, y especialmente configurar sus ámbitos.
Y el último paso (en caso de que esté utilizando Sentinel) es modificar
AuthServiceProvider
.
Añadir
$this->app->rebinding(''request'', function ($app, $request) {
$request->setUserResolver(function () use ($app) {
return /Auth::user();
// return $app[''sentinel'']->getUser();
});
});
justo después de
$this->registerPolicies();
en el método de arranque.
Después de estos pasos, debería poder hacer que su API funcione, proporcionando nombre de usuario (''esto siempre será correo electrónico, en esta implementación''), contraseña y grant_type = ''contraseña''
En este punto, puede agregar a los ámbitos de
scopes:...
o
scope:...
para proteger sus rutas.
Espero que realmente ayude ...
Sé que esto es un poco tarde, pero si está consumiendo una API de back-end en un SPA usando
CreateFreshApiToken
en el middleware web, entonces simplemente puede agregar un middleware ''admin'' a su aplicación:
php artisan make:middleware Admin
Luego, en
/App/Http/Middleware/Admin
haga lo siguiente:
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);
}
Asegúrese de haber agregado el método de
role
a
/App/User
para recuperar el rol de usuario.
Ahora todo lo que necesita hacer es registrar su middleware en la
app/Http/Kernel.php
$routeMiddleware
, así:
protected $routeMiddleware = [
// Other Middleware
''admin'' => /App/Http/Middleware/Admin::class,
];
Y agregue eso a su ruta en
routes/api.php
Route::middleware([''auth:api'',''admin''])->get(''/customers'',''Api/CustomersController@index'');
Ahora, si intenta acceder a la API sin permiso, recibirá un error "401 no autorizado", que puede verificar y manejar en su aplicación.