Skip to content

Instantly share code, notes, and snippets.

@kmuenkel
Created October 1, 2021 03:41
Show Gist options
  • Save kmuenkel/ce39bdec364344491398d5cbec9311cd to your computer and use it in GitHub Desktop.
Save kmuenkel/ce39bdec364344491398d5cbec9311cd to your computer and use it in GitHub Desktop.
Passport scopes with abstracted route names
<?php
namespace App\Models\Data;
use BadMethodCallException;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
/**
* @method static string accountRetrieval()
* @method static string accountCreation()
*/
class AccountScopes
{
/**
* @var string
*/
protected static $accountRetrieval = 'auth.account.create';
/**
* @var string
*/
protected static $accountCreation = 'auth.account.read';
/**
* @var array
*/
protected static $scopes = [];
/**
* @return string[]
*/
public static function all(): array
{
static::make();
return static::$scopes;
}
/**
* @return string[]
*/
public static function keys(): array
{
static::make();
return array_keys(static::$scopes);
}
protected static function make()
{
static::$scopes = [
static::accountCreation() => 'Retrieve an account',
static::accountRetrieval() => 'Create an account'
];
}
/**
* @param string $name
* @param array $arguments
* @return string
*/
public static function __callStatic(string $name, array $arguments = [])
{
if (property_exists(static::class, $name)) {
try {
return route(static::$$name);
} catch (RouteNotFoundException $exception) {
return '{'.static::$$name.'}';
}
}
throw new BadMethodCallException('Method '.static::class."::$name does not exist");
}
}
<?php
namespace App\Providers;
use Laravel\Passport\Passport;
use Illuminate\Routing\Router;
use App\Models\Data\AccountScopes;
use Illuminate\Support\Collection;
use Laravel\Passport\Http\Middleware as PassportMiddleware;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
$this->bootPassport();
}
protected function bootPassport()
{
!$this->app->routesAreCached() && Passport::routes();
$this->resoldRouteScopes();
/** @var Router $router */
$router = $this->app->make('router');
$router->aliasMiddleware('user_scope.all', PassportMiddleware\CheckScopes::class)
->aliasMiddleware('user_scope.any', PassportMiddleware\CheckForAnyScope::class)
->aliasMiddleware('client_scope.all', PassportMiddleware\CheckClientCredentials::class)
->aliasMiddleware('client_scope.any', PassportMiddleware\CheckClientCredentialsForAnyScope::class);
Passport::tokensExpireIn(now()->addDays(15));
Passport::refreshTokensExpireIn(now()->addDays(30));
Passport::personalAccessTokensExpireIn(now()->addMonths(6));
}
/**
* Referencing routs by name only works after the RouteServiceProvider boots, which is after this Provider
*/
protected function resoldRouteScopes()
{
/** @var RouteServiceProvider $routeProvider */
$routeProvider = current($this->app->getProviders(RouteServiceProvider::class));
optional($routeProvider)->booted(function () {
Passport::tokensCan(AccountScopes::all());
foreach (app('router')->getRoutes()->getRoutes() as $route) {
$action = $route->getAction();
$action['middleware'] = array_map(function (string $middleware) {
return $this->resolveRouteScope($middleware);
}, (array)($action['middleware'] ?? []));
$route->setAction($action);
}
});
}
/**
* @param string $middleware
* @return string
*/
protected function resolveRouteScope(string $middleware): string
{
[$alias, $scopes] = array_pad(explode(':', $middleware, 2), 2, '');
if ($this->getPassportAliases()->contains($alias)) {
$scopes = array_map(function (string $scope) {
return preg_match('/^{(.+?)}$/', $scope, $matches) ? route($matches[1]) : $scope;
}, explode(',', $scopes));
$middleware = $alias.':'.implode(',', $scopes);
}
return $middleware;
}
/**
* @return Collection<string>
*/
protected function getPassportAliases(): Collection
{
static $aliases = null;
return $aliases ?: $aliases = collect(app('router')->getMiddleware())
->map(function (string $className, string $alias) {
return in_array($className, [
PassportMiddleware\CheckScopes::class,
PassportMiddleware\CheckForAnyScope::class,
PassportMiddleware\CheckClientCredentials::class,
PassportMiddleware\CheckClientCredentialsForAnyScope::class
]) ? $alias : null;
})->filter();
}
}
<?php
use App\Models\Data\AccountScopes;
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::group(['prefix' => 'auth', 'as' => 'auth.'], function () {
Route::group(['as' => 'account.'], function () {
Route::get('account', ['as' => 'create', 'uses' => function () {
return response(AccountScopes::all()[AccountScopes::accountCreation()]);
}]);
Route::get('account.readonly', ['as' => 'read', 'uses' => function () {
return response(AccountScopes::all()[AccountScopes::accountRetrieval()]);
}]);
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment