Thanks for this, this question was riddling my mind for a while! I took Raymond Lagonda's solution customised it a little for Laravel 5.6, using the built-in rate limiting, using a single thirdparty
client (or be more custom if needed), while still giving each user a list of permissions (scopes).
- Uses Laravel Passport
password
grant and follows Oauth flow
- Gives you ability to set roles (scopes) for different users
- don't expose/release client ID or client secret, only the user's username (email) and password, pretty much a password grant, minus the client/grant stuff
Examples at bottom
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');
});
(note: api_access
is a flag which decides whether a user can login to the website/frontend portion of the app, to view dashboards/records etc.),
routes/api.php
Route::group(['middleware' => ['auth.client:YOUR_SCOPE_HERE', 'throttle:60,1']], function () {
...routes...
});
MySQL - Users scopes
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 - Logging in/requesting a 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 longlived access_token/refresh_token as normal!
Accessing forbidden scope
{
"data": {
"errors": "Invalid scope(s) provided."
},
"meta": {
"code": 403,
"status": "FORBIDDEN"
}
}