├── LICENSE.md ├── README.md ├── UPGRADE.md ├── composer.json ├── config └── sanctum.php ├── database └── migrations │ └── 2019_12_14_000001_create_personal_access_tokens_table.php ├── src ├── Console │ └── Commands │ │ └── PruneExpired.php ├── Contracts │ ├── HasAbilities.php │ └── HasApiTokens.php ├── Events │ └── TokenAuthenticated.php ├── Exceptions │ ├── MissingAbilityException.php │ └── MissingScopeException.php ├── Guard.php ├── HasApiTokens.php ├── Http │ ├── Controllers │ │ └── CsrfCookieController.php │ └── Middleware │ │ ├── AuthenticateSession.php │ │ ├── CheckAbilities.php │ │ ├── CheckForAnyAbility.php │ │ ├── CheckForAnyScope.php │ │ ├── CheckScopes.php │ │ └── EnsureFrontendRequestsAreStateful.php ├── NewAccessToken.php ├── PersonalAccessToken.php ├── Sanctum.php ├── SanctumServiceProvider.php └── TransientToken.php └── testbench.yaml /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Taylor Otwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Logo Laravel Sanctum

2 | 3 |

4 | Build Status 5 | Total Downloads 6 | Latest Stable Version 7 | License 8 |

9 | 10 | ## Introduction 11 | 12 | Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs. 13 | 14 | ## Official Documentation 15 | 16 | Documentation for Sanctum can be found on the [Laravel website](https://laravel.com/docs/sanctum). 17 | 18 | ## Contributing 19 | 20 | Thank you for considering contributing to Sanctum! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). 21 | 22 | ## Code of Conduct 23 | 24 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). 25 | 26 | ## Security Vulnerabilities 27 | 28 | Please review [our security policy](https://github.com/laravel/sanctum/security/policy) on how to report security vulnerabilities. 29 | 30 | ## License 31 | 32 | Laravel Sanctum is open-sourced software licensed under the [MIT license](LICENSE.md). 33 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | 3 | ## Upgrading To 4.0 From 3.x 4 | 5 | ### Minimum PHP Version 6 | 7 | PHP 8.2 is now the minimum required version. 8 | 9 | ### Minimum Laravel Version 10 | 11 | Laravel 11.0 is now the minimum required version. 12 | 13 | ### Migration Changes 14 | 15 | Sanctum 4.0 no longer automatically loads migrations from its own migrations directory. Instead, you should run the following command to publish Sanctum's migrations to your application: 16 | 17 | ```bash 18 | php artisan vendor:publish --tag=sanctum-migrations 19 | ``` 20 | 21 | ### Configuration Changes 22 | 23 | In your application's `config/sanctum.php` configuration file, you should update the references to the `authenticate_session`, `encrypt_cookies`, and `validate_csrf_token` middleware to the following: 24 | 25 | ```php 26 | 'middleware' => [ 27 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 28 | 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, 29 | 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, 30 | ], 31 | ``` 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/sanctum", 3 | "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", 4 | "keywords": ["laravel", "sanctum", "auth"], 5 | "license": "MIT", 6 | "support": { 7 | "issues": "https://github.com/laravel/sanctum/issues", 8 | "source": "https://github.com/laravel/sanctum" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Taylor Otwell", 13 | "email": "taylor@laravel.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^8.2", 18 | "ext-json": "*", 19 | "illuminate/console": "^11.0|^12.0", 20 | "illuminate/contracts": "^11.0|^12.0", 21 | "illuminate/database": "^11.0|^12.0", 22 | "illuminate/support": "^11.0|^12.0", 23 | "symfony/console": "^7.0" 24 | }, 25 | "require-dev": { 26 | "mockery/mockery": "^1.6", 27 | "orchestra/testbench": "^9.0|^10.0", 28 | "phpstan/phpstan": "^1.10", 29 | "phpunit/phpunit": "^11.3" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Laravel\\Sanctum\\": "src/" 34 | } 35 | }, 36 | "autoload-dev": { 37 | "psr-4": { 38 | "Laravel\\Sanctum\\Tests\\": "tests/", 39 | "Workbench\\App\\": "workbench/app/", 40 | "Workbench\\Database\\Factories\\": "workbench/database/factories/" 41 | } 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "Laravel\\Sanctum\\SanctumServiceProvider" 47 | ] 48 | } 49 | }, 50 | "config": { 51 | "sort-packages": true 52 | }, 53 | "minimum-stability": "dev", 54 | "prefer-stable": true 55 | } 56 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 19 | '%s%s', 20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 21 | Sanctum::currentApplicationUrlWithPort(), 22 | // Sanctum::currentRequestHost(), 23 | ))), 24 | 25 | /* 26 | |-------------------------------------------------------------------------- 27 | | Sanctum Guards 28 | |-------------------------------------------------------------------------- 29 | | 30 | | This array contains the authentication guards that will be checked when 31 | | Sanctum is trying to authenticate a request. If none of these guards 32 | | are able to authenticate the request, Sanctum will use the bearer 33 | | token that's present on an incoming request for authentication. 34 | | 35 | */ 36 | 37 | 'guard' => ['web'], 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Expiration Minutes 42 | |-------------------------------------------------------------------------- 43 | | 44 | | This value controls the number of minutes until an issued token will be 45 | | considered expired. This will override any values set in the token's 46 | | "expires_at" attribute, but first-party sessions are not affected. 47 | | 48 | */ 49 | 50 | 'expiration' => null, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Token Prefix 55 | |-------------------------------------------------------------------------- 56 | | 57 | | Sanctum can prefix new tokens in order to take advantage of numerous 58 | | security scanning initiatives maintained by open source platforms 59 | | that notify developers if they commit tokens into repositories. 60 | | 61 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning 62 | | 63 | */ 64 | 65 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Sanctum Middleware 70 | |-------------------------------------------------------------------------- 71 | | 72 | | When authenticating your first-party SPA with Sanctum you may need to 73 | | customize some of the middleware Sanctum uses while processing the 74 | | request. You may change the middleware listed below as required. 75 | | 76 | */ 77 | 78 | 'middleware' => [ 79 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 80 | 'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class, 81 | 'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class, 82 | ], 83 | 84 | ]; 85 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->text('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable()->index(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/Console/Commands/PruneExpired.php: -------------------------------------------------------------------------------- 1 | option('hours'); 36 | 37 | $this->components->task( 38 | 'Pruning tokens with expired expires_at timestamps', 39 | fn () => $model::where('expires_at', '<', now()->subHours($hours))->delete() 40 | ); 41 | 42 | if ($expiration = config('sanctum.expiration')) { 43 | $this->components->task( 44 | 'Pruning tokens with expired expiration value based on configuration file', 45 | fn () => $model::where('created_at', '<', now()->subMinutes($expiration + ($hours * 60)))->delete() 46 | ); 47 | } else { 48 | $this->components->warn('Expiration value not specified in configuration file.'); 49 | } 50 | 51 | $this->components->info("Tokens expired for more than [$hours hours] pruned successfully."); 52 | 53 | return 0; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Contracts/HasAbilities.php: -------------------------------------------------------------------------------- 1 | token = $token; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Exceptions/MissingAbilityException.php: -------------------------------------------------------------------------------- 1 | abilities = Arr::wrap($abilities); 29 | } 30 | 31 | /** 32 | * Get the abilities that the user did not have. 33 | * 34 | * @return array 35 | */ 36 | public function abilities() 37 | { 38 | return $this->abilities; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Exceptions/MissingScopeException.php: -------------------------------------------------------------------------------- 1 | scopes = Arr::wrap($scopes); 33 | } 34 | 35 | /** 36 | * Get the scopes that the user did not have. 37 | * 38 | * @return array 39 | */ 40 | public function scopes() 41 | { 42 | return $this->scopes; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Guard.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 44 | $this->expiration = $expiration; 45 | $this->provider = $provider; 46 | } 47 | 48 | /** 49 | * Retrieve the authenticated user for the incoming request. 50 | * 51 | * @param \Illuminate\Http\Request $request 52 | * @return mixed 53 | */ 54 | public function __invoke(Request $request) 55 | { 56 | foreach (Arr::wrap(config('sanctum.guard', 'web')) as $guard) { 57 | if ($user = $this->auth->guard($guard)->user()) { 58 | return $this->supportsTokens($user) 59 | ? $user->withAccessToken(new TransientToken) 60 | : $user; 61 | } 62 | } 63 | 64 | if ($token = $this->getTokenFromRequest($request)) { 65 | $model = Sanctum::$personalAccessTokenModel; 66 | 67 | $accessToken = $model::findToken($token); 68 | 69 | if (! $this->isValidAccessToken($accessToken) || 70 | ! $this->supportsTokens($accessToken->tokenable)) { 71 | return; 72 | } 73 | 74 | $tokenable = $accessToken->tokenable->withAccessToken( 75 | $accessToken 76 | ); 77 | 78 | event(new TokenAuthenticated($accessToken)); 79 | 80 | $this->updateLastUsedAt($accessToken); 81 | 82 | return $tokenable; 83 | } 84 | } 85 | 86 | /** 87 | * Determine if the tokenable model supports API tokens. 88 | * 89 | * @param mixed $tokenable 90 | * @return bool 91 | */ 92 | protected function supportsTokens($tokenable = null) 93 | { 94 | return $tokenable && in_array(HasApiTokens::class, class_uses_recursive( 95 | get_class($tokenable) 96 | )); 97 | } 98 | 99 | /** 100 | * Get the token from the request. 101 | * 102 | * @param \Illuminate\Http\Request $request 103 | * @return string|null 104 | */ 105 | protected function getTokenFromRequest(Request $request) 106 | { 107 | if (is_callable(Sanctum::$accessTokenRetrievalCallback)) { 108 | return (string) (Sanctum::$accessTokenRetrievalCallback)($request); 109 | } 110 | 111 | $token = $request->bearerToken(); 112 | 113 | return $this->isValidBearerToken($token) ? $token : null; 114 | } 115 | 116 | /** 117 | * Determine if the bearer token is in the correct format. 118 | * 119 | * @param string|null $token 120 | * @return bool 121 | */ 122 | protected function isValidBearerToken(?string $token = null) 123 | { 124 | if (! is_null($token) && str_contains($token, '|')) { 125 | $model = new Sanctum::$personalAccessTokenModel; 126 | 127 | if ($model->getKeyType() === 'int') { 128 | [$id, $token] = explode('|', $token, 2); 129 | 130 | return ctype_digit($id) && ! empty($token); 131 | } 132 | } 133 | 134 | return ! empty($token); 135 | } 136 | 137 | /** 138 | * Determine if the provided access token is valid. 139 | * 140 | * @param mixed $accessToken 141 | * @return bool 142 | */ 143 | protected function isValidAccessToken($accessToken): bool 144 | { 145 | if (! $accessToken) { 146 | return false; 147 | } 148 | 149 | $isValid = 150 | (! $this->expiration || $accessToken->created_at->gt(now()->subMinutes($this->expiration))) 151 | && (! $accessToken->expires_at || ! $accessToken->expires_at->isPast()) 152 | && $this->hasValidProvider($accessToken->tokenable); 153 | 154 | if (is_callable(Sanctum::$accessTokenAuthenticationCallback)) { 155 | $isValid = (bool) (Sanctum::$accessTokenAuthenticationCallback)($accessToken, $isValid); 156 | } 157 | 158 | return $isValid; 159 | } 160 | 161 | /** 162 | * Determine if the tokenable model matches the provider's model type. 163 | * 164 | * @param \Illuminate\Database\Eloquent\Model $tokenable 165 | * @return bool 166 | */ 167 | protected function hasValidProvider($tokenable) 168 | { 169 | if (is_null($this->provider)) { 170 | return true; 171 | } 172 | 173 | $model = config("auth.providers.{$this->provider}.model"); 174 | 175 | return $tokenable instanceof $model; 176 | } 177 | 178 | /** 179 | * Store the time the token was last used. 180 | * 181 | * @param \Laravel\Sanctum\PersonalAccessToken $accessToken 182 | * @return void 183 | */ 184 | protected function updateLastUsedAt($accessToken) 185 | { 186 | if (method_exists($accessToken->getConnection(), 'hasModifiedRecords') && 187 | method_exists($accessToken->getConnection(), 'setRecordModificationState')) { 188 | $hasModifiedRecords = $accessToken->getConnection()->hasModifiedRecords(); 189 | $accessToken->forceFill(['last_used_at' => now()])->save(); 190 | 191 | $accessToken->getConnection()->setRecordModificationState($hasModifiedRecords); 192 | } else { 193 | $accessToken->forceFill(['last_used_at' => now()])->save(); 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/HasApiTokens.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | public function tokens() 26 | { 27 | return $this->morphMany(Sanctum::$personalAccessTokenModel, 'tokenable'); 28 | } 29 | 30 | /** 31 | * Determine if the current API token has a given scope. 32 | * 33 | * @param string $ability 34 | * @return bool 35 | */ 36 | public function tokenCan(string $ability) 37 | { 38 | return $this->accessToken && $this->accessToken->can($ability); 39 | } 40 | 41 | /** 42 | * Determine if the current API token does not have a given scope. 43 | * 44 | * @param string $ability 45 | * @return bool 46 | */ 47 | public function tokenCant(string $ability) 48 | { 49 | return ! $this->tokenCan($ability); 50 | } 51 | 52 | /** 53 | * Create a new personal access token for the user. 54 | * 55 | * @param string $name 56 | * @param array $abilities 57 | * @param \DateTimeInterface|null $expiresAt 58 | * @return \Laravel\Sanctum\NewAccessToken 59 | */ 60 | public function createToken(string $name, array $abilities = ['*'], ?DateTimeInterface $expiresAt = null) 61 | { 62 | $plainTextToken = $this->generateTokenString(); 63 | 64 | $token = $this->tokens()->create([ 65 | 'name' => $name, 66 | 'token' => hash('sha256', $plainTextToken), 67 | 'abilities' => $abilities, 68 | 'expires_at' => $expiresAt, 69 | ]); 70 | 71 | return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken); 72 | } 73 | 74 | /** 75 | * Generate the token string. 76 | * 77 | * @return string 78 | */ 79 | public function generateTokenString() 80 | { 81 | return sprintf( 82 | '%s%s%s', 83 | config('sanctum.token_prefix', ''), 84 | $tokenEntropy = Str::random(40), 85 | hash('crc32b', $tokenEntropy) 86 | ); 87 | } 88 | 89 | /** 90 | * Get the access token currently associated with the user. 91 | * 92 | * @return TToken 93 | */ 94 | public function currentAccessToken() 95 | { 96 | return $this->accessToken; 97 | } 98 | 99 | /** 100 | * Set the current access token for the user. 101 | * 102 | * @param TToken $accessToken 103 | * @return $this 104 | */ 105 | public function withAccessToken($accessToken) 106 | { 107 | $this->accessToken = $accessToken; 108 | 109 | return $this; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Http/Controllers/CsrfCookieController.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 20 | return new JsonResponse(status: 204); 21 | } 22 | 23 | return new Response(status: 204); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Http/Middleware/AuthenticateSession.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 32 | } 33 | 34 | /** 35 | * Handle an incoming request. 36 | * 37 | * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next 38 | * 39 | * @throws \Illuminate\Auth\AuthenticationException 40 | */ 41 | public function handle(Request $request, Closure $next): Response 42 | { 43 | if (! $request->hasSession() || ! $request->user()) { 44 | return $next($request); 45 | } 46 | 47 | $guards = Collection::make(Arr::wrap(config('sanctum.guard'))) 48 | ->mapWithKeys(fn ($guard) => [$guard => $this->auth->guard($guard)]) 49 | ->filter(fn ($guard) => $guard instanceof SessionGuard); 50 | 51 | $shouldLogout = $guards->filter( 52 | fn ($guard, $driver) => $request->session()->has('password_hash_'.$driver) 53 | )->filter( 54 | fn ($guard, $driver) => $request->session()->get('password_hash_'.$driver) !== 55 | $request->user()->getAuthPassword() 56 | ); 57 | 58 | if ($shouldLogout->isNotEmpty()) { 59 | $shouldLogout->each->logoutCurrentDevice(); 60 | 61 | $request->session()->flush(); 62 | 63 | throw new AuthenticationException('Unauthenticated.', [...$shouldLogout->keys()->all(), 'sanctum']); 64 | } 65 | 66 | return tap($next($request), function () use ($request, $guards) { 67 | if (! is_null($guard = $this->getFirstGuardWithUser($guards->keys()))) { 68 | $this->storePasswordHashInSession($request, $guard); 69 | } 70 | }); 71 | } 72 | 73 | /** 74 | * Get the first authentication guard that has a user. 75 | * 76 | * @param \Illuminate\Support\Collection $guards 77 | * @return string|null 78 | */ 79 | protected function getFirstGuardWithUser(Collection $guards) 80 | { 81 | return $guards->first(function ($guard) { 82 | $guardInstance = $this->auth->guard($guard); 83 | 84 | return method_exists($guardInstance, 'hasUser') && 85 | $guardInstance->hasUser(); 86 | }); 87 | } 88 | 89 | /** 90 | * Store the user's current password hash in the session. 91 | * 92 | * @param \Illuminate\Http\Request $request 93 | * @param string $guard 94 | * @return void 95 | */ 96 | protected function storePasswordHashInSession($request, string $guard) 97 | { 98 | $request->session()->put([ 99 | "password_hash_{$guard}" => $this->auth->guard($guard)->user()->getAuthPassword(), 100 | ]); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Http/Middleware/CheckAbilities.php: -------------------------------------------------------------------------------- 1 | user() || ! $request->user()->currentAccessToken()) { 23 | throw new AuthenticationException; 24 | } 25 | 26 | foreach ($abilities as $ability) { 27 | if (! $request->user()->tokenCan($ability)) { 28 | throw new MissingAbilityException($ability); 29 | } 30 | } 31 | 32 | return $next($request); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Http/Middleware/CheckForAnyAbility.php: -------------------------------------------------------------------------------- 1 | user() || ! $request->user()->currentAccessToken()) { 23 | throw new AuthenticationException; 24 | } 25 | 26 | foreach ($abilities as $ability) { 27 | if ($request->user()->tokenCan($ability)) { 28 | return $next($request); 29 | } 30 | } 31 | 32 | throw new MissingAbilityException($abilities); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Http/Middleware/CheckForAnyScope.php: -------------------------------------------------------------------------------- 1 | handle($request, $next, ...$scopes); 27 | } catch (\Laravel\Sanctum\Exceptions\MissingAbilityException $e) { 28 | throw new MissingScopeException($e->abilities()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Http/Middleware/CheckScopes.php: -------------------------------------------------------------------------------- 1 | handle($request, $next, ...$scopes); 27 | } catch (\Laravel\Sanctum\Exceptions\MissingAbilityException $e) { 28 | throw new MissingScopeException($e->abilities()); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Http/Middleware/EnsureFrontendRequestsAreStateful.php: -------------------------------------------------------------------------------- 1 | configureSecureCookieSessions(); 22 | 23 | return (new Pipeline(app()))->send($request)->through( 24 | static::fromFrontend($request) ? $this->frontendMiddleware() : [] 25 | )->then(function ($request) use ($next) { 26 | return $next($request); 27 | }); 28 | } 29 | 30 | /** 31 | * Configure secure cookie sessions. 32 | * 33 | * @return void 34 | */ 35 | protected function configureSecureCookieSessions() 36 | { 37 | config([ 38 | 'session.http_only' => true, 39 | 'session.same_site' => 'lax', 40 | ]); 41 | } 42 | 43 | /** 44 | * Get the middleware that should be applied to requests from the "frontend". 45 | * 46 | * @return array 47 | */ 48 | protected function frontendMiddleware() 49 | { 50 | $middleware = array_values(array_filter(array_unique([ 51 | config('sanctum.middleware.encrypt_cookies', \Illuminate\Cookie\Middleware\EncryptCookies::class), 52 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 53 | \Illuminate\Session\Middleware\StartSession::class, 54 | config('sanctum.middleware.validate_csrf_token', config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class)), 55 | config('sanctum.middleware.authenticate_session'), 56 | ]))); 57 | 58 | array_unshift($middleware, function ($request, $next) { 59 | $request->attributes->set('sanctum', true); 60 | 61 | return $next($request); 62 | }); 63 | 64 | return $middleware; 65 | } 66 | 67 | /** 68 | * Determine if the given request is from the first-party application frontend. 69 | * 70 | * @param \Illuminate\Http\Request $request 71 | * @return bool 72 | */ 73 | public static function fromFrontend($request) 74 | { 75 | $domain = $request->headers->get('referer') ?: $request->headers->get('origin'); 76 | 77 | if (is_null($domain)) { 78 | return false; 79 | } 80 | 81 | $domain = Str::replaceFirst('https://', '', $domain); 82 | $domain = Str::replaceFirst('http://', '', $domain); 83 | $domain = Str::endsWith($domain, '/') ? $domain : "{$domain}/"; 84 | 85 | $stateful = array_filter(config('sanctum.stateful', [])); 86 | 87 | return Str::is(Collection::make($stateful)->map(function ($uri) use ($request) { 88 | $uri = $uri === Sanctum::$currentRequestHostPlaceholder ? $request->getHttpHost() : $uri; 89 | 90 | return trim($uri).'/*'; 91 | })->all(), $domain); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/NewAccessToken.php: -------------------------------------------------------------------------------- 1 | accessToken = $accessToken; 34 | $this->plainTextToken = $plainTextToken; 35 | } 36 | 37 | /** 38 | * Get the instance as an array. 39 | * 40 | * @return array 41 | */ 42 | public function toArray() 43 | { 44 | return [ 45 | 'accessToken' => $this->accessToken, 46 | 'plainTextToken' => $this->plainTextToken, 47 | ]; 48 | } 49 | 50 | /** 51 | * Convert the object to its JSON representation. 52 | * 53 | * @param int $options 54 | * @return string 55 | */ 56 | public function toJson($options = 0) 57 | { 58 | return json_encode($this->toArray(), $options); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/PersonalAccessToken.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $casts = [ 16 | 'abilities' => 'json', 17 | 'last_used_at' => 'datetime', 18 | 'expires_at' => 'datetime', 19 | ]; 20 | 21 | /** 22 | * The attributes that are mass assignable. 23 | * 24 | * @var array 25 | */ 26 | protected $fillable = [ 27 | 'name', 28 | 'token', 29 | 'abilities', 30 | 'expires_at', 31 | ]; 32 | 33 | /** 34 | * The attributes that should be hidden for serialization. 35 | * 36 | * @var array 37 | */ 38 | protected $hidden = [ 39 | 'token', 40 | ]; 41 | 42 | /** 43 | * Get the tokenable model that the access token belongs to. 44 | * 45 | * @return \Illuminate\Database\Eloquent\Relations\MorphTo 46 | */ 47 | public function tokenable() 48 | { 49 | return $this->morphTo('tokenable'); 50 | } 51 | 52 | /** 53 | * Find the token instance matching the given token. 54 | * 55 | * @param string $token 56 | * @return static|null 57 | */ 58 | public static function findToken($token) 59 | { 60 | if (strpos($token, '|') === false) { 61 | return static::where('token', hash('sha256', $token))->first(); 62 | } 63 | 64 | [$id, $token] = explode('|', $token, 2); 65 | 66 | if ($instance = static::find($id)) { 67 | return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null; 68 | } 69 | } 70 | 71 | /** 72 | * Determine if the token has a given ability. 73 | * 74 | * @param string $ability 75 | * @return bool 76 | */ 77 | public function can($ability) 78 | { 79 | return in_array('*', $this->abilities) || 80 | array_key_exists($ability, array_flip($this->abilities)); 81 | } 82 | 83 | /** 84 | * Determine if the token is missing a given ability. 85 | * 86 | * @param string $ability 87 | * @return bool 88 | */ 89 | public function cant($ability) 90 | { 91 | return ! $this->can($ability); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Sanctum.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | public static $personalAccessTokenModel = 'Laravel\\Sanctum\\PersonalAccessToken'; 18 | 19 | /** 20 | * A callback that can get the token from the request. 21 | * 22 | * @var callable|null 23 | */ 24 | public static $accessTokenRetrievalCallback; 25 | 26 | /** 27 | * A callback that can add to the validation of the access token. 28 | * 29 | * @var callable|null 30 | */ 31 | public static $accessTokenAuthenticationCallback; 32 | 33 | /** 34 | * A placeholder to instruct Sanctum to include the current request host in the list of stateful domains. 35 | * 36 | * @var string; 37 | */ 38 | public static $currentRequestHostPlaceholder = '__SANCTUM_CURRENT_REQUEST_HOST__'; 39 | 40 | /** 41 | * Get the current application URL from the "APP_URL" environment variable - with port. 42 | * 43 | * @return string 44 | */ 45 | public static function currentApplicationUrlWithPort() 46 | { 47 | $appUrl = config('app.url'); 48 | 49 | return $appUrl ? ','.parse_url($appUrl, PHP_URL_HOST).(parse_url($appUrl, PHP_URL_PORT) ? ':'.parse_url($appUrl, PHP_URL_PORT) : '') : ''; 50 | } 51 | 52 | /** 53 | * Get a fixed token instructing Sanctum to include the current request host in the list of stateful domains. 54 | * 55 | * @return string 56 | */ 57 | public static function currentRequestHost() 58 | { 59 | return ','.static::$currentRequestHostPlaceholder; 60 | } 61 | 62 | /** 63 | * Set the current user for the application with the given abilities. 64 | * 65 | * @param \Illuminate\Contracts\Auth\Authenticatable|\Laravel\Sanctum\HasApiTokens $user 66 | * @param array $abilities 67 | * @param string $guard 68 | * @return \Illuminate\Contracts\Auth\Authenticatable 69 | */ 70 | public static function actingAs($user, $abilities = [], $guard = 'sanctum') 71 | { 72 | $token = Mockery::mock(self::personalAccessTokenModel())->shouldIgnoreMissing(false); 73 | 74 | if (in_array('*', $abilities)) { 75 | $token->shouldReceive('can')->withAnyArgs()->andReturn(true); 76 | } else { 77 | foreach ($abilities as $ability) { 78 | $token->shouldReceive('can')->with($ability)->andReturn(true); 79 | } 80 | } 81 | 82 | $user->withAccessToken($token); 83 | 84 | if (isset($user->wasRecentlyCreated) && $user->wasRecentlyCreated) { 85 | $user->wasRecentlyCreated = false; 86 | } 87 | 88 | app('auth')->guard($guard)->setUser($user); 89 | 90 | app('auth')->shouldUse($guard); 91 | 92 | return $user; 93 | } 94 | 95 | /** 96 | * Set the personal access token model name. 97 | * 98 | * @param class-string $model 99 | * @return void 100 | */ 101 | public static function usePersonalAccessTokenModel($model) 102 | { 103 | static::$personalAccessTokenModel = $model; 104 | } 105 | 106 | /** 107 | * Specify a callback that should be used to fetch the access token from the request. 108 | * 109 | * @param callable|null $callback 110 | * @return void 111 | */ 112 | public static function getAccessTokenFromRequestUsing(?callable $callback) 113 | { 114 | static::$accessTokenRetrievalCallback = $callback; 115 | } 116 | 117 | /** 118 | * Specify a callback that should be used to authenticate access tokens. 119 | * 120 | * @param callable $callback 121 | * @return void 122 | */ 123 | public static function authenticateAccessTokensUsing(callable $callback) 124 | { 125 | static::$accessTokenAuthenticationCallback = $callback; 126 | } 127 | 128 | /** 129 | * Get the token model class name. 130 | * 131 | * @return class-string 132 | */ 133 | public static function personalAccessTokenModel() 134 | { 135 | return static::$personalAccessTokenModel; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/SanctumServiceProvider.php: -------------------------------------------------------------------------------- 1 | array_merge([ 25 | 'driver' => 'sanctum', 26 | 'provider' => null, 27 | ], config('auth.guards.sanctum', [])), 28 | ]); 29 | 30 | if (! app()->configurationIsCached()) { 31 | $this->mergeConfigFrom(__DIR__.'/../config/sanctum.php', 'sanctum'); 32 | } 33 | } 34 | 35 | /** 36 | * Bootstrap any application services. 37 | * 38 | * @return void 39 | */ 40 | public function boot() 41 | { 42 | if (app()->runningInConsole()) { 43 | $this->publishesMigrations([ 44 | __DIR__.'/../database/migrations' => database_path('migrations'), 45 | ], 'sanctum-migrations'); 46 | 47 | $this->publishes([ 48 | __DIR__.'/../config/sanctum.php' => config_path('sanctum.php'), 49 | ], 'sanctum-config'); 50 | 51 | $this->commands([ 52 | PruneExpired::class, 53 | ]); 54 | } 55 | 56 | $this->defineRoutes(); 57 | $this->configureGuard(); 58 | $this->configureMiddleware(); 59 | } 60 | 61 | /** 62 | * Define the Sanctum routes. 63 | * 64 | * @return void 65 | */ 66 | protected function defineRoutes() 67 | { 68 | if (app()->routesAreCached() || config('sanctum.routes') === false) { 69 | return; 70 | } 71 | 72 | Route::group(['prefix' => config('sanctum.prefix', 'sanctum')], function () { 73 | Route::get( 74 | '/csrf-cookie', 75 | CsrfCookieController::class.'@show' 76 | )->middleware('web')->name('sanctum.csrf-cookie'); 77 | }); 78 | } 79 | 80 | /** 81 | * Configure the Sanctum authentication guard. 82 | * 83 | * @return void 84 | */ 85 | protected function configureGuard() 86 | { 87 | Auth::resolved(function ($auth) { 88 | $auth->extend('sanctum', function ($app, $name, array $config) use ($auth) { 89 | return tap($this->createGuard($auth, $config), function ($guard) { 90 | app()->refresh('request', $guard, 'setRequest'); 91 | }); 92 | }); 93 | }); 94 | } 95 | 96 | /** 97 | * Register the guard. 98 | * 99 | * @param \Illuminate\Contracts\Auth\Factory $auth 100 | * @param array $config 101 | * @return RequestGuard 102 | */ 103 | protected function createGuard($auth, $config) 104 | { 105 | return new RequestGuard( 106 | new Guard($auth, config('sanctum.expiration'), $config['provider']), 107 | request(), 108 | $auth->createUserProvider($config['provider'] ?? null) 109 | ); 110 | } 111 | 112 | /** 113 | * Configure the Sanctum middleware and priority. 114 | * 115 | * @return void 116 | */ 117 | protected function configureMiddleware() 118 | { 119 | $kernel = app()->make(Kernel::class); 120 | 121 | $kernel->prependToMiddlewarePriority(EnsureFrontendRequestsAreStateful::class); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/TransientToken.php: -------------------------------------------------------------------------------- 1 |