Repository: codecasts/laravel-jwt Branch: master Commit: 3e26ca60041e Files: 19 Total size: 51.4 KB Directory structure: gitextract_u6zs6ejr/ ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── composer.json ├── config/ │ └── jwt.php ├── phpunit.xml ├── readme.md ├── sonar-project.properties ├── src/ │ ├── Auth/ │ │ ├── Guard.php │ │ └── ServiceProvider.php │ ├── Console/ │ │ └── KeyGenerateCommand.php │ ├── Contracts/ │ │ ├── Auth/ │ │ │ └── Guard.php │ │ └── Token/ │ │ └── Manager.php │ ├── ServiceProvider.php │ └── Token/ │ └── Manager.php └── tests/ ├── Auth/ │ └── GuardTest.php └── TestCase.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - master pull_request: types: [opened, synchronize, reopened] jobs: sonarcloud: name: SonarCloud runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ================================================ FILE: .gitignore ================================================ composer.lock /vendor ================================================ FILE: .travis.yml ================================================ language: php sudo: false git: depth: 2 matrix: include: - php: 7.0 - php: 7.1 - php: 7.2 - php: nightly fast_finish: true cache: directories: - $HOME/.composer/cache before_script: - phpenv config-rm xdebug.ini || true - travis_retry composer self-update - travis_retry composer install --no-interaction script: - composer test ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Contributions are **welcome** and will be fully **credited**. We accept contributions via Pull Requests on [Github](https://github.com/codecasts/laravel-jwt). ## Pull Requests - Follow the **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)**. - **Add tests!** - Your patch won't be accepted if it doesn't have tests. - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date. - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. - **Create feature branches** - Don't ask us to pull from your master branch. - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. - **Branch** - This is a **GIT FLOW** enabled repository, so please send your pull requests to our `develop` branch. ## Running Tests ``` bash $ composer test ``` **Happy coding**! ================================================ FILE: LICENSE.txt ================================================ MIT License Copyright (c) 2017 Diego Hernandes 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. ================================================ FILE: composer.json ================================================ { "name": "codecasts/laravel-jwt", "description": "Dead simple JWT Auth Provider for Laravel 5.4+", "type": "library", "license": "MIT", "authors": [ { "name": "Diego Hernandes", "email": "diego@hernandev.com" } ], "autoload": { "psr-4": { "Codecasts\\Auth\\JWT\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "minimum-stability": "stable", "require": { "php": ">=7.0.0", "ext-openssl": "*", "lcobucci/jwt": "^3.2", "illuminate/support": ">=5.4.0" }, "require-dev": { "orchestra/testbench": "^3.4", "phpunit/phpunit": "^6.1", "mockery/mockery": "^0.9.9" }, "extra": { "laravel": { "providers": [ "Codecasts\\Auth\\JWT\\ServiceProvider" ] } } } ================================================ FILE: config/jwt.php ================================================ env('JWT_SECRET', null), /* |-------------------------------------------------------------------------- | JWT Time to live (TTL) |-------------------------------------------------------------------------- | | Use an Integer value in minutes. | | This will set how long will take for a key to be considered expired. | In other words, this is the amount of time, in minutes, that will be | added to the current time that the token is being issue and set | ont the 'exp' claim. | */ 'ttl' => 60, /* |-------------------------------------------------------------------------- | Expired Token Refresh Limit |-------------------------------------------------------------------------- | | Use an Integer value in minutes. | | This will set how long after the expiration date a token can be refreshed. | You can set it to 0 if you DO NOT want expired tokens to refreshed. | | The default limit of 7200 (minutes) takes in account mobile apps where the | logged used may take a few days before using your "app" again. | | In those cases, the token would have been expired but a new one could still | be issued, unless the limit configured here has already passed. | | If you're considering a not secure client platform, short values here reduces | the odds of a leaked token be used to generate a new one. | */ 'refresh_limit' => 7200, /* |-------------------------------------------------------------------------- | Auto Guard Binding By Middleware Match |-------------------------------------------------------------------------- | | Useful when your application has more than 1 guard, and the JWT powered one | is not the default. | | If your application is a API only, you can safely set this to false. | | Setting it to false without having JWT guard as default will make | mandatory using the suffix :guardName when using the 'auth' middleware. | */ 'middleware_match' => true, ]; ================================================ FILE: phpunit.xml ================================================ tests/ src/ ================================================ FILE: readme.md ================================================ ![Readme Art](http://imageshack.com/a/img922/4489/tftxQ1.png) # Laravel JWT [![Latest Stable Version](https://poser.pugx.org/codecasts/laravel-jwt/v/stable)](https://packagist.org/packages/codecasts/laravel-jwt) [![Total Downloads](https://poser.pugx.org/codecasts/laravel-jwt/downloads)](https://packagist.org/packages/codecasts/laravel-jwt) [![License](https://poser.pugx.org/codecasts/laravel-jwt/license)](https://packagist.org/packages/codecasts/laravel-jwt) This package provides out-of-the-box API authentication using JWT for Laravel. ## Installation. You can install this package by running: ```bash composer require codecasts/laravel-jwt ``` ## Setup. In order to setup this package into your application, minimal configuration is actually needed. ### 1) Service Provider. Register this package's Service Provider by adding it to the `providers` section of your `config/app.php` file: > You may skip this step on Laravel 5.5 due to the [auto-discovery package feature](https://laravel.com/docs/5.5/packages#package-discovery). ```php 'providers' => [ // ... other providers omitted Codecasts\Auth\JWT\ServiceProvider::class, ], ``` ### 2) Configuration file. Publish the configuration file (`config/jwt.php`) by running the following command after registering the Service Provider. ```bash php artisan vendor:publish --provider="Codecasts\Auth\JWT\ServiceProvider" ``` ### 3) Generate a Secret. In order for this package to works, you will need a separate secret (do not use the application key). This package provides a command that can be used for generating a strong key. Get a new key by running: ```bash php artisan jwt:generate ``` Then, copy the generated key contents into your `.env` file. **NOTICE**: The key generation process will not automatically set it inside your `.env` file, do it manually. ### 4) Setup Guard In order to automatically authenticate your routes using `JWT` tokens, you need to change the guard driver to `jwt` Inside `config/auth.php` set the corresponding guard group you want to protect: If you have the default guard group named `api`, your `auth.php` should be like this: ```php 'guards' => [ // ... other guards omitted. 'api' => [ 'driver' => 'jwt', // this is the line you need to change. 'provider' => 'users', ], ], ``` That's it, we are all ready to use it. ## Usage. This package aims to be dead simple to use. The following templates can be used to setup your existing authentication controllers and resources. **NOTICE**: Full working examples of use for this package will be added on this package when it reaches it's 1.0 version. ### Protecting Routes. This package is fully integrated with Laravel Authentication. The default configuration (`config/jwt.php`) brings a sensitive value that is very useful when your application is not completely an API: **`middleware_match`** By not completely an API, I mean, the JWT guard is not the default one. In those cases, in order to use the `auth` middleware, the config key **`middleware_match`** **MUST** be set to true. This configuration key allows non protected routes to work properly. Notice that this option will match middleware group names with guard names. **In this case, the 'api' middleware group will always use the `api` guard.** **Also, the 'web' middleware group will always use the `web` guard** If you do not use this value, you will need to use suffixes when referencing the `auth` middleware, like `auth:api`. ### Issuing and Renewing Tokens. For issuing tokens, no special class is actually needed, you can just expect create a Guard current implementation from the IoC and work from there. Check out the examples. **On the following examples, all Guard instances are injected from `Illuminate\Contracts\Auth\Guard`** **On the following examples, all Request instances are injected from `Illuminate\Http\Request`** #### Token from User Instance. This method should be used when you just registered a user and any other special cases. ```php public function tokenFromUser(Guard $auth) { // generating a token from a given user. $user = SomeUserModel::find(12); // logs in the user $auth->login($user); // get and return a new token $token = $auth->issue(); return $token; } ``` #### Token from User Credentials. This method should be used when you just registered a user and any other special cases. ```php public function tokenFromCredentials(Guard $auth, Request $request) { // get some credentials $credentials = $request->only(['email', 'password']); if ($auth->attempt($credentials)) { return $token = $auth->issue(); } return ['Invalid Credentials']; } ``` #### Refreshing Tokens. Tokens can be refreshed in 2 different ways: Auto detect or manual. If you do not pass any argument into the refresh method, the Guard will look for either a **`Authorization`** header or a **`token`** field on the request's body. ```php public function refreshToken(Guard $auth) { // auto detecting token from request. $token = $auth->refresh(); // manually passing the token to be refreshed. $token = $auth->refresh($oldToken); return $token; } ``` ### Custom Claims. Of course, there are support for custom claims. You can set them in two ways. #### By explicitly passing them. ```php $customClaims = [ 'custom1' => 'value1', 'custom2' => 'value2', ]; // when issuing $auth->issue($customClaims); // when refreshing // custom claims are the second parameter as the first one is the // old token $auth->refresh(null, $customClaims); ``` #### By Authenticatable method. If all your users will have the same custom claims, you can setup a default custom claims method on your User's model (or any other Authenticatable you're using): If the method `customJWTClaims()` is present on the model being issue the token against, this claims will be automatically included. ```php class User extends Model implements Authenticatable { public function customJWTClaims() { return [ 'email' => $this->email, 'name' => $this->name, ]; } } ``` ## Contributing Please see [CONTRIBUTING](CONTRIBUTING.md) for details. ================================================ FILE: sonar-project.properties ================================================ sonar.projectKey=codecasts_laravel-jwt sonar.organization=codecasts # This is the name and version displayed in the SonarCloud UI. #sonar.projectName=laravel-jwt #sonar.projectVersion=1.0 # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. #sonar.sources=. # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 ================================================ FILE: src/Auth/Guard.php ================================================ app = $app; $this->name = $name; $this->provider = $provider; $this->manager = $manager; } /** * Fire the attempt event with the arguments. * * @param array $credentials * @return void */ protected function fireAttemptEvent(array $credentials) { if (isset($this->events)) { $this->events->dispatch(new Attempting( $credentials, false )); } } /** * Fire the failed authentication attempt event with the given arguments. * * @param \Illuminate\Contracts\Auth\Authenticatable|null $user * @param array $credentials * @return void */ protected function fireFailedEvent($user, array $credentials) { if (isset($this->events)) { $this->events->dispatch(new Failed($user, $credentials)); } } /** * Fire the login event if the dispatcher is set. * * @param \Illuminate\Contracts\Auth\Authenticatable $user * * @return void */ protected function fireLoginEvent($user) { if (isset($this->events)) { $this->events->dispatch(new Login($user, false)); } } /** * Validate a user's credentials. * * @param array $credentials * @return bool */ public function validate(array $credentials = []) { $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); return $this->hasValidCredentials($user, $credentials); } /** * Attempt to authenticate a user using the given credentials. * * @param array $credentials * @return bool */ public function attempt(array $credentials = []) { $this->fireAttemptEvent($credentials); $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); // If an implementation of UserInterface was returned, we'll ask the provider // to validate the user against the given credentials, and if they are in // fact valid we'll log the users into the application and return true. if ($this->hasValidCredentials($user, $credentials)) { $this->login($user); return true; } // If the authentication attempt fails we will fire an event so that the user // may be notified of any suspicious attempts to access their account from // an unrecognized user. A developer may listen to this event as needed. $this->fireFailedEvent($user, $credentials); return false; } /** * Determine if the user matches the credentials. * * @param mixed $user * @param array $credentials * @return bool */ protected function hasValidCredentials($user, $credentials) { return ! is_null($user) && $this->provider->validateCredentials($user, $credentials); } /** * Login a given user. It means, generate a new token for a user. * * @param Authenticatable $user * @return mixed */ public function login(Authenticatable $user) { // If we have an event dispatcher instance set we will fire an event so that // any listeners will hook into the authentication events and run actions // based on the login and logout events fired from the guard instances. $this->fireLoginEvent($user); $this->setUser($user); } /** * Parse the request looking for the authorization header. * * @return null|string */ protected function getTokenFromHeader() { // if there is no authorization header present. if (!$this->getRequest()->headers->has('Authorization')) { // abort by returning null. return null; } // gets the full header string. $header = $this->getRequest()->headers->get('Authorization'); // returns the token without the 'Bearer ' prefix. return Str::replaceFirst('Bearer ', '', $header); } /** * Parse the request looking a token as parameter. * * @return null|string */ protected function getTokenFromParameter() { if (!$this->getRequest()->has('token')) { // abort by returning null. return null; } // return the request token. return $this->getRequest()->get('token', null); } /** * @return Token|null */ protected function detectedToken() { // retrieve the token from request. $detectedToken = $this->getTokenFromHeader() ?? $this->getTokenFromParameter(); if ($detectedToken) { // update the currently used token $this->token = $this->manager()->parseToken($detectedToken); } // return current token in use return $this->token; } /** * Retrieves the user by it's token. * * @param Token $token * @return Authenticatable|null */ protected function findUserByToken(Token $token) { // retrieves the user ID from the token. $id = $token->getClaim('sub'); // use the users provider to find the token subject (user) // but it's id (subject) return $this->provider->retrieveById($id); } /** * Get / Detect the currently authenticated user. * * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function user() { // if the user was explicitly marked as logged out. if ($this->loggedOut) { // just return null. return null; } // if the user is already set. if ($this->user) { return $this->user; } // detects a token presence. $token = $this->detectedToken(); // if the received token is not actually valid. if (!$token || !$this->manager->validToken($token)) { // also return null since the token // signature could not be determined. return null; } // if the token has expired. if ($this->manager->expired($token)) { // you got right? return null; } // try to find the user which the token belongs to. $user = $this->findUserByToken($token); // if the user has not been found. if (!$user) { // abort! return null; } // set the current user on the scope. $this->setUser($user); // return the scope user. return $this->user; } /** * Log the user out of the application. * * @return void */ public function logout() { $user = $this->user(); // blacklist the user token. $this->loggedOut = true; } /** * Returns the guard instance of the token manager. * * @return \Codecasts\Auth\JWT\Contracts\Token\Manager */ public function manager() { return $this->manager; } /** * Issue a token for the current authenticated user. * * @param array $customClaims * @return bool|string */ public function issue(array $customClaims = []) { // ensure there is a user logged in. if (!$this->user) { return false; } // try to issue a new token and return. try { return $this->manager()->issue($this->user, $customClaims); } catch (\Exception $e) { // catch any exceptions that the token issuing may trigger. return false; } } /** * Refresh a given token. * * @param string $token * @param array $customClaims * @return bool|string */ public function refresh(string $token = null, array $customClaims = []) { // detect token if none was passed. $token = $token ?? $this->detectedToken(); // if no token was detected. if (!$token) { return false; } // if the token cannot be refreshed. if (!$this->manager()->canBeRenewed($token)) { // abort by returning false. return false; } // try to locate the user which the token belongs to. $user = $this->findUserByToken($token); // if not user could be found. if (!$user) { return false; } // set the user instance. $this->user = $user; // try to issue a new token and refresh try { return $this->manager()->issue($user, $customClaims); } catch (\Exception $e) { return false; } } /** * Get the event dispatcher instance. * * @return \Illuminate\Contracts\Events\Dispatcher */ public function getDispatcher() { return $this->events; } /** * Set the event dispatcher instance. * * @param \Illuminate\Contracts\Events\Dispatcher $events * @return void */ public function setDispatcher(Dispatcher $events) { $this->events = $events; } /** * Get the current request instance. * * @return \Symfony\Component\HttpFoundation\Request */ public function getRequest() { return $this->request ?: $this->app->request; } /** * Set the current request instance. * * @param \Symfony\Component\HttpFoundation\Request $request * @return $this */ public function setRequest(Request $request) { $this->request = $request; return $this; } /** * The detected JWT token. * * @return string */ public function getToken() { return $this->token; } } ================================================ FILE: src/Auth/ServiceProvider.php ================================================ authManager = $this->app->make(AuthManager::class); // register auth policies. $this->registerPolicies(); // define a "jwt" guard. $this->authManager->extend('jwt', function ($app, $name, array $config) { // gets a instance of the token manager $tokenManager = $this->getTokenManager(); // gets a instance of the user provider $userProvider = $this->getUserProvider($config['provider']); // creates a new guard instance passing a provider and a token manager $guard = new Guard($app, $name, $userProvider, $tokenManager); // set a event dispatcher on the guard. $guard->setDispatcher(resolve(Dispatcher::class)); // returns the guard instance. return new Guard($app, $name, $userProvider, $tokenManager); }); } /** * Get's the configured user provider instance. * * @param $alias * @return UserProvider */ protected function getUserProvider($alias) { return $this->authManager->createUserProvider($alias); } /** * Get's a instance of the token manager. * * @return \Codecasts\Auth\JWT\Contracts\Token\Manager */ protected function getTokenManager() { return $this->app->make(TokenManager::class); } } ================================================ FILE: src/Console/KeyGenerateCommand.php ================================================ handle(); } /** * Execute the command that will generate and print a key. */ public function handle() { // call the action to generate a new key. $key = $this->generateRandomKey(); if ($this->option('show')) { // print the success block. $this->printBlock([ 'JWT Key Generated!', 'Please Update your .env file manually with the following key:', ], 'bg=green;fg=black', true); // print the key block. return $this->printBlock([ "JWT_SECRET={$key}", ], 'bg=yellow;fg=black'); } // Next, we will replace the application key in the environment file so it is // automatically setup for this developer. This key gets generated using a // secure random byte generator and is later base64 encoded for storage. if (!$this->setKeyInEnvironmentFile($key)) { return; } $this->laravel['config']['jwt.secret'] = $key; // print the key block. $this->printBlock([ "JWT_SECRET={$key}", ], 'bg=yellow;fg=black'); } /** * Set the application key in the environment file. * * @param string $key * * @return bool */ protected function setKeyInEnvironmentFile($key) { $currentKey = $this->laravel['config']['jwt.secret']; if (0 !== strlen($currentKey) && (!$this->confirmToProceed())) { return false; } $this->writeNewEnvironmentFileWith($key); return true; } /** * Write a new environment file with the given key. * * @param string $key */ protected function writeNewEnvironmentFileWith($key) { file_put_contents($this->laravel->environmentFilePath(), preg_replace( $this->keyReplacementPattern(), 'JWT_SECRET='.$key, file_get_contents($this->laravel->environmentFilePath()) )); } /** * Generates a random, 32 bytes long, base64 encoded key. * * @return string */ protected function generateRandomKey() { return 'base64:'.base64_encode(random_bytes(32)); } /** * Prints a text block into console output. * * @param array $lines * @param $style * @param bool $firstBlock */ protected function printBlock(array $lines, $style, $firstBlock = false) { /** @var FormatterHelper $formatter */ $formatter = $this->getHelper('formatter'); // just to satisfy my obsessive needs. if ($firstBlock) { // prints an empty line at the begging of output. $this->line(''); } // merge argument lines with an empty line before and after. $spacedLines = array_merge([''], array_merge($lines, [''])); // generate a text block. $block = $formatter->formatBlock($spacedLines, $style); // print it. $this->line($block); // empty ending line. $this->line(''); } /** * Get a regex pattern that will match env APP_KEY with any random key. * * @return string */ protected function keyReplacementPattern() { $escaped = preg_quote('='.$this->laravel['config']['jwt.secret'], '/'); return "/^JWT_SECRET{$escaped}/m"; } } ================================================ FILE: src/Contracts/Auth/Guard.php ================================================ publishes([ __DIR__.'/../config/jwt.php' => config_path('jwt.php') ]); // case enabled, setups a guard match by middleware group name. $this->setupGuardMiddlewareMatch(); } public function register() { // register contract/concrete bindings. $this->registerBindings(); // register commands. $this->registerCommands(); // register the "auth" service provider. // this is needed in order because that service // provider needs to inherit from the Laravel // default service provider, which register policies // and other resources that are not possible (at least harder) // to do in a common service provider. $this->app->register(AuthServiceProvider::class); } /** * Binds Contracts (interfaces) and Concretes (implementations) together. */ protected function registerBindings() { // bind the manager class. $this->app->bind(Contracts\Token\Manager::class, Manager::class); // bind the guard class. $this->app->bind(Contracts\Auth\Guard::class, Guard::class); } /** * Register console commands this package provides. */ protected function registerCommands() { $this->commands([ // "jwt:generate" command (generates keys). KeyGenerateCommand::class, ]); } /** * Setup the current guard to be matched by route middleware name. */ protected function setupGuardMiddlewareMatch() { // should the middleware group match the guard name? $middlewareMatch = $this->app['config']->get('jwt.middleware_match', true); if ($middlewareMatch) { // when the route is actually matched... $this->app['router']->matched(function (RouteMatched $event) { // get the route middleware group. $middlewareGroup = Arr::first((array) $event->route->middleware()); // if there is a group detected and there is a guard that matches the middleware // group name... if ($middlewareGroup && !!$this->app['auth']->guard($middlewareGroup)) { // setup the matching guard as default. $this->app['auth']->setDefaultDriver($middlewareGroup); } }); } } } ================================================ FILE: src/Token/Manager.php ================================================ config = $config; //config repository. $this->cache = $cache; // cache repository. // setup the secret that will be used to sign keys. $this->setupSecret(); // setup other config resources. $this->setupConfig(); } /** * Setup the secret that will be used to sign keys. */ public function setupSecret() { // gets the key from config. $secret = $this->config->get('jwt.secret'); // if the secret is in a base64 format, unwrap it's value if (Str::startsWith($secret, 'base64:')) { // remove the 'base64:' part and decode the value. $secret = base64_decode(substr($secret, 7)); } // set the secret on the local scope. $this->secret = $secret; // if the secret is not valid, // throw an exception to avoid continuing. if (!$this->validSecret()) { throw new \Exception('Invalid Secret (not present or too short). Use php artisan jwt:generate to create a valid key.'); } } /** * Determines if the current secret is valid and secure enough. * Of course it will only check for size since there's no way * (that I know of) of check if a value is really random. * * @return bool */ protected function validSecret() { // this method is intentionally broken in two pieces // so the logic will be loud and clear. // is the secret empty? if (empty($this->secret)) { return false; } // created a hex representation of the secret. $hexRepresentation = bin2hex($this->secret); // check for the secret's hex representation length... // hex size is deterministic, it's a way of counting bytes from binary // ( SORT OF) // in any case, the length will double of the binary random generated secret. // 16 bytes secret will have a hex of length 32 // 32 bytes secret will have a hex of length 64 // 64 bytes secret will have a hex of length 128 // you get it right? if ((Str::length($hexRepresentation) / 2) < 16) { // the minimum secret size should be 16 // so return false in case the secret // is shorter than that. return false; } // after all this checks, declare the secret valid (finally). return true; } /** * Setup JWT configuration. */ protected function setupConfig() { // setup time to live (in minutes), defaults to 60. $this->ttl = $this->config->get('jwt.ttl', 60); // setup refresh limit (in minutes), defaults to 7200 (5 days) $this->refreshLimit = $this->config->get('jwt.refresh_limit', 7200); // set the token issuer (domain name). $this->issuer = url(''); } /** * Returns a new Signer instance. * * @return Sha256 */ protected function signer() { return app()->make($this->signerClass); } /** * Returns a new Builder instance. * * @return Builder */ protected function builder() { return app()->make($this->builderClass); } /** * Returns a new Parser instance. * * @return Parser */ protected function parser() { return app()->make($this->parserClass); } /** * Create a carbon object with current time and date. * @return Carbon */ protected function now() { return Carbon::now('UTC'); } /** * Generates a random short ID to be used as the token id. * * This id is actually used only for blacklisting the tokens, not need for * additional security. * * @return string */ protected function generateId() { return Str::random(16); } /** * @param Authenticatable $user * @param array $additionalCustomClaims * * @return string */ public function issue(Authenticatable $user, array $additionalCustomClaims = []) { // gets a new builder instance. $builder = $this->builder(); // set the issued $builder->setIssuer($this->issuer); // set the subject. $builder->setSubject($user->getAuthIdentifier()); // generate a unique id for the token, and set it to replicate as a header. $builder->setId($this->generateId(), true); // detect what time is it. $now = $this->now(); // set issue date. $builder->setIssuedAt($now->timestamp); // set the token cannot be used before now. $builder->setNotBefore($now->timestamp); // calculate and set the expiration date for the token. $expiresAt = (clone $now)->addMinutes($this->ttl); $builder->setExpiration($expiresAt->timestamp); // created a custom claim that informs the limit time // for the token to be renewed. // the refresh limit is based on ttl + limit (grace period). $refreshLimit = (clone $now)->addMinutes($this->ttl + $this->refreshLimit); $builder->set('rli', $refreshLimit->timestamp); // loop through custom claims. foreach($additionalCustomClaims as $claim => $value) { // set custom claim. $builder->set($claim, $value); } // set user object default custom claims. if (method_exists($user, 'customJWTClaims')) { // call the methods. try { // call the custom claims method. $customClaims = (array) $user->customJWTClaims(); // if the custom claims method returns a array. foreach($customClaims as $claim => $value) { $builder->set($claim, $value); } } catch(\Exception $e) { // just continue since the custom claims should // not prevent the token of being issued. } } // get the signer. $signer = $this->signer(); // sign the configure token. $builder->sign($signer, $this->secret); // gets the actual signed token as string. $token = (string) $builder->getToken(); // returns the token. return $token; } /** * Verify the signature of a given token object. * * @param Token $token * @return bool */ protected function verify(Token $token) { // the verification is made against the secret key. // this will not check of expiration, only for signature checking. return $token->verify($this->signer(), $this->secret); } /** * Method for parsing a token from a string. * * @param string $tokenString * * @return Token */ public function parseToken(string $tokenString) { // try to parse a token string. try { return $this->parser()->parse($tokenString); } catch (\Exception $e) { // if it was not possible to, return null. return null; } } /** * Detects if a given token is valid or not. * * @param Token $token * * @return bool */ public function validToken(Token $token) { return $this->verify($token); } /** * Detects if a given token is Invalid. * * @param Token $token * @return bool */ public function invalidToken(Token $token) { return !$this->validToken($token); } /** * Is the token Expired? * * @param Token $token * @return bool */ public function expired(Token $token) { return $token->isExpired(); } /** * Is the token Expired? * * @param Token $token * @return bool */ public function canBeRenewed(Token $token) { if(!$token->isExpired()) { return true; } // get the renewal limit from token. $renewalLimitTimestamp = $token->getClaim('rli', null); // if no renewal limit exists, it means // that it should not be renewed. if (!$renewalLimitTimestamp) { return false; } // created a UTC carbon object using the renewal limit. $limit = Carbon::createFromTimestampUTC($renewalLimitTimestamp); // get a UTC carbon object that represents now. $now = $this->now(); // return true if now if before the limit. return $now->lessThanOrEqualTo($limit); } } ================================================ FILE: tests/Auth/GuardTest.php ================================================