diff --git a/app/Extensions/Laravel/Sanctum/NewAccessToken.php b/app/Extensions/Laravel/Sanctum/NewAccessToken.php new file mode 100644 index 000000000..256e57b61 --- /dev/null +++ b/app/Extensions/Laravel/Sanctum/NewAccessToken.php @@ -0,0 +1,23 @@ +accessToken = $accessToken; + $this->plainTextToken = $plainTextToken; + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 3e9f1dd19..fe924119f 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http; -use Pterodactyl\Models\ApiKey; use Illuminate\Auth\Middleware\Authorize; use Illuminate\Auth\Middleware\Authenticate; use Illuminate\Http\Middleware\TrustProxies; @@ -16,7 +15,6 @@ use Pterodactyl\Http\Middleware\AdminAuthenticate; use Illuminate\Routing\Middleware\ThrottleRequests; use Pterodactyl\Http\Middleware\LanguageMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; -use Pterodactyl\Http\Middleware\Api\AuthenticateKey; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\AuthenticateSession; use Illuminate\View\Middleware\ShareErrorsFromSession; @@ -25,13 +23,13 @@ use Pterodactyl\Http\Middleware\RedirectIfAuthenticated; use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth; use Pterodactyl\Http\Middleware\Api\AuthenticateIPAccess; use Illuminate\Foundation\Http\Middleware\ValidatePostSize; -use Pterodactyl\Http\Middleware\Api\HandleStatelessRequest; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Pterodactyl\Http\Middleware\Api\Daemon\DaemonAuthenticate; use Pterodactyl\Http\Middleware\RequireTwoFactorAuthentication; use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode; use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; use Pterodactyl\Http\Middleware\Api\Client\SubstituteClientBindings; +use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful; use Pterodactyl\Http\Middleware\Api\Application\AuthenticateApplicationUser; class Kernel extends HttpKernel @@ -67,29 +65,19 @@ class Kernel extends HttpKernel RequireTwoFactorAuthentication::class, ], 'api' => [ - HandleStatelessRequest::class, IsValidJson::class, - StartSession::class, - AuthenticateSession::class, - VerifyCsrfToken::class, + EnsureFrontendRequestsAreStateful::class, + 'auth:sanctum', + RequireTwoFactorAuthentication::class, + AuthenticateIPAccess::class, ], 'application-api' => [ SubstituteBindings::class, - 'api..key:' . ApiKey::TYPE_APPLICATION, AuthenticateApplicationUser::class, - AuthenticateIPAccess::class, - ], - 'client-api' => [ - SubstituteClientBindings::class, - 'api..key:' . ApiKey::TYPE_ACCOUNT, - AuthenticateIPAccess::class, - // This is perhaps a little backwards with the Client API, but logically you'd be unable - // to create/get an API key without first enabling 2FA on the account, so I suppose in the - // end it makes sense. - // - // You just wouldn't be authenticating with the API by providing a 2FA token. - RequireTwoFactorAuthentication::class, ], + // TODO: don't allow an application key to use the client API, but do allow a client + // api key to access the application API. + 'client-api' => [SubstituteClientBindings::class], 'daemon' => [ SubstituteBindings::class, DaemonAuthenticate::class, @@ -112,7 +100,5 @@ class Kernel extends HttpKernel 'bindings' => SubstituteBindings::class, 'recaptcha' => VerifyReCaptcha::class, 'node.maintenance' => MaintenanceMiddleware::class, - // API Specific Middleware - 'api..key' => AuthenticateKey::class, ]; } diff --git a/app/Http/Middleware/Api/AuthenticateIPAccess.php b/app/Http/Middleware/Api/AuthenticateIPAccess.php index 2af34cfd9..839bf83dd 100644 --- a/app/Http/Middleware/Api/AuthenticateIPAccess.php +++ b/app/Http/Middleware/Api/AuthenticateIPAccess.php @@ -6,6 +6,7 @@ use Closure; use IPTools\IP; use IPTools\Range; use Illuminate\Http\Request; +use Laravel\Sanctum\TransientToken; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; class AuthenticateIPAccess @@ -20,14 +21,19 @@ class AuthenticateIPAccess */ public function handle(Request $request, Closure $next) { - $model = $request->attributes->get('api_key'); + /** @var \Laravel\Sanctum\TransientToken|\Pterodactyl\Models\ApiKey $token */ + $token = $request->user()->currentAccessToken(); - if (is_null($model->allowed_ips) || empty($model->allowed_ips)) { + // If this is a stateful request just push the request through to the next + // middleware in the stack, there is nothing we need to explicitly check. If + // this is a valid API Key, but there is no allowed IP restriction, also pass + // the request through. + if ($token instanceof TransientToken || empty($token->allowed_ips)) { return $next($request); } $find = new IP($request->ip()); - foreach ($model->allowed_ips as $ip) { + foreach ($token->allowed_ips as $ip) { if (Range::parse($ip)->contains($find)) { return $next($request); } diff --git a/app/Http/Middleware/Api/AuthenticateKey.php b/app/Http/Middleware/Api/AuthenticateKey.php deleted file mode 100644 index 857bfab29..000000000 --- a/app/Http/Middleware/Api/AuthenticateKey.php +++ /dev/null @@ -1,109 +0,0 @@ -auth = $auth; - $this->encrypter = $encrypter; - $this->repository = $repository; - } - - /** - * Handle an API request by verifying that the provided API key is in a valid - * format and exists in the database. If there is currently a user in the session - * do not even bother to look at the token (they provided a cookie for this to - * be the case). - * - * @return mixed - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - public function handle(Request $request, Closure $next, int $keyType) - { - if (is_null($request->bearerToken()) && is_null($request->user())) { - throw new HttpException(401, 'A bearer token or valid user session cookie must be provided to access this endpoint.', null, ['WWW-Authenticate' => 'Bearer']); - } - - // This is a request coming through using cookies, we have an authenticated user - // not using an API key. Make some fake API key models and continue on through - // the process. - if ($request->user() instanceof User) { - $model = (new ApiKey())->forceFill([ - 'user_id' => $request->user()->id, - 'key_type' => ApiKey::TYPE_ACCOUNT, - ]); - } else { - $model = $this->authenticateApiKey($request->bearerToken(), $keyType); - - $this->auth->guard()->onceUsingId($model->user_id); - } - - $request->attributes->set('api_key', $model); - - return $next($request); - } - - /** - * Authenticate an API key. - * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException - */ - protected function authenticateApiKey(string $key, int $keyType): ApiKey - { - $identifier = substr($key, 0, ApiKey::IDENTIFIER_LENGTH); - $token = substr($key, ApiKey::IDENTIFIER_LENGTH); - - try { - $model = $this->repository->findFirstWhere([ - ['identifier', '=', $identifier], - ['key_type', '=', $keyType], - ]); - } catch (RecordNotFoundException $exception) { - throw new AccessDeniedHttpException(); - } - - if (!hash_equals($this->encrypter->decrypt($model->token), $token)) { - throw new AccessDeniedHttpException(); - } - - $this->repository->withoutFreshModel()->update($model->id, ['last_used_at' => CarbonImmutable::now()]); - - return $model; - } -} diff --git a/app/Http/Middleware/Api/HandleStatelessRequest.php b/app/Http/Middleware/Api/HandleStatelessRequest.php deleted file mode 100644 index ab697d687..000000000 --- a/app/Http/Middleware/Api/HandleStatelessRequest.php +++ /dev/null @@ -1,35 +0,0 @@ -bearerToken()) && $request->isJson()) { - $request->session()->getHandler()->destroy( - $request->session()->getId() - ); - - $response->headers->remove('Set-Cookie'); - } - - return $response; - } -} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 6471814c6..ee5a3d216 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -2,8 +2,6 @@ namespace Pterodactyl\Http\Middleware; -use Closure; -use Pterodactyl\Models\ApiKey; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as BaseVerifier; class VerifyCsrfToken extends BaseVerifier @@ -16,31 +14,4 @@ class VerifyCsrfToken extends BaseVerifier * @var string[] */ protected $except = ['remote/*', 'daemon/*']; - - /** - * Manually apply CSRF protection to routes depending on the authentication - * mechanism being used. If the API request is using an API key that exists - * in the database we can safely ignore CSRF protections, since that would be - * a manually initiated request by a user or server. - * - * All other requests should go through the standard CSRF protections that - * Laravel affords us. This code will be removed in v2 since we have switched - * to using Sanctum for the API endpoints, which handles that for us automatically. - * - * @param \Illuminate\Http\Request $request - * - * @return mixed - * - * @throws \Illuminate\Session\TokenMismatchException - */ - public function handle($request, Closure $next) - { - $key = $request->attributes->get('api_key'); - - if ($key instanceof ApiKey && $key->exists) { - return $next($request); - } - - return parent::handle($request, $next); - } } diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 4c2363333..32c6fa03e 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -2,19 +2,59 @@ namespace Pterodactyl\Models; +use Illuminate\Support\Str; use Pterodactyl\Services\Acl\Api\AdminAcl; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** + * Pterodactyl\Models\ApiKey. + * * @property int $id * @property int $user_id * @property int $key_type - * @property string $identifier + * @property string|null $identifier * @property string $token - * @property array $allowed_ips - * @property string $memo - * @property \Carbon\Carbon|null $last_used_at - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at + * @property array|null $allowed_ips + * @property string|null $memo + * @property \Illuminate\Support\Carbon|null $last_used_at + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property int $r_servers + * @property int $r_nodes + * @property int $r_allocations + * @property int $r_users + * @property int $r_locations + * @property int $r_nests + * @property int $r_eggs + * @property int $r_database_hosts + * @property int $r_server_databases + * @property \Pterodactyl\Models\User $tokenable + * @property \Pterodactyl\Models\User $user + * + * @method static \Database\Factories\ApiKeyFactory factory(...$parameters) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey query() + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereAllowedIps($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereIdentifier($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereKeyType($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereLastUsedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereMemo($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRAllocations($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRDatabaseHosts($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereREggs($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRLocations($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNests($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRNodes($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServerDatabases($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRServers($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereRUsers($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value) + * @mixin \Eloquent */ class ApiKey extends Model { @@ -23,21 +63,21 @@ class ApiKey extends Model * API representation using fractal. */ public const RESOURCE_NAME = 'api_key'; - /** * Different API keys that can exist on the system. */ public const TYPE_NONE = 0; public const TYPE_ACCOUNT = 1; + /* @deprecated */ public const TYPE_APPLICATION = 2; + /* @deprecated */ public const TYPE_DAEMON_USER = 3; + /* @deprecated */ public const TYPE_DAEMON_APPLICATION = 4; - /** * The length of API key identifiers. */ public const IDENTIFIER_LENGTH = 16; - /** * The length of the actual API key that is encrypted and stored * in the database. @@ -124,4 +164,47 @@ class ApiKey extends Model self::UPDATED_AT, 'last_used_at', ]; + + /** + * Returns the user this token is assigned to. + */ + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + /** + * Required for support with Laravel Sanctum. + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + * + * @see \Laravel\Sanctum\Guard::supportsTokens() + */ + public function tokenable() + { + return $this->user(); + } + + /** + * Finds the model matching the provided token. + * + * @param string $token + * + * @return self|null + */ + public static function findToken($token) + { + $id = Str::substr($token, 0, self::IDENTIFIER_LENGTH); + $token = Str::substr($token, strlen($id)); + + return static::where('identifier', $id)->where('token', encrypt($token))->first(); + } + + /** + * Generates a new identifier for an API key. + */ + public static function generateTokenIdentifier(): string + { + return 'ptdl_' . Str::random(self::IDENTIFIER_LENGTH - 5); + } } diff --git a/app/Models/Traits/HasAccessTokens.php b/app/Models/Traits/HasAccessTokens.php new file mode 100644 index 000000000..2aa21cb9e --- /dev/null +++ b/app/Models/Traits/HasAccessTokens.php @@ -0,0 +1,37 @@ +hasMany(Sanctum::$personalAccessTokenModel); + } + + public function createToken(string $name, array $abilities = ['*']) + { + /** @var \Pterodactyl\Models\ApiKey $token */ + $token = $this->tokens()->create([ + 'user_id' => $this->id, + 'key_type' => ApiKey::TYPE_ACCOUNT, + 'identifier' => ApiKey::generateTokenIdentifier(), + 'token' => encrypt($plain = Str::random(ApiKey::KEY_LENGTH)), + 'memo' => $name, + 'allowed_ips' => [], + ]); + + return new NewAccessToken($token, $token->identifier . $plain); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 196ad18ed..6e676a416 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,6 +3,7 @@ namespace Pterodactyl\Models; use Pterodactyl\Rules\Username; +use Laravel\Sanctum\HasApiTokens; use Illuminate\Support\Collection; use Illuminate\Validation\Rules\In; use Illuminate\Auth\Authenticatable; @@ -18,7 +19,7 @@ use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; /** - * \Pterodactyl\Models\User. + * Pterodactyl\Models\User. * * @property int $id * @property string|null $external_id @@ -28,27 +29,28 @@ use Pterodactyl\Notifications\SendPasswordReset as ResetPasswordNotification; * @property string|null $name_first * @property string|null $name_last * @property string $password - * @property string|null $remeber_token + * @property string|null $remember_token * @property string $language * @property bool $root_admin * @property bool $use_totp * @property string|null $totp_secret - * @property \Carbon\Carbon|null $totp_authenticated_at + * @property \Illuminate\Support\Carbon|null $totp_authenticated_at * @property bool $gravatar - * @property \Carbon\Carbon $created_at - * @property \Carbon\Carbon $updated_at - * @property string $name - * @property \Pterodactyl\Models\ApiKey[]|\Illuminate\Database\Eloquent\Collection $apiKeys - * @property \Pterodactyl\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers - * @property \Pterodactyl\Models\RecoveryToken[]|\Illuminate\Database\Eloquent\Collection $recoveryTokens - * @property string|null $remember_token + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $apiKeys * @property int|null $api_keys_count + * @property string $name * @property \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications * @property int|null $notifications_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\RecoveryToken[] $recoveryTokens * @property int|null $recovery_tokens_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\Server[] $servers * @property int|null $servers_count * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\UserSSHKey[] $sshKeys * @property int|null $ssh_keys_count + * @property \Illuminate\Database\Eloquent\Collection|\Pterodactyl\Models\ApiKey[] $tokens + * @property int|null $tokens_count * * @method static \Database\Factories\UserFactory factory(...$parameters) * @method static Builder|User newModelQuery() @@ -82,6 +84,7 @@ class User extends Model implements use Authorizable; use AvailableLanguages; use CanResetPassword; + use HasApiTokens; use Notifiable; public const USER_LEVEL_USER = 0; diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index e147736ed..76ac26f37 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -2,6 +2,10 @@ namespace Pterodactyl\Providers; +use Laravel\Sanctum\Sanctum; +use Pterodactyl\Models\Server; +use Pterodactyl\Models\ApiKey; +use Pterodactyl\Policies\ServerPolicy; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider @@ -12,7 +16,7 @@ class AuthServiceProvider extends ServiceProvider * @var array */ protected $policies = [ - 'Pterodactyl\Models\Server' => 'Pterodactyl\Policies\ServerPolicy', + Server::class => ServerPolicy::class, ]; /** @@ -20,6 +24,8 @@ class AuthServiceProvider extends ServiceProvider */ public function boot() { + Sanctum::usePersonalAccessTokenModel(ApiKey::class); + $this->registerPolicies(); } } diff --git a/composer.json b/composer.json index 8279aee77..eb553e6a7 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "laracasts/utilities": "~3.2.1", "laravel/framework": "^8.83", "laravel/helpers": "~1.5.0", + "laravel/sanctum": "~2.15.1", "laravel/tinker": "~2.7.2", "laravel/ui": "~3.4.5", "lcobucci/jwt": "~4.1.5", diff --git a/composer.lock b/composer.lock index db2b52ca1..534c1958a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "59024efe671be95afe14319b19606566", + "content-hash": "0368e946c40456bcd1fb007bfc3e7bf0", "packages": [ { "name": "aws/aws-crt-php", @@ -1668,6 +1668,71 @@ }, "time": "2022-01-12T15:58:51+00:00" }, + { + "name": "laravel/sanctum", + "version": "v2.15.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/sanctum.git", + "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473", + "shasum": "" + }, + "require": { + "ext-json": "*", + "illuminate/console": "^6.9|^7.0|^8.0|^9.0", + "illuminate/contracts": "^6.9|^7.0|^8.0|^9.0", + "illuminate/database": "^6.9|^7.0|^8.0|^9.0", + "illuminate/support": "^6.9|^7.0|^8.0|^9.0", + "php": "^7.2|^8.0" + }, + "require-dev": { + "mockery/mockery": "^1.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", + "phpunit/phpunit": "^8.0|^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + }, + "laravel": { + "providers": [ + "Laravel\\Sanctum\\SanctumServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Sanctum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", + "keywords": [ + "auth", + "laravel", + "sanctum" + ], + "support": { + "issues": "https://github.com/laravel/sanctum/issues", + "source": "https://github.com/laravel/sanctum" + }, + "time": "2022-04-08T13:39:49+00:00" + }, { "name": "laravel/serializable-closure", "version": "v1.1.1", @@ -10773,5 +10838,5 @@ "platform-overrides": { "php": "7.4.0" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.3.0" } diff --git a/config/sanctum.php b/config/sanctum.php new file mode 100644 index 000000000..5e2a12062 --- /dev/null +++ b/config/sanctum.php @@ -0,0 +1,67 @@ + explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( + '%s%s', + 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', + Sanctum::currentApplicationUrlWithPort() + ))), + + /* + |-------------------------------------------------------------------------- + | Sanctum Guards + |-------------------------------------------------------------------------- + | + | This array contains the authentication guards that will be checked when + | Sanctum is trying to authenticate a request. If none of these guards + | are able to authenticate the request, Sanctum will use the bearer + | token that's present on an incoming request for authentication. + | + */ + + 'guard' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Expiration Minutes + |-------------------------------------------------------------------------- + | + | This value controls the number of minutes until an issued token will be + | considered expired. If this value is null, personal access tokens do + | not expire. This won't tweak the lifetime of first-party sessions. + | + */ + + 'expiration' => null, + + /* + |-------------------------------------------------------------------------- + | Sanctum Middleware + |-------------------------------------------------------------------------- + | + | When authenticating your first-party SPA with Sanctum you may need to + | customize some of the middleware Sanctum uses while processing the + | request. You may change the middleware listed below as required. + | + */ + + 'middleware' => [ + 'verify_csrf_token' => Pterodactyl\Http\Middleware\VerifyCsrfToken::class, + 'encrypt_cookies' => Pterodactyl\Http\Middleware\EncryptCookies::class, + ], + +];