8889841cREADME.md000066600000003020150514451630006026 0ustar00

Logo Laravel Sanctum

Build Status Total Downloads Latest Stable Version License

## Introduction Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs. ## Official Documentation Documentation for Sanctum can be found on the [Laravel website](https://laravel.com/docs/sanctum). ## Contributing Thank you for considering contributing to Sanctum! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). ## Code of Conduct 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). ## Security Vulnerabilities Please review [our security policy](https://github.com/laravel/sanctum/security/policy) on how to report security vulnerabilities. ## License Laravel Sanctum is open-sourced software licensed under the [MIT license](LICENSE.md). LICENSE.md000066600000002063150514451630006161 0ustar00The MIT License (MIT) Copyright (c) Taylor Otwell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. config/sanctum.php000066600000004366150514451630010215 0ustar00 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' => App\Http\Middleware\VerifyCsrfToken::class, 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, ], ]; database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php000066600000001554150514451630022514 0ustar00bigIncrements('id'); $table->morphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); $table->timestamp('last_used_at')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('personal_access_tokens'); } } src/SanctumServiceProvider.php000066600000007027150514451630012530 0ustar00 array_merge([ 'driver' => 'sanctum', 'provider' => null, ], config('auth.guards.sanctum', [])), ]); if (! app()->configurationIsCached()) { $this->mergeConfigFrom(__DIR__.'/../config/sanctum.php', 'sanctum'); } } /** * Bootstrap any application services. * * @return void */ public function boot() { if (app()->runningInConsole()) { $this->registerMigrations(); $this->publishes([ __DIR__.'/../database/migrations' => database_path('migrations'), ], 'sanctum-migrations'); $this->publishes([ __DIR__.'/../config/sanctum.php' => config_path('sanctum.php'), ], 'sanctum-config'); $this->commands([ PruneExpired::class, ]); } $this->defineRoutes(); $this->configureGuard(); $this->configureMiddleware(); } /** * Register Sanctum's migration files. * * @return void */ protected function registerMigrations() { if (Sanctum::shouldRunMigrations()) { return $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); } } /** * Define the Sanctum routes. * * @return void */ protected function defineRoutes() { if (app()->routesAreCached() || config('sanctum.routes') === false) { return; } Route::group(['prefix' => config('sanctum.prefix', 'sanctum')], function () { Route::get( '/csrf-cookie', CsrfCookieController::class.'@show' )->middleware('web'); }); } /** * Configure the Sanctum authentication guard. * * @return void */ protected function configureGuard() { Auth::resolved(function ($auth) { $auth->extend('sanctum', function ($app, $name, array $config) use ($auth) { return tap($this->createGuard($auth, $config), function ($guard) { app()->refresh('request', $guard, 'setRequest'); }); }); }); } /** * Register the guard. * * @param \Illuminate\Contracts\Auth\Factory $auth * @param array $config * @return RequestGuard */ protected function createGuard($auth, $config) { return new RequestGuard( new Guard($auth, config('sanctum.expiration'), $config['provider']), request(), $auth->createUserProvider($config['provider'] ?? null) ); } /** * Configure the Sanctum middleware and priority. * * @return void */ protected function configureMiddleware() { $kernel = app()->make(Kernel::class); $kernel->prependToMiddlewarePriority(EnsureFrontendRequestsAreStateful::class); } } src/Events/TokenAuthenticated.php000066600000000672150514451630013110 0ustar00token = $token; } } src/Contracts/HasAbilities.php000066600000000623150514451630012356 0ustar00expectsJson()) { return new JsonResponse(null, 204); } return new Response('', 204); } } src/Http/Middleware/CheckAbilities.php000066600000001650150514451630013715 0ustar00user() || ! $request->user()->currentAccessToken()) { throw new AuthenticationException; } foreach ($abilities as $ability) { if (! $request->user()->tokenCan($ability)) { throw new MissingAbilityException($ability); } } return $next($request); } } src/Http/Middleware/CheckForAnyAbility.php000066600000001654150514451630014530 0ustar00user() || ! $request->user()->currentAccessToken()) { throw new AuthenticationException; } foreach ($abilities as $ability) { if ($request->user()->tokenCan($ability)) { return $next($request); } } throw new MissingAbilityException($abilities); } } src/Http/Middleware/CheckForAnyScope.php000066600000001530150514451630014175 0ustar00handle($request, $next, ...$scopes); } catch (\Laravel\Sanctum\Exceptions\MissingAbilityException $e) { throw new MissingScopeException($e->abilities()); } } } src/Http/Middleware/CheckScopes.php000066600000001513150514451630013242 0ustar00handle($request, $next, ...$scopes); } catch (\Laravel\Sanctum\Exceptions\MissingAbilityException $e) { throw new MissingScopeException($e->abilities()); } } } src/Http/Middleware/EnsureFrontendRequestsAreStateful.php000066600000004345150514451630017713 0ustar00configureSecureCookieSessions(); return (new Pipeline(app()))->send($request)->through(static::fromFrontend($request) ? [ function ($request, $next) { $request->attributes->set('sanctum', true); return $next($request); }, config('sanctum.middleware.encrypt_cookies', \Illuminate\Cookie\Middleware\EncryptCookies::class), \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, config('sanctum.middleware.verify_csrf_token', \Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class), ] : [])->then(function ($request) use ($next) { return $next($request); }); } /** * Configure secure cookie sessions. * * @return void */ protected function configureSecureCookieSessions() { config([ 'session.http_only' => true, 'session.same_site' => 'lax', ]); } /** * Determine if the given request is from the first-party application frontend. * * @param \Illuminate\Http\Request $request * @return bool */ public static function fromFrontend($request) { $domain = $request->headers->get('referer') ?: $request->headers->get('origin'); if (is_null($domain)) { return false; } $domain = Str::replaceFirst('https://', '', $domain); $domain = Str::replaceFirst('http://', '', $domain); $domain = Str::endsWith($domain, '/') ? $domain : "{$domain}/"; $stateful = array_filter(config('sanctum.stateful', [])); return Str::is(Collection::make($stateful)->map(function ($uri) { return trim($uri).'/*'; })->all(), $domain); } } src/TransientToken.php000066600000001015150514451630011021 0ustar00auth = $auth; $this->expiration = $expiration; $this->provider = $provider; } /** * Retrieve the authenticated user for the incoming request. * * @param \Illuminate\Http\Request $request * @return mixed */ public function __invoke(Request $request) { foreach (Arr::wrap(config('sanctum.guard', 'web')) as $guard) { if ($user = $this->auth->guard($guard)->user()) { return $this->supportsTokens($user) ? $user->withAccessToken(new TransientToken) : $user; } } if ($token = $this->getTokenFromRequest($request)) { $model = Sanctum::$personalAccessTokenModel; $accessToken = $model::findToken($token); if (! $this->isValidAccessToken($accessToken) || ! $this->supportsTokens($accessToken->tokenable)) { return; } $tokenable = $accessToken->tokenable->withAccessToken( $accessToken ); event(new TokenAuthenticated($accessToken)); if (method_exists($accessToken->getConnection(), 'hasModifiedRecords') && method_exists($accessToken->getConnection(), 'setRecordModificationState')) { tap($accessToken->getConnection()->hasModifiedRecords(), function ($hasModifiedRecords) use ($accessToken) { $accessToken->forceFill(['last_used_at' => now()])->save(); $accessToken->getConnection()->setRecordModificationState($hasModifiedRecords); }); } else { $accessToken->forceFill(['last_used_at' => now()])->save(); } return $tokenable; } } /** * Determine if the tokenable model supports API tokens. * * @param mixed $tokenable * @return bool */ protected function supportsTokens($tokenable = null) { return $tokenable && in_array(HasApiTokens::class, class_uses_recursive( get_class($tokenable) )); } /** * Get the token from the request. * * @param \Illuminate\Http\Request $request * @return string|null */ protected function getTokenFromRequest(Request $request) { if (is_callable(Sanctum::$accessTokenRetrievalCallback)) { return (string) (Sanctum::$accessTokenRetrievalCallback)($request); } return $request->bearerToken(); } /** * Determine if the provided access token is valid. * * @param mixed $accessToken * @return bool */ protected function isValidAccessToken($accessToken): bool { if (! $accessToken) { return false; } $isValid = (! $this->expiration || $accessToken->created_at->gt(now()->subMinutes($this->expiration))) && $this->hasValidProvider($accessToken->tokenable); if (is_callable(Sanctum::$accessTokenAuthenticationCallback)) { $isValid = (bool) (Sanctum::$accessTokenAuthenticationCallback)($accessToken, $isValid); } return $isValid; } /** * Determine if the tokenable model matches the provider's model type. * * @param \Illuminate\Database\Eloquent\Model $tokenable * @return bool */ protected function hasValidProvider($tokenable) { if (is_null($this->provider)) { return true; } $model = config("auth.providers.{$this->provider}.model"); return $tokenable instanceof $model; } } src/Exceptions/MissingAbilityException.php000066600000001502150514451630015001 0ustar00abilities = Arr::wrap($abilities); } /** * Get the abilities that the user did not have. * * @return array */ public function abilities() { return $this->abilities; } } src/Exceptions/MissingScopeException.php000066600000001446150514451630014464 0ustar00scopes = Arr::wrap($scopes); } /** * Get the scopes that the user did not have. * * @return array */ public function scopes() { return $this->scopes; } } src/NewAccessToken.php000066600000002427150514451630010735 0ustar00accessToken = $accessToken; $this->plainTextToken = $plainTextToken; } /** * Get the instance as an array. * * @return array */ public function toArray() { return [ 'accessToken' => $this->accessToken, 'plainTextToken' => $this->plainTextToken, ]; } /** * Convert the object to its JSON representation. * * @param int $options * @return string */ public function toJson($options = 0) { return json_encode($this->toArray(), $options); } } src/PersonalAccessToken.php000066600000003724150514451630011770 0ustar00 'json', 'last_used_at' => 'datetime', ]; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'name', 'token', 'abilities', ]; /** * The attributes that should be hidden for serialization. * * @var array */ protected $hidden = [ 'token', ]; /** * Get the tokenable model that the access token belongs to. * * @return \Illuminate\Database\Eloquent\Relations\MorphTo */ public function tokenable() { return $this->morphTo('tokenable'); } /** * Find the token instance matching the given token. * * @param string $token * @return static|null */ public static function findToken($token) { if (strpos($token, '|') === false) { return static::where('token', hash('sha256', $token))->first(); } [$id, $token] = explode('|', $token, 2); if ($instance = static::find($id)) { return hash_equals($instance->token, hash('sha256', $token)) ? $instance : null; } } /** * Determine if the token has a given ability. * * @param string $ability * @return bool */ public function can($ability) { return in_array('*', $this->abilities) || array_key_exists($ability, array_flip($this->abilities)); } /** * Determine if the token is missing a given ability. * * @param string $ability * @return bool */ public function cant($ability) { return ! $this->can($ability); } } src/Console/Commands/PruneExpired.php000066600000002205150514451630013630 0ustar00option('hours'); $model::where('created_at', '<', now()->subMinutes($expiration + ($hours * 60)))->delete(); $this->info("Tokens expired for more than {$hours} hours pruned successfully."); return 0; } $this->warn('Expiration value not specified in configuration file.'); return 1; } } src/Sanctum.php000066600000007160150514451630007472 0ustar00shouldIgnoreMissing(false); if (in_array('*', $abilities)) { $token->shouldReceive('can')->withAnyArgs()->andReturn(true); } else { foreach ($abilities as $ability) { $token->shouldReceive('can')->with($ability)->andReturn(true); } } $user->withAccessToken($token); if (isset($user->wasRecentlyCreated) && $user->wasRecentlyCreated) { $user->wasRecentlyCreated = false; } app('auth')->guard($guard)->setUser($user); app('auth')->shouldUse($guard); return $user; } /** * Set the personal access token model name. * * @param string $model * @return void */ public static function usePersonalAccessTokenModel($model) { static::$personalAccessTokenModel = $model; } /** * Specify a callback that should be used to fetch the access token from the request. * * @param callable $callback * @return void */ public static function getAccessTokenFromRequestUsing(callable $callback) { static::$accessTokenRetrievalCallback = $callback; } /** * Specify a callback that should be used to authenticate access tokens. * * @param callable $callback * @return void */ public static function authenticateAccessTokensUsing(callable $callback) { static::$accessTokenAuthenticationCallback = $callback; } /** * Determine if Sanctum's migrations should be run. * * @return bool */ public static function shouldRunMigrations() { return static::$runsMigrations; } /** * Configure Sanctum to not register its migrations. * * @return static */ public static function ignoreMigrations() { static::$runsMigrations = false; return new static; } /** * Get the token model class name. * * @return string */ public static function personalAccessTokenModel() { return static::$personalAccessTokenModel; } } src/HasApiTokens.php000066600000003506150514451630010411 0ustar00morphMany(Sanctum::$personalAccessTokenModel, 'tokenable'); } /** * Determine if the current API token has a given scope. * * @param string $ability * @return bool */ public function tokenCan(string $ability) { return $this->accessToken && $this->accessToken->can($ability); } /** * Create a new personal access token for the user. * * @param string $name * @param array $abilities * @return \Laravel\Sanctum\NewAccessToken */ public function createToken(string $name, array $abilities = ['*']) { $token = $this->tokens()->create([ 'name' => $name, 'token' => hash('sha256', $plainTextToken = Str::random(40)), 'abilities' => $abilities, ]); return new NewAccessToken($token, $token->getKey().'|'.$plainTextToken); } /** * Get the access token currently associated with the user. * * @return \Laravel\Sanctum\Contracts\HasAbilities */ public function currentAccessToken() { return $this->accessToken; } /** * Set the current access token for the user. * * @param \Laravel\Sanctum\Contracts\HasAbilities $accessToken * @return $this */ public function withAccessToken($accessToken) { $this->accessToken = $accessToken; return $this; } } composer.json000066600000002674150514451630007307 0ustar00{ "name": "laravel/sanctum", "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", "keywords": ["laravel", "sanctum", "auth"], "license": "MIT", "support": { "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, "authors": [ { "name": "Taylor Otwell", "email": "taylor@laravel.com" } ], "require": { "php": "^7.2|^8.0", "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" }, "require-dev": { "mockery/mockery": "^1.0", "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0", "phpunit/phpunit": "^8.0|^9.3" }, "autoload": { "psr-4": { "Laravel\\Sanctum\\": "src/" } }, "autoload-dev": { "psr-4": { "Laravel\\Sanctum\\Tests\\": "tests/" } }, "extra": { "branch-alias": { "dev-master": "2.x-dev" }, "laravel": { "providers": [ "Laravel\\Sanctum\\SanctumServiceProvider" ] } }, "config": { "sort-packages": true }, "minimum-stability": "dev", "prefer-stable": true }