├── 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 |

2 |
3 |
4 |
5 |
6 |
7 |
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 |