Repository: tymondesigns/jwt-auth Branch: 2.x Commit: 6c70930a9271 Files: 122 Total size: 300.0 KB Directory structure: gitextract_f_blvbi4/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE.md │ ├── stale.yml │ └── workflows/ │ └── phpunit.yml ├── .gitignore ├── .styleci.yml ├── LICENSE ├── README.md ├── composer.json ├── config/ │ └── config.php ├── docs/ │ ├── auth-guard.md │ ├── configuration.md │ ├── exception-handling.md │ ├── index.md │ ├── laravel-installation.md │ ├── lumen-installation.md │ ├── quick-start.md │ └── resources.md ├── mkdocs.yml ├── phpunit.xml.dist ├── src/ │ ├── Blacklist.php │ ├── Claims/ │ │ ├── Audience.php │ │ ├── Claim.php │ │ ├── Collection.php │ │ ├── Custom.php │ │ ├── DatetimeTrait.php │ │ ├── Expiration.php │ │ ├── Factory.php │ │ ├── IssuedAt.php │ │ ├── Issuer.php │ │ ├── JwtId.php │ │ ├── NotBefore.php │ │ └── Subject.php │ ├── Console/ │ │ └── JWTGenerateSecretCommand.php │ ├── Contracts/ │ │ ├── Claim.php │ │ ├── Http/ │ │ │ └── Parser.php │ │ ├── JWTSubject.php │ │ ├── Providers/ │ │ │ ├── Auth.php │ │ │ ├── JWT.php │ │ │ └── Storage.php │ │ └── Validator.php │ ├── Exceptions/ │ │ ├── InvalidClaimException.php │ │ ├── JWTException.php │ │ ├── PayloadException.php │ │ ├── TokenBlacklistedException.php │ │ ├── TokenExpiredException.php │ │ ├── TokenInvalidException.php │ │ └── UserNotDefinedException.php │ ├── Facades/ │ │ ├── JWTAuth.php │ │ ├── JWTFactory.php │ │ └── JWTProvider.php │ ├── Factory.php │ ├── Http/ │ │ ├── Middleware/ │ │ │ ├── Authenticate.php │ │ │ ├── AuthenticateAndRenew.php │ │ │ ├── BaseMiddleware.php │ │ │ ├── Check.php │ │ │ └── RefreshToken.php │ │ └── Parser/ │ │ ├── AuthHeaders.php │ │ ├── Cookies.php │ │ ├── InputSource.php │ │ ├── KeyTrait.php │ │ ├── LumenRouteParams.php │ │ ├── Parser.php │ │ ├── QueryString.php │ │ └── RouteParams.php │ ├── JWT.php │ ├── JWTAuth.php │ ├── JWTGuard.php │ ├── Manager.php │ ├── Payload.php │ ├── Providers/ │ │ ├── AbstractServiceProvider.php │ │ ├── Auth/ │ │ │ └── Illuminate.php │ │ ├── JWT/ │ │ │ ├── Lcobucci.php │ │ │ └── Provider.php │ │ ├── LaravelServiceProvider.php │ │ ├── LumenServiceProvider.php │ │ └── Storage/ │ │ └── Illuminate.php │ ├── Support/ │ │ ├── CustomClaims.php │ │ ├── RefreshFlow.php │ │ └── Utils.php │ ├── Token.php │ └── Validators/ │ ├── PayloadValidator.php │ ├── TokenValidator.php │ └── Validator.php └── tests/ ├── AbstractTestCase.php ├── BlacklistTest.php ├── Claims/ │ ├── ClaimTest.php │ ├── CollectionTest.php │ ├── DatetimeClaimTest.php │ ├── FactoryTest.php │ ├── IssuedAtTest.php │ └── NotBeforeTest.php ├── FactoryTest.php ├── Fixtures/ │ └── Foo.php ├── Http/ │ └── ParserTest.php ├── JWTAuthTest.php ├── JWTGuardTest.php ├── ManagerTest.php ├── Middleware/ │ ├── AbstractMiddlewareTest.php │ ├── AuthenticateAndRenewTest.php │ ├── AuthenticateTest.php │ ├── CheckTest.php │ └── RefreshTokenTest.php ├── PayloadTest.php ├── Providers/ │ ├── Auth/ │ │ └── IlluminateTest.php │ ├── JWT/ │ │ ├── LcobucciTest.php │ │ └── ProviderTest.php │ ├── Keys/ │ │ ├── id_ecdsa │ │ ├── id_ecdsa.pub │ │ ├── id_rsa │ │ └── id_rsa.pub │ └── Storage/ │ └── IlluminateTest.php ├── Stubs/ │ ├── JWTProviderStub.php │ ├── LaravelUserStub.php │ ├── TaggedStorage.php │ └── UserStub.php ├── TokenTest.php └── Validators/ ├── PayloadValidatorTest.php └── TokenValidatorTest.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] charset = utf-8 end_of_line = lf insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.yml] indent_style = space indent_size = 2 ================================================ FILE: .gitattributes ================================================ * text=auto /tests export-ignore /docs export-ignore /.codecov.yml export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore /.styleci.yml export-ignore /.travis.yml export-ignore /.github export-ignore /phpunit.xml.dist export-ignore /README.md export-ignore /mkdocs.yml export-ignore ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tymon148@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # Contributors Guide Please read and understand the contribution guide before creating an issue or pull request. ## Etiquette This project is open source, and as such, the maintainers give their free time to build and maintain the source code held within. They make the code freely available in the hope that it will be of use to other developers. It would be extremely unfair for them to suffer abuse or anger for their hard work. Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the world that developers are civilized and selfless people. It's the duty of the maintainer to ensure that all submissions to the project are of sufficient quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used. ## Viability When requesting or submitting new features, first consider whether it might be useful to others. Open source projects are used by many developers, who may have entirely different needs to your own. Think about whether or not your feature is likely to be used by other users of the project. ## Procedure Before filing an issue: - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident. - Check to make sure your feature suggestion isn't already present within the project. - Check the pull requests tab to ensure that the bug doesn't have a fix in progress. - Check the pull requests tab to ensure that the feature isn't already in progress. Before submitting a pull request: - Ensure that your submission is [viable](#viability) for the project. - Check the codebase to ensure that your feature doesn't already exist. - Check the pull requests to ensure that another person hasn't already submitted the feature or fix. ## Requirements If the project maintainer has any additional requirements, you will find them listed here. - No additional requirements. ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ ### Subject of the issue Describe your issue here. ### Your environment | Q | A | ----------------- | --- | Bug? | no / yes | New Feature? | no / yes | Framework | Laravel / Lumen | Framework version | 5.x.y | Package version | 1.x.y | PHP version | 5.x.y / 7.x.y ### Steps to reproduce Tell us how to reproduce this issue. ### Expected behaviour Tell us what should happen ### Actual behaviour Tell us what happens instead ================================================ FILE: .github/stale.yml ================================================ daysUntilStale: 60 daysUntilClose: 7 exemptLabels: - pinned - security staleLabel: stale ================================================ FILE: .github/workflows/phpunit.yml ================================================ name: PHPUnit tests on: push: release: types: - published jobs: php-tests: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: php: [8.0, 8.1, 8.2, 8.3, 8.4, 8.5] laravel: [9.*, 10.*, 11.*, 12.*, 13.*] os: [ubuntu-latest] coverage: [none] stability: [prefer-lowest, prefer-stable] include: - php: 8.5 laravel: 12.* os: ubuntu-latest coverage: xdebug stability: prefer-stable exclude: - php: 8.0 laravel: 10.* - php: 8.0 laravel: 11.* - php: 8.1 laravel: 11.* - php: 8.0 laravel: 12.* - php: 8.1 laravel: 12.* - php: 8.0 laravel: 13.* - php: 8.1 laravel: 13.* - php: 8.2 laravel: 13.* name: PHP${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} steps: - name: Checkout code uses: actions/checkout@v1 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: mbstring json openssl coverage: ${{ matrix.coverage }} ini-values: memory_limit=-1 - name: Get composer cache directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Cache dependencies uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: "php-${{ matrix.php }}-laravel-${{ matrix.laravel }}-${{ matrix.stability }}-${{ hashFiles('**/composer.json') }}" restore-keys: "php-${{ matrix.php }}-laravel-${{ matrix.laravel }}-${{ matrix.stability }}-${{ hashFiles('**/composer.json') }}" - name: Install dependencies run: | composer require "illuminate/contracts:${{ matrix.laravel }}" --no-interaction --no-progress --no-update composer update --${{ matrix.stability }} --no-progress --prefer-dist --no-interaction - name: Execute tests run: composer test:ci - name: Upload coverage uses: codecov/codecov-action@v5 if: matrix.coverage == 'xdebug' with: token: ${{ secrets.CODECOV_TOKEN }} files: ./coverage.xml ================================================ FILE: .gitignore ================================================ /vendor composer.lock build coverage.xml .phpunit.result.cache ================================================ FILE: .styleci.yml ================================================ preset: laravel enabled: - no_useless_else - unalign_double_arrow ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) Sean Tymon 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: README.md ================================================ ![jwt-auth-banner](https://cloud.githubusercontent.com/assets/1801923/9915273/119b9350-5cae-11e5-850b-c941cac60b32.png) [![PHPUnit](https://github.com/tymondesigns/jwt-auth/workflows/PHPUnit%20tests/badge.svg)](https://github.com/tymondesigns/jwt-auth/actions) [![Codecov branch](https://img.shields.io/codecov/c/github/tymondesigns/jwt-auth/develop.svg?style=flat-square&logo=codecov)](https://codecov.io/github/tymondesigns/jwt-auth) [![StyleCI](https://styleci.io/repos/23680678/shield?style=flat-square)](https://styleci.io/repos/23680678) [![Latest Version](http://img.shields.io/packagist/v/tymon/jwt-auth.svg?style=flat-square&logo=composer)](https://packagist.org/packages/tymon/jwt-auth) [![Latest Dev Version](https://img.shields.io/packagist/vpre/tymon/jwt-auth.svg?style=flat-square&logo=composer)](https://packagist.org/packages/tymon/jwt-auth#dev-develop) [![Monthly Downloads](https://img.shields.io/packagist/dm/tymon/jwt-auth.svg?style=flat-square&logo=composer)](https://packagist.org/packages/tymon/jwt-auth) ## Documentation [jwt-auth.com](https://jwt-auth.com) ----------------------------------- ## Security If you discover any security related issues, please email tymon148@gmail.com instead of using the issue tracker. ## License The MIT License (MIT) ================================================ FILE: composer.json ================================================ { "name": "tymon/jwt-auth", "description": "JSON Web Token Authentication for Laravel and Lumen", "keywords": [ "auth", "authentication", "json web token", "jwt", "laravel" ], "homepage": "https://github.com/tymondesigns/jwt-auth", "support": { "issues": "https://github.com/tymondesigns/jwt-auth/issues", "source": "https://github.com/tymondesigns/jwt-auth" }, "license": "MIT", "authors": [ { "name": "Sean Tymon", "email": "tymon148@gmail.com", "homepage": "https://tymon.xyz", "role": "Developer" } ], "require": { "php": "^8.0", "illuminate/auth": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/contracts": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/http": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/support": "^9.0|^10.0|^11.0|^12.0|^13.0", "lcobucci/jwt": "^4.0|^5.0", "nesbot/carbon": "^2.69|^3.0" }, "require-dev": { "illuminate/console": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/database": "^9.0|^10.0|^11.0|^12.0|^13.0", "illuminate/routing": "^9.0|^10.0|^11.0|^12.0|^13.0", "mockery/mockery": "^1.6", "phpunit/phpunit": "^9.4" }, "autoload": { "psr-4": { "Tymon\\JWTAuth\\": "src/" } }, "autoload-dev": { "psr-4": { "Tymon\\JWTAuth\\Test\\": "tests/" } }, "extra": { "branch-alias": { "dev-develop": "1.0-dev", "dev-2.x": "2.0-dev" }, "laravel": { "aliases": { "JWTAuth": "Tymon\\JWTAuth\\Facades\\JWTAuth", "JWTFactory": "Tymon\\JWTAuth\\Facades\\JWTFactory" }, "providers": [ "Tymon\\JWTAuth\\Providers\\LaravelServiceProvider" ] } }, "config": { "sort-packages": true }, "prefer-stable": true, "minimum-stability": "dev", "scripts": { "test": "phpunit --colors=always", "test:ci": "composer test -- --verbose --coverage-text --coverage-clover=coverage.xml" } } ================================================ FILE: config/config.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ return [ /* |-------------------------------------------------------------------------- | JWT Authentication Secret |-------------------------------------------------------------------------- | | Don't forget to set this in your .env file, as it will be used to sign | your tokens. A helper command is provided for this: | `php artisan jwt:secret` | | Note: This will be used for Symmetric algorithms only (HMAC), | since RSA and ECDSA use a private/public key combo (See below). | */ 'secret' => env('JWT_SECRET'), /* |-------------------------------------------------------------------------- | JWT Authentication Keys |-------------------------------------------------------------------------- | | The algorithm you are using, will determine whether your tokens are | signed with a random string (defined in `JWT_SECRET`) or using the | following public & private keys. | | Symmetric Algorithms: | HS256, HS384 & HS512 will use `JWT_SECRET`. | | Asymmetric Algorithms: | RS256, RS384 & RS512 / ES256, ES384 & ES512 will use the keys below. | */ 'keys' => [ /* |-------------------------------------------------------------------------- | Public Key |-------------------------------------------------------------------------- | | A path or resource to your public key. | | E.g. 'file://path/to/public/key' | */ 'public' => env('JWT_PUBLIC_KEY'), /* |-------------------------------------------------------------------------- | Private Key |-------------------------------------------------------------------------- | | A path or resource to your private key. | | E.g. 'file://path/to/private/key' | */ 'private' => env('JWT_PRIVATE_KEY'), /* |-------------------------------------------------------------------------- | Passphrase |-------------------------------------------------------------------------- | | The passphrase for your private key. Can be null if none set. | */ 'passphrase' => env('JWT_PASSPHRASE'), ], /* |-------------------------------------------------------------------------- | JWT time to live |-------------------------------------------------------------------------- | | Specify the length of time (in minutes) that the token will be valid for. | Defaults to 1 hour. | | You can also set this to null, to yield a never expiring token. | Some people may want this behaviour for e.g. a mobile app. | This is not particularly recommended, so make sure you have appropriate | systems in place to revoke the token if necessary. | Notice: If you set this to null you should remove 'exp' element from 'required_claims' list. | */ 'ttl' => env('JWT_TTL', 60), /* |-------------------------------------------------------------------------- | Refresh time to live |-------------------------------------------------------------------------- | | Specify the length of time (in minutes) that the token can be refreshed | within. I.E. The user can refresh their token within a 2 week window of | the original token being created until they must re-authenticate. | Defaults to 2 weeks. | | You can also set this to null, to yield an infinite refresh time. | Some may want this instead of never expiring tokens for e.g. a mobile app. | This is not particularly recommended, so make sure you have appropriate | systems in place to revoke the token if necessary. | */ 'refresh_ttl' => env('JWT_REFRESH_TTL', 20160), /* |-------------------------------------------------------------------------- | JWT hashing algorithm |-------------------------------------------------------------------------- | | Specify the hashing algorithm that will be used to sign the token. | */ 'algo' => env('JWT_ALGO', Tymon\JWTAuth\Providers\JWT\Provider::ALGO_HS256), /* |-------------------------------------------------------------------------- | Required Claims |-------------------------------------------------------------------------- | | Specify the required claims that must exist in any token. | A TokenInvalidException will be thrown if any of these claims are not | present in the payload. | */ 'required_claims' => [ 'iss', 'iat', 'exp', 'nbf', 'sub', 'jti', ], /* |-------------------------------------------------------------------------- | Persistent Claims |-------------------------------------------------------------------------- | | Specify the claim keys to be persisted when refreshing a token. | `sub` and `iat` will automatically be persisted, in | addition to the these claims. | | Note: If a claim does not exist then it will be ignored. | */ 'persistent_claims' => [ // 'foo', // 'bar', ], /* |-------------------------------------------------------------------------- | Lock Subject |-------------------------------------------------------------------------- | | This will determine whether a `prv` claim is automatically added to | the token. The purpose of this is to ensure that if you have multiple | authentication models e.g. `App\User` & `App\OtherPerson`, then we | should prevent one authentication request from impersonating another, | if 2 tokens happen to have the same id across the 2 different models. | | Under specific circumstances, you may want to disable this behaviour | e.g. if you only have one authentication model, then you would save | a little on token size. | */ 'lock_subject' => true, /* |-------------------------------------------------------------------------- | Leeway |-------------------------------------------------------------------------- | | This property gives the jwt timestamp claims some "leeway". | Meaning that if you have any unavoidable slight clock skew on | any of your servers then this will afford you some level of cushioning. | | This applies to the claims `iat`, `nbf` and `exp`. | | Specify in seconds - only if you know you need it. | */ 'leeway' => env('JWT_LEEWAY', 0), /* |-------------------------------------------------------------------------- | Blacklist Enabled |-------------------------------------------------------------------------- | | In order to invalidate tokens, you must have the blacklist enabled. | If you do not want or need this functionality, then set this to false. | */ 'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true), /* | ------------------------------------------------------------------------- | Blacklist Grace Period | ------------------------------------------------------------------------- | | When multiple concurrent requests are made with the same JWT, | it is possible that some of them fail, due to token regeneration | on every request. | | Set grace period in seconds to prevent parallel request failure. | */ 'blacklist_grace_period' => env('JWT_BLACKLIST_GRACE_PERIOD', 0), /* |-------------------------------------------------------------------------- | Cookies encryption |-------------------------------------------------------------------------- | | By default Laravel encrypt cookies for security reason. | If you decide to not decrypt cookies, you will have to configure Laravel | to not encrypt your cookie token by adding its name into the $except | array available in the middleware "EncryptCookies" provided by Laravel. | see https://laravel.com/docs/master/responses#cookies-and-encryption | for details. | | Set it to true if you want to decrypt cookies. | */ 'decrypt_cookies' => false, /* |-------------------------------------------------------------------------- | Providers |-------------------------------------------------------------------------- | | Specify the various providers used throughout the package. | */ 'providers' => [ /* |-------------------------------------------------------------------------- | JWT Provider |-------------------------------------------------------------------------- | | Specify the provider that is used to create and decode the tokens. | */ 'jwt' => Tymon\JWTAuth\Providers\JWT\Lcobucci::class, /* |-------------------------------------------------------------------------- | Authentication Provider |-------------------------------------------------------------------------- | | Specify the provider that is used to authenticate users. | */ 'auth' => Tymon\JWTAuth\Providers\Auth\Illuminate::class, /* |-------------------------------------------------------------------------- | Storage Provider |-------------------------------------------------------------------------- | | Specify the provider that is used to store tokens in the blacklist. | */ 'storage' => Tymon\JWTAuth\Providers\Storage\Illuminate::class, ], ]; ================================================ FILE: docs/auth-guard.md ================================================ ## Methods The following methods are available on the Auth guard instance. ### Multiple Guards If the newly created 'api' guard is not set as a default guard or you have defined multiple guards to handle authentication, you should specify the guard when calling auth(). ```php $token = auth('api')->attempt($credentials); ``` ### attempt() Attempt to authenticate a user via some credentials. ```php // Generate a token for the user if the credentials are valid $token = auth()->attempt($credentials); ``` This will return either a jwt or `null` ### login() Log a user in and return a jwt for them. ```php // Get some user from somewhere $user = User::first(); // Get the token $token = auth()->login($user); ``` ### user() Get the currently authenticated user. ```php // Get the currently authenticated user $user = auth()->user(); ``` If the user is not then authenticated, then `null` will be returned. ### userOrFail() Get the currently authenticated user or throw an exception. ```php try { $user = auth()->userOrFail(); } catch (\Tymon\JWTAuth\Exceptions\UserNotDefinedException $e) { // do something } ``` If the user is not set, then a `Tymon\JWTAuth\Exceptions\UserNotDefinedException` will be thrown ### logout() Log the user out - which will invalidate the current token and unset the authenticated user. ```php auth()->logout(); // Pass true to force the token to be blacklisted "forever" auth()->logout(true); ``` ### refresh() Refresh a token, which invalidates the current one ```php $newToken = auth()->refresh(); // Pass true as the first param to force the token to be blacklisted "forever". // The second parameter will reset the claims for the new token $newToken = auth()->refresh(true, true); ``` ### invalidate() Invalidate the token (add it to the blacklist) ```php auth()->invalidate(); // Pass true as the first param to force the token to be blacklisted "forever". auth()->invalidate(true); ``` ### tokenById() Get a token based on a given user's id. ```php $token = auth()->tokenById(123); ``` ### payload() Get the raw JWT payload ```php $payload = auth()->payload(); // then you can access the claims directly e.g. $payload->get('sub'); // = 123 $payload['jti']; // = 'asfe4fq434asdf' $payload('exp') // = 123456 $payload->toArray(); // = ['sub' => 123, 'exp' => 123456, 'jti' => 'asfe4fq434asdf'] etc ``` ### validate() Validate a user's credentials ```php if (auth()->validate($credentials)) { // credentials are valid } ``` ## More advanced usage ### Adding custom claims ```php $token = auth()->claims(['foo' => 'bar'])->attempt($credentials); ``` ### Set the token explicitly ```php $user = auth()->setToken('eyJhb...')->user(); ``` ### Set the request instance explicitly ```php $user = auth()->setRequest($request)->user(); ``` ### Override the token ttl ```php $token = auth()->setTTL(7200)->attempt($credentials); ``` ================================================ FILE: docs/configuration.md ================================================ Let's review some of the options in the `config/jwt.php` file that we published earlier. I won't go through all of the options here since [the file itself](https://github.com/tymondesigns/jwt-auth/blob/1.0.0-beta.2/config/config.php) is pretty well documented. First up is: ``` 'secret' => env('JWT_SECRET'), ``` Coming soon... ================================================ FILE: docs/exception-handling.md ================================================ Coming soon... ================================================ FILE: docs/index.md ================================================ JSON Web Token Authentication for Laravel & Lumen ![jwt-auth-banner](https://cloud.githubusercontent.com/assets/1801923/9915273/119b9350-5cae-11e5-850b-c941cac60b32.png) ================================================ FILE: docs/laravel-installation.md ================================================ ### Install via composer Run the following command to pull in the latest version: ```bash composer require tymon/jwt-auth ``` ------------------------------------------------------------------------------- ### Add service provider ( Laravel 5.4 or below ) Add the service provider to the `providers` array in the `config/app.php` config file as follows: ```php 'providers' => [ ... Tymon\JWTAuth\Providers\LaravelServiceProvider::class, ] ``` ------------------------------------------------------------------------------- ### Publish the config Run the following command to publish the package config file: ```bash php artisan vendor:publish --provider="Tymon\JWTAuth\Providers\LaravelServiceProvider" ``` You should now have a `config/jwt.php` file that allows you to configure the basics of this package. ------------------------------------------------------------------------------- ### Generate secret key I have included a helper command to generate a key for you: ```bash php artisan jwt:secret ``` This will update your `.env` file with something like `JWT_SECRET=foobar` It is the key that will be used to sign your tokens. How that happens exactly will depend on the algorithm that you choose to use. ================================================ FILE: docs/lumen-installation.md ================================================ ### Install via composer Run the following command to pull in the latest version: ```bash composer require tymon/jwt-auth ``` ------------------------------------------------------------------------------- ### Copy the config Copy the `config` file from `vendor/tymon/jwt-auth/config/config.php` to `config` folder of your Lumen application and rename it to `jwt.php` Register your config by adding the following in the `bootstrap/app.php` before middleware declaration. ```php $app->configure('jwt'); ``` ------------------------------------------------------------------------------- ### Bootstrap file changes Add the following snippet to the `bootstrap/app.php` file under the providers section as follows: ```php // Uncomment this line $app->register(App\Providers\AuthServiceProvider::class); // Add this line $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class); ``` Then uncomment the `auth` middleware in the same file: ```php $app->routeMiddleware([ 'auth' => App\Http\Middleware\Authenticate::class, ]); ``` ------------------------------------------------------------------------------- ### Generate secret key I have included a helper command to generate a key for you: ```bash php artisan jwt:secret ``` This will update your `.env` file with something like `JWT_SECRET=foobar` It is the key that will be used to sign your tokens. How that happens exactly will depend on the algorithm that you choose to use. ================================================ FILE: docs/quick-start.md ================================================ Before continuing, make sure you have installed the package as per the installation instructions for [Laravel](laravel-installation) or [Lumen](lumen-installation). ### Update your User model Firstly you need to implement the `Tymon\JWTAuth\Contracts\JWTSubject` contract on your User model, which requires that you implement the 2 methods `getJWTIdentifier()` and `getJWTCustomClaims()`. The example below should give you an idea of how this could look. Obviously you should make any changes, as necessary, to suit your own needs. ```php getKey(); } /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims() { return []; } } ``` ### Configure Auth guard *Note: This will only work if you are using Laravel 5.2 and above.* Inside the `config/auth.php` file you will need to make a few changes to configure Laravel to use the `jwt` guard to power your application authentication. Make the following changes to the file: ```php 'defaults' => [ 'guard' => 'api', 'passwords' => 'users', ], ... 'guards' => [ 'api' => [ 'driver' => 'jwt', 'provider' => 'users', ], ], ``` Here we are telling the `api` guard to use the `jwt` driver, and we are setting the `api` guard as the default. We can now use Laravel's built in Auth system, with jwt-auth doing the work behind the scenes! ### Add some basic authentication routes First let's add some routes in `routes/api.php` as follows: ```php Route::group([ 'middleware' => 'api', 'prefix' => 'auth' ], function ($router) { Route::post('login', 'AuthController@login'); Route::post('logout', 'AuthController@logout'); Route::post('refresh', 'AuthController@refresh'); Route::post('me', 'AuthController@me'); }); ``` ### Create the AuthController Then create the `AuthController`, either manually or by running the artisan command: ```bash php artisan make:controller AuthController ``` Then add the following: ```php middleware('auth:api', ['except' => ['login']]); } /** * Get a JWT via given credentials. * * @return \Illuminate\Http\JsonResponse */ public function login() { $credentials = request(['email', 'password']); if (! $token = auth()->attempt($credentials)) { return response()->json(['error' => 'Unauthorized'], 401); } return $this->respondWithToken($token); } /** * Get the authenticated User. * * @return \Illuminate\Http\JsonResponse */ public function me() { return response()->json(auth()->user()); } /** * Log the user out (Invalidate the token). * * @return \Illuminate\Http\JsonResponse */ public function logout() { auth()->logout(); return response()->json(['message' => 'Successfully logged out']); } /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh() { return $this->respondWithToken(auth()->refresh()); } /** * Get the token array structure. * * @param string $token * * @return \Illuminate\Http\JsonResponse */ protected function respondWithToken($token) { return response()->json([ 'access_token' => $token, 'token_type' => 'bearer', 'expires_in' => auth()->factory()->getTTL() * 60 ]); } } ``` You should now be able to POST to the login endpoint (e.g. `http://example.dev/auth/login`) with some valid credentials and see a response like: ```json { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ", "token_type": "bearer", "expires_in": 3600 } ``` This token can then be used to make authenticated requests to your application. ### Authenticated requests There are a number of ways to send the token via http: **Authorization header** `Authorization: Bearer eyJhbGciOiJIUzI1NiI...` **Query string parameter** `http://example.dev/me?token=eyJhbGciOiJIUzI1NiI...` **Post parameter** **Cookies** **Laravel route parameter** ================================================ FILE: docs/resources.md ================================================ - [The Anatomy of a JSON Web Token](https://scotch.io/tutorials/the-anatomy-of-a-json-web-token) - [jwt.io](https://jwt.io/) Over the last couple of years, the community has provided some great tutorials and packages that have helped people get up and running with jwt-auth. So it's only right that I post some of them here. Coming soon... ================================================ FILE: mkdocs.yml ================================================ site_name: jwt-auth pages: - Home: index.md - Laravel Installation: laravel-installation.md - Lumen Installation (incomplete): lumen-installation.md - Quick start: quick-start.md - Auth guard: auth-guard.md - Configuration: configuration.md - Exception Handling: exception-handling.md - Resources: resources.md theme: readthedocs ================================================ FILE: phpunit.xml.dist ================================================ src/ src/Providers/AbstractServiceProvider.php src/Providers/LaravelServiceProvider.php src/Providers/LumenServiceProvider.php src/Facades/ src/Console/ tests ================================================ FILE: src/Blacklist.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use Tymon\JWTAuth\Contracts\Providers\Storage; use Tymon\JWTAuth\Support\Utils; class Blacklist { /** * The storage. * * @var \Tymon\JWTAuth\Contracts\Providers\Storage */ protected $storage; /** * The grace period when a token is blacklisted. In seconds. * * @var int */ protected $gracePeriod = 0; /** * Number of minutes from issue date in which a JWT can be refreshed. * * @var int */ protected $refreshTTL = 20160; /** * The unique key held within the blacklist. * * @var string */ protected $key = 'jti'; /** * Constructor. * * @param \Tymon\JWTAuth\Contracts\Providers\Storage $storage * @return void */ public function __construct(Storage $storage) { $this->storage = $storage; } /** * Add the token (jti claim) to the blacklist. * * @param \Tymon\JWTAuth\Payload $payload * @return bool */ public function add(Payload $payload) { // if there is no exp claim then add the jwt to // the blacklist indefinitely if (! $payload->hasKey('exp')) { return $this->addForever($payload); } // if we have already added this token to the blacklist if (! empty($this->storage->get($this->getKey($payload)))) { return true; } $this->storage->add( $this->getKey($payload), ['valid_until' => $this->getGraceTimestamp()], $this->getMinutesUntilExpired($payload) ); return true; } /** * Get the number of minutes until the token expiry. * * @param \Tymon\JWTAuth\Payload $payload * @return int */ protected function getMinutesUntilExpired(Payload $payload) { $exp = Utils::timestamp($payload['exp']); $iat = Utils::timestamp($payload['iat']); // get the latter of the two expiration dates and find // the number of minutes until the expiration date, // plus 1 minute to avoid overlap $expiration = $exp->max($iat->addMinutes($this->refreshTTL))->addMinute(); $minutes = method_exists($expiration, 'diffInRealMinutes') ? $expiration->diffInRealMinutes() : $expiration->diffInUTCMinutes(); return (int) ceil(abs($minutes)); } /** * Add the token (jti claim) to the blacklist indefinitely. * * @param \Tymon\JWTAuth\Payload $payload * @return bool */ public function addForever(Payload $payload) { $this->storage->forever($this->getKey($payload), 'forever'); return true; } /** * Determine whether the token has been blacklisted. * * @param \Tymon\JWTAuth\Payload $payload * @return bool */ public function has(Payload $payload) { $val = $this->storage->get($this->getKey($payload)); // exit early if the token was blacklisted forever, if ($val === 'forever') { return true; } // check whether the expiry + grace has past return ! empty($val) && ! Utils::isFuture($val['valid_until']); } /** * Remove the token (jti claim) from the blacklist. * * @param \Tymon\JWTAuth\Payload $payload * @return bool */ public function remove(Payload $payload) { return $this->storage->destroy($this->getKey($payload)); } /** * Remove all tokens from the blacklist. * * @return bool */ public function clear() { $this->storage->flush(); return true; } /** * Get the timestamp when the blacklist comes into effect * This defaults to immediate (0 seconds). * * @return int */ protected function getGraceTimestamp() { return Utils::now()->addSeconds($this->gracePeriod)->getTimestamp(); } /** * Set the grace period. * * @param int $gracePeriod * @return $this */ public function setGracePeriod($gracePeriod) { $this->gracePeriod = (int) $gracePeriod; return $this; } /** * Get the grace period. * * @return int */ public function getGracePeriod() { return $this->gracePeriod; } /** * Get the unique key held within the blacklist. * * @param \Tymon\JWTAuth\Payload $payload * @return mixed */ public function getKey(Payload $payload) { return $payload($this->key); } /** * Set the unique key held within the blacklist. * * @param string $key * @return $this */ public function setKey($key) { $this->key = value($key); return $this; } /** * Set the refresh time limit. * * @param int $ttl * @return $this */ public function setRefreshTTL($ttl) { $this->refreshTTL = (int) $ttl; return $this; } /** * Get the refresh time limit. * * @return int */ public function getRefreshTTL() { return $this->refreshTTL; } } ================================================ FILE: src/Claims/Audience.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; class Audience extends Claim { /** * {@inheritdoc} */ protected $name = 'aud'; } ================================================ FILE: src/Claims/Claim.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use JsonSerializable; use Tymon\JWTAuth\Contracts\Claim as ClaimContract; abstract class Claim implements Arrayable, ClaimContract, Jsonable, JsonSerializable { /** * The claim name. * * @var string */ protected $name; /** * The claim value. * * @var mixed */ private $value; /** * @param mixed $value * @return void */ public function __construct($value) { $this->setValue($value); } /** * Set the claim value, and call a validate method. * * @param mixed $value * @return $this * * @throws \Tymon\JWTAuth\Exceptions\InvalidClaimException */ public function setValue($value) { $this->value = $this->validateCreate($value); return $this; } /** * Get the claim value. * * @return mixed */ public function getValue() { return $this->value; } /** * Set the claim name. * * @param string $name * @return $this */ public function setName($name) { $this->name = $name; return $this; } /** * Get the claim name. * * @return string */ public function getName() { return $this->name; } /** * Validate the claim in a standalone Claim context. * * @param mixed $value * @return bool */ public function validateCreate($value) { return $value; } /** * Validate the Claim within a Payload context. * * @return bool */ public function validatePayload() { return $this->getValue(); } /** * Validate the Claim within a refresh context. * * @param int $refreshTTL * @return bool */ public function validateRefresh($refreshTTL) { return $this->getValue(); } /** * Checks if the value matches the claim. * * @param mixed $value * @param bool $strict * @return bool */ public function matches($value, $strict = true) { return $strict ? $this->value === $value : $this->value == $value; } /** * Convert the object into something JSON serializable. * * @return array */ #[\ReturnTypeWillChange] public function jsonSerialize(): mixed { return $this->toArray(); } /** * Build a key value array comprising of the claim name and value. * * @return array */ public function toArray() { return [$this->getName() => $this->getValue()]; } /** * Get the claim as JSON. * * @param int $options * @return string */ public function toJson($options = JSON_UNESCAPED_SLASHES) { return json_encode($this->toArray(), $options); } /** * Get the payload as a string. * * @return string */ public function __toString() { return $this->toJson(); } } ================================================ FILE: src/Claims/Collection.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use Illuminate\Support\Collection as IlluminateCollection; use Illuminate\Support\Str; class Collection extends IlluminateCollection { /** * Create a new collection. * * @param mixed $items * @return void */ public function __construct($items = []) { parent::__construct($this->getArrayableItems($items)); } /** * Get a Claim instance by it's unique name. * * @param string $name * @param callable|null $callback * @param mixed $default * @return \Tymon\JWTAuth\Claims\Claim */ public function getByClaimName($name, ?callable $callback = null, $default = null) { return $this->filter(function (Claim $claim) use ($name) { return $claim->getName() === $name; })->first($callback, $default); } /** * Validate each claim under a given context. * * @param string $context * @return $this */ public function validate($context = 'payload') { $args = func_get_args(); array_shift($args); $this->each(function ($claim) use ($context, $args) { call_user_func_array( [$claim, 'validate'.Str::ucfirst($context)], $args ); }); return $this; } /** * Determine if the Collection contains all of the given keys. * * @param mixed $claims * @return bool */ public function hasAllClaims($claims) { return count($claims) && (new static($claims))->diff($this->keys())->isEmpty(); } /** * Get the claims as key/val array. * * @return array */ public function toPlainArray() { return $this->map(function (Claim $claim) { return $claim->getValue(); })->toArray(); } /** * {@inheritdoc} */ protected function getArrayableItems($items) { return $this->sanitizeClaims($items); } /** * Ensure that the given claims array is keyed by the claim name. * * @param mixed $items * @return array */ private function sanitizeClaims($items) { $claims = []; foreach ($items as $key => $value) { if (! is_string($key) && $value instanceof Claim) { $key = $value->getName(); } $claims[$key] = $value; } return $claims; } } ================================================ FILE: src/Claims/Custom.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; class Custom extends Claim { /** * @param string $name * @param mixed $value * @return void */ public function __construct($name, $value) { parent::__construct($value); $this->setName($name); } } ================================================ FILE: src/Claims/DatetimeTrait.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use DateInterval; use DateTimeInterface; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Support\Utils; trait DatetimeTrait { /** * Time leeway in seconds. * * @var int */ protected $leeway = 0; /** * Set the claim value, and call a validate method. * * @param mixed $value * @return $this * * @throws \Tymon\JWTAuth\Exceptions\InvalidClaimException */ public function setValue($value) { if ($value instanceof DateInterval) { $value = Utils::now()->add($value); } if ($value instanceof DateTimeInterface) { $value = $value->getTimestamp(); } return parent::setValue($value); } /** * {@inheritdoc} */ public function validateCreate($value) { if (! is_numeric($value)) { throw new InvalidClaimException($this); } return $value; } /** * Determine whether the value is in the future. * * @param mixed $value * @return bool */ protected function isFuture($value) { return Utils::isFuture($value, $this->leeway); } /** * Determine whether the value is in the past. * * @param mixed $value * @return bool */ protected function isPast($value) { return Utils::isPast($value, $this->leeway); } /** * Set the leeway in seconds. * * @param int $leeway * @return $this */ public function setLeeway($leeway) { $this->leeway = $leeway; return $this; } } ================================================ FILE: src/Claims/Expiration.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use Tymon\JWTAuth\Exceptions\TokenExpiredException; class Expiration extends Claim { use DatetimeTrait; /** * {@inheritdoc} */ protected $name = 'exp'; /** * {@inheritdoc} */ public function validatePayload() { if ($this->isPast($this->getValue())) { throw new TokenExpiredException('Token has expired'); } } } ================================================ FILE: src/Claims/Factory.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use Illuminate\Http\Request; use Illuminate\Support\Str; use Tymon\JWTAuth\Support\Utils; class Factory { /** * The request. * * @var \Illuminate\Http\Request */ protected $request; /** * The TTL. * * @var int */ protected $ttl = 60; /** * Time leeway in seconds. * * @var int */ protected $leeway = 0; /** * The classes map. * * @var array */ private $classMap = [ 'aud' => Audience::class, 'exp' => Expiration::class, 'iat' => IssuedAt::class, 'iss' => Issuer::class, 'jti' => JwtId::class, 'nbf' => NotBefore::class, 'sub' => Subject::class, ]; /** * Constructor. * * @param \Illuminate\Http\Request $request * @return void */ public function __construct(Request $request) { $this->request = $request; } /** * Get the instance of the claim when passing the name and value. * * @param string $name * @param mixed $value * @return \Tymon\JWTAuth\Claims\Claim */ public function get($name, $value) { if ($this->has($name)) { $claim = new $this->classMap[$name]($value); return method_exists($claim, 'setLeeway') ? $claim->setLeeway($this->leeway) : $claim; } return new Custom($name, $value); } /** * Check whether the claim exists. * * @param string $name * @return bool */ public function has($name) { return array_key_exists($name, $this->classMap); } /** * Generate the initial value and return the Claim instance. * * @param string $name * @return \Tymon\JWTAuth\Claims\Claim */ public function make($name) { return $this->get($name, $this->$name()); } /** * Get the Issuer (iss) claim. * * @return string */ public function iss() { return $this->request->url(); } /** * Get the Issued At (iat) claim. * * @return int */ public function iat() { return Utils::now()->getTimestamp(); } /** * Get the Expiration (exp) claim. * * @return int */ public function exp() { return Utils::now()->addMinutes($this->ttl)->getTimestamp(); } /** * Get the Not Before (nbf) claim. * * @return int */ public function nbf() { return Utils::now()->getTimestamp(); } /** * Get the JWT Id (jti) claim. * * @return string */ public function jti() { return Str::random(); } /** * Add a new claim mapping. * * @param string $name * @param string $classPath * @return $this */ public function extend($name, $classPath) { $this->classMap[$name] = $classPath; return $this; } /** * Set the request instance. * * @param \Illuminate\Http\Request $request * @return $this */ public function setRequest(Request $request) { $this->request = $request; return $this; } /** * Set the token ttl (in minutes). * * @param int $ttl * @return $this */ public function setTTL($ttl) { $this->ttl = (int) $ttl; return $this; } /** * Get the token ttl. * * @return int */ public function getTTL() { return $this->ttl; } /** * Set the leeway in seconds. * * @param int $leeway * @return $this */ public function setLeeway($leeway) { $this->leeway = (int) $leeway; return $this; } /** * Get the leeway in seconds. * * @return int */ public function getLeeway() { return $this->leeway; } } ================================================ FILE: src/Claims/IssuedAt.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Exceptions\TokenExpiredException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; class IssuedAt extends Claim { use DatetimeTrait { validateCreate as commonValidateCreate; } /** * {@inheritdoc} */ protected $name = 'iat'; /** * {@inheritdoc} */ public function validateCreate($value) { $this->commonValidateCreate($value); if ($this->isFuture($value)) { throw new InvalidClaimException($this); } return $value; } /** * {@inheritdoc} */ public function validatePayload() { if ($this->isFuture($this->getValue())) { throw new TokenInvalidException('Issued At (iat) timestamp cannot be in the future'); } } /** * {@inheritdoc} */ public function validateRefresh($refreshTTL) { if ($this->isPast($this->getValue() + $refreshTTL * 60)) { throw new TokenExpiredException('Token has expired and can no longer be refreshed'); } } } ================================================ FILE: src/Claims/Issuer.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; class Issuer extends Claim { /** * {@inheritdoc} */ protected $name = 'iss'; } ================================================ FILE: src/Claims/JwtId.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; class JwtId extends Claim { /** * {@inheritdoc} */ protected $name = 'jti'; } ================================================ FILE: src/Claims/NotBefore.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; use Tymon\JWTAuth\Exceptions\TokenInvalidException; class NotBefore extends Claim { use DatetimeTrait; /** * {@inheritdoc} */ protected $name = 'nbf'; /** * {@inheritdoc} */ public function validatePayload() { if ($this->isFuture($this->getValue())) { throw new TokenInvalidException('Not Before (nbf) timestamp cannot be in the future'); } } } ================================================ FILE: src/Claims/Subject.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Claims; class Subject extends Claim { /** * {@inheritdoc} */ protected $name = 'sub'; } ================================================ FILE: src/Console/JWTGenerateSecretCommand.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Console; use Illuminate\Console\Command; use Illuminate\Support\Str; class JWTGenerateSecretCommand extends Command { /** * The console command signature. * * @var string */ protected $signature = 'jwt:secret {--s|show : Display the key instead of modifying files.} {--always-no : Skip generating key if it already exists.} {--f|force : Skip confirmation when overwriting an existing key.}'; /** * The console command description. * * @var string */ protected $description = 'Set the JWTAuth secret key used to sign the tokens'; /** * Execute the console command. * * @return void */ public function handle() { $key = Str::random(64); if ($this->option('show')) { $this->comment($key); return; } if (file_exists($path = $this->envPath()) === false) { return $this->displayKey($key); } if (Str::contains(file_get_contents($path), 'JWT_SECRET') === false) { // create new entry file_put_contents($path, PHP_EOL."JWT_SECRET=$key".PHP_EOL, FILE_APPEND); } else { if ($this->option('always-no')) { $this->comment('Secret key already exists. Skipping...'); return; } if ($this->isConfirmed() === false) { $this->comment('Phew... No changes were made to your secret key.'); return; } // update existing entry file_put_contents($path, str_replace( 'JWT_SECRET='.$this->laravel['config']['jwt.secret'], 'JWT_SECRET='.$key, file_get_contents($path) )); } $this->displayKey($key); } /** * Display the key. * * @param string $key * @return void */ protected function displayKey($key) { $this->laravel['config']['jwt.secret'] = $key; $this->info("jwt-auth secret [$key] set successfully."); } /** * Check if the modification is confirmed. * * @return bool */ protected function isConfirmed() { return $this->option('force') ? true : $this->confirm( 'This will invalidate all existing tokens. Are you sure you want to override the secret key?' ); } /** * Get the .env file path. * * @return string */ protected function envPath() { if (method_exists($this->laravel, 'environmentFilePath')) { return $this->laravel->environmentFilePath(); } // check if laravel version Less than 5.4.17 if (version_compare($this->laravel->version(), '5.4.17', '<')) { return $this->laravel->basePath().DIRECTORY_SEPARATOR.'.env'; } return $this->laravel->basePath('.env'); } } ================================================ FILE: src/Contracts/Claim.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts; interface Claim { /** * Set the claim value, and call a validate method. * * @param mixed $value * @return $this * * @throws \Tymon\JWTAuth\Exceptions\InvalidClaimException */ public function setValue($value); /** * Get the claim value. * * @return mixed */ public function getValue(); /** * Set the claim name. * * @param string $name * @return $this */ public function setName($name); /** * Get the claim name. * * @return string */ public function getName(); /** * Validate the Claim value. * * @param mixed $value * @return bool */ public function validateCreate($value); } ================================================ FILE: src/Contracts/Http/Parser.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts\Http; use Illuminate\Http\Request; interface Parser { /** * Parse the request. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request); } ================================================ FILE: src/Contracts/JWTSubject.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts; interface JWTSubject { /** * Get the identifier that will be stored in the subject claim of the JWT. * * @return mixed */ public function getJWTIdentifier(); /** * Return a key value array, containing any custom claims to be added to the JWT. * * @return array */ public function getJWTCustomClaims(); } ================================================ FILE: src/Contracts/Providers/Auth.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts\Providers; interface Auth { /** * Check a user's credentials. * * @param array $credentials * @return mixed */ public function byCredentials(array $credentials); /** * Authenticate a user via the id. * * @param mixed $id * @return mixed */ public function byId($id); /** * Get the currently authenticated user. * * @return mixed */ public function user(); } ================================================ FILE: src/Contracts/Providers/JWT.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts\Providers; interface JWT { /** * @param array $payload * @return string */ public function encode(array $payload); /** * @param string $token * @return array */ public function decode($token); } ================================================ FILE: src/Contracts/Providers/Storage.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts\Providers; interface Storage { /** * @param string $key * @param mixed $value * @param int $minutes * @return void */ public function add($key, $value, $minutes); /** * @param string $key * @param mixed $value * @return void */ public function forever($key, $value); /** * @param string $key * @return mixed */ public function get($key); /** * @param string $key * @return bool */ public function destroy($key); /** * @return void */ public function flush(); } ================================================ FILE: src/Contracts/Validator.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Contracts; interface Validator { /** * Perform some checks on the value. * * @param mixed $value * @return void */ public function check($value); /** * Helper function to return a boolean. * * @param array $value * @return bool */ public function isValid($value); } ================================================ FILE: src/Exceptions/InvalidClaimException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; use Exception; use Tymon\JWTAuth\Claims\Claim; class InvalidClaimException extends JWTException { /** * Constructor. * * @param \Tymon\JWTAuth\Claims\Claim $claim * @param int $code * @param \Exception|null $previous * @return void */ public function __construct(Claim $claim, $code = 0, ?Exception $previous = null) { parent::__construct('Invalid value provided for claim ['.$claim->getName().']', $code, $previous); } } ================================================ FILE: src/Exceptions/JWTException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; use Exception; class JWTException extends Exception { /** * {@inheritdoc} */ protected $message = 'An error occurred'; } ================================================ FILE: src/Exceptions/PayloadException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; class PayloadException extends JWTException { // } ================================================ FILE: src/Exceptions/TokenBlacklistedException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; class TokenBlacklistedException extends TokenInvalidException { // } ================================================ FILE: src/Exceptions/TokenExpiredException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; class TokenExpiredException extends JWTException { // } ================================================ FILE: src/Exceptions/TokenInvalidException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; class TokenInvalidException extends JWTException { // } ================================================ FILE: src/Exceptions/UserNotDefinedException.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Exceptions; class UserNotDefinedException extends JWTException { // } ================================================ FILE: src/Facades/JWTAuth.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Facades; use Illuminate\Support\Facades\Facade; class JWTAuth extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'tymon.jwt.auth'; } } ================================================ FILE: src/Facades/JWTFactory.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Facades; use Illuminate\Support\Facades\Facade; class JWTFactory extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'tymon.jwt.payload.factory'; } } ================================================ FILE: src/Facades/JWTProvider.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Facades; use Illuminate\Support\Facades\Facade; class JWTProvider extends Facade { /** * Get the registered name of the component. * * @return string */ protected static function getFacadeAccessor() { return 'tymon.jwt.provider.jwt'; } } ================================================ FILE: src/Factory.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use Tymon\JWTAuth\Claims\Claim; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Factory as ClaimFactory; use Tymon\JWTAuth\Support\CustomClaims; use Tymon\JWTAuth\Support\RefreshFlow; use Tymon\JWTAuth\Validators\PayloadValidator; class Factory { use CustomClaims, RefreshFlow; /** * The claim factory. * * @var \Tymon\JWTAuth\Claims\Factory */ protected $claimFactory; /** * The validator. * * @var \Tymon\JWTAuth\Validators\PayloadValidator */ protected $validator; /** * The default claims. * * @var array */ protected $defaultClaims = [ 'iss', 'iat', 'exp', 'nbf', 'jti', ]; /** * The claims collection. * * @var \Tymon\JWTAuth\Claims\Collection */ protected $claims; /** * Constructor. * * @param \Tymon\JWTAuth\Claims\Factory $claimFactory * @param \Tymon\JWTAuth\Validators\PayloadValidator $validator * @return void */ public function __construct(ClaimFactory $claimFactory, PayloadValidator $validator) { $this->claimFactory = $claimFactory; $this->validator = $validator; $this->claims = new Collection; } /** * Create the Payload instance. * * @param bool $resetClaims * @return \Tymon\JWTAuth\Payload */ public function make($resetClaims = false) { if ($resetClaims) { $this->emptyClaims(); } return $this->withClaims($this->buildClaimsCollection()); } /** * Empty the claims collection. * * @return $this */ public function emptyClaims() { $this->claims = new Collection; return $this; } /** * Add an array of claims to the Payload. * * @param array $claims * @return $this */ protected function addClaims(array $claims) { foreach ($claims as $name => $value) { $this->addClaim($name, $value); } return $this; } /** * Add a claim to the Payload. * * @param string $name * @param mixed $value * @return $this */ protected function addClaim($name, $value) { $this->claims->put($name, $value); return $this; } /** * Build the default claims. * * @return $this */ protected function buildClaims() { // remove the exp claim if it exists and the ttl is null if ($this->claimFactory->getTTL() === null && $key = array_search('exp', $this->defaultClaims)) { unset($this->defaultClaims[$key]); } // add the default claims foreach ($this->defaultClaims as $claim) { $this->addClaim($claim, $this->claimFactory->make($claim)); } // add custom claims on top, allowing them to overwrite defaults return $this->addClaims($this->getCustomClaims()); } /** * Build out the Claim DTO's. * * @return \Tymon\JWTAuth\Claims\Collection */ protected function resolveClaims() { return $this->claims->map(function ($value, $name) { return $value instanceof Claim ? $value : $this->claimFactory->get($name, $value); }); } /** * Build and get the Claims Collection. * * @return \Tymon\JWTAuth\Claims\Collection */ public function buildClaimsCollection() { return $this->buildClaims()->resolveClaims(); } /** * Get a Payload instance with a claims collection. * * @param \Tymon\JWTAuth\Claims\Collection $claims * @return \Tymon\JWTAuth\Payload */ public function withClaims(Collection $claims) { return new Payload($claims, $this->validator, $this->refreshFlow); } /** * Set the default claims to be added to the Payload. * * @param array $claims * @return $this */ public function setDefaultClaims(array $claims) { $this->defaultClaims = $claims; return $this; } /** * Helper to set the ttl. * * @param int $ttl * @return $this */ public function setTTL($ttl) { $this->claimFactory->setTTL($ttl); return $this; } /** * Helper to get the ttl. * * @return int */ public function getTTL() { return $this->claimFactory->getTTL(); } /** * Get the default claims. * * @return array */ public function getDefaultClaims() { return $this->defaultClaims; } /** * Get the PayloadValidator instance. * * @return \Tymon\JWTAuth\Validators\PayloadValidator */ public function validator() { return $this->validator; } /** * Magically add a claim. * * @param string $method * @param array $parameters * @return $this */ public function __call($method, $parameters) { $this->addClaim($method, $parameters[0]); return $this; } } ================================================ FILE: src/Http/Middleware/Authenticate.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Middleware; use Closure; /** @deprecated */ class Authenticate extends BaseMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException */ public function handle($request, Closure $next) { $this->authenticate($request); return $next($request); } } ================================================ FILE: src/Http/Middleware/AuthenticateAndRenew.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Middleware; use Closure; /** @deprecated */ class AuthenticateAndRenew extends BaseMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException */ public function handle($request, Closure $next) { $this->authenticate($request); $response = $next($request); // Send the refreshed token back to the client. return $this->setAuthenticationHeader($response); } } ================================================ FILE: src/Http/Middleware/BaseMiddleware.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Middleware; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\JWTAuth; /** @deprecated */ abstract class BaseMiddleware { /** * The JWT Authenticator. * * @var \Tymon\JWTAuth\JWTAuth */ protected $auth; /** * Create a new BaseMiddleware instance. * * @param \Tymon\JWTAuth\JWTAuth $auth * @return void */ public function __construct(JWTAuth $auth) { $this->auth = $auth; } /** * Check the request for the presence of a token. * * @param \Illuminate\Http\Request $request * @return void * * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException */ public function checkForToken(Request $request) { if (! $this->auth->parser()->setRequest($request)->hasToken()) { throw new UnauthorizedHttpException('jwt-auth', 'Token not provided'); } } /** * Attempt to authenticate a user via the token in the request. * * @param \Illuminate\Http\Request $request * @return void * * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException */ public function authenticate(Request $request) { $this->checkForToken($request); try { if (! $this->auth->parseToken()->authenticate()) { throw new UnauthorizedHttpException('jwt-auth', 'User not found'); } } catch (JWTException $e) { throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), $e, $e->getCode()); } } /** * Set the authentication header. * * @param \Illuminate\Http\Response|\Illuminate\Http\JsonResponse $response * @param string|null $token * @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse */ protected function setAuthenticationHeader($response, $token = null) { $token = $token ?: $this->auth->refresh(); $response->headers->set('Authorization', 'Bearer '.$token); return $response; } } ================================================ FILE: src/Http/Middleware/Check.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Middleware; use Closure; use Exception; /** @deprecated */ class Check extends BaseMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { if ($this->auth->parser()->setRequest($request)->hasToken()) { try { $this->auth->parseToken()->authenticate(); } catch (Exception $e) { // } } return $next($request); } } ================================================ FILE: src/Http/Middleware/RefreshToken.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Middleware; use Closure; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\JWTException; /** @deprecated */ class RefreshToken extends BaseMiddleware { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed * * @throws \Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException */ public function handle($request, Closure $next) { $this->checkForToken($request); try { $token = $this->auth->parseToken()->refresh(); } catch (JWTException $e) { throw new UnauthorizedHttpException('jwt-auth', $e->getMessage(), $e, $e->getCode()); } $response = $next($request); // Send the refreshed token back to the client. return $this->setAuthenticationHeader($response, $token); } } ================================================ FILE: src/Http/Parser/AuthHeaders.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class AuthHeaders implements ParserContract { /** * The header name. * * @var string */ protected $header = 'authorization'; /** * The header prefix. * * @var string */ protected $prefix = 'bearer'; /** * Attempt to parse the token from some other possible headers. * * @param \Illuminate\Http\Request $request * @return null|string */ protected function fromAltHeaders(Request $request) { return $request->server->get('HTTP_AUTHORIZATION') ?: $request->server->get('REDIRECT_HTTP_AUTHORIZATION'); } /** * Try to parse the token from the request header. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { $header = $request->headers->get($this->header) ?: $this->fromAltHeaders($request); if ($header !== null) { $position = strripos($header, $this->prefix); if ($position !== false) { $header = substr($header, $position + strlen($this->prefix)); return trim( strpos($header, ',') !== false ? strstr($header, ',', true) : $header ); } } return null; } /** * Set the header name. * * @param string $headerName * @return $this */ public function setHeaderName($headerName) { $this->header = $headerName; return $this; } /** * Set the header prefix. * * @param string $headerPrefix * @return $this */ public function setHeaderPrefix($headerPrefix) { $this->prefix = $headerPrefix; return $this; } } ================================================ FILE: src/Http/Parser/Cookies.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Illuminate\Support\Facades\Crypt; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class Cookies implements ParserContract { use KeyTrait; /** * Decrypt or not the cookie while parsing. * * @var bool */ private $decrypt; public function __construct($decrypt = true) { $this->decrypt = $decrypt; } /** * Try to parse the token from the request cookies. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { if ($this->decrypt && $request->hasCookie($this->key)) { return Crypt::decrypt($request->cookie($this->key)); } return $request->cookie($this->key); } } ================================================ FILE: src/Http/Parser/InputSource.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class InputSource implements ParserContract { use KeyTrait; /** * Try to parse the token from the request input source. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { return $request->input($this->key); } } ================================================ FILE: src/Http/Parser/KeyTrait.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; trait KeyTrait { /** * The key. * * @var string */ protected $key = 'token'; /** * Set the key. * * @param string $key * @return $this */ public function setKey($key) { $this->key = $key; return $this; } /** * Get the key. * * @return string */ public function getKey() { return $this->key; } } ================================================ FILE: src/Http/Parser/LumenRouteParams.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Illuminate\Support\Arr; class LumenRouteParams extends RouteParams { /** * Try to get the token from the route parameters. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { // WARNING: Only use this parser if you know what you're doing! // It will only work with poorly-specified aspects of certain Lumen releases. // Route is the expected kind of array, and has a parameter with the key we want. return Arr::get($request->route(), '2.'.$this->key); } } ================================================ FILE: src/Http/Parser/Parser.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; class Parser { /** * The chain. * * @var array */ private $chain; /** * The request. * * @var \Illuminate\Http\Request */ protected $request; /** * Constructor. * * @param \Illuminate\Http\Request $request * @param array $chain * @return void */ public function __construct(Request $request, array $chain = []) { $this->request = $request; $this->chain = $chain; } /** * Get the parser chain. * * @return array */ public function getChain() { return $this->chain; } /** * Add a new parser to the chain. * * @param array|\Tymon\JWTAuth\Contracts\Http\Parser $parsers * @return $this */ public function addParser($parsers) { $this->chain = array_merge($this->chain, is_array($parsers) ? $parsers : [$parsers]); return $this; } /** * Set the order of the parser chain. * * @param array $chain * @return $this */ public function setChain(array $chain) { $this->chain = $chain; return $this; } /** * Alias for setting the order of the chain. * * @param array $chain * @return $this */ public function setChainOrder(array $chain) { return $this->setChain($chain); } /** * Iterate through the parsers and attempt to retrieve * a value, otherwise return null. * * @return string|null */ public function parseToken() { foreach ($this->chain as $parser) { if ($response = $parser->parse($this->request)) { return $response; } } } /** * Check whether a token exists in the chain. * * @return bool */ public function hasToken() { return $this->parseToken() !== null; } /** * Set the request instance. * * @param \Illuminate\Http\Request $request * @return $this */ public function setRequest(Request $request) { $this->request = $request; return $this; } } ================================================ FILE: src/Http/Parser/QueryString.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class QueryString implements ParserContract { use KeyTrait; /** * Try to parse the token from the request query string. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { return $request->query($this->key); } } ================================================ FILE: src/Http/Parser/RouteParams.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Http\Parser; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; class RouteParams implements ParserContract { use KeyTrait; /** * Try to get the token from the route parameters. * * @param \Illuminate\Http\Request $request * @return null|string */ public function parse(Request $request) { $route = $request->route(); // Route may not be an instance of Illuminate\Routing\Route // (it's an array in Lumen <5.2) or not exist at all // (if the request was never dispatched) if (is_callable([$route, 'parameter'])) { return $route->parameter($this->key); } } } ================================================ FILE: src/JWT.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use BadMethodCallException; use Illuminate\Http\Request; use Tymon\JWTAuth\Contracts\JWTSubject; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\Support\CustomClaims; class JWT { use CustomClaims; /** * The authentication manager. * * @var \Tymon\JWTAuth\Manager */ protected $manager; /** * The HTTP parser. * * @var \Tymon\JWTAuth\Http\Parser\Parser */ protected $parser; /** * The token. * * @var \Tymon\JWTAuth\Token|null */ protected $token; /** * Lock the subject. * * @var bool */ protected $lockSubject = true; /** * JWT constructor. * * @param \Tymon\JWTAuth\Manager $manager * @param \Tymon\JWTAuth\Http\Parser\Parser $parser * @return void */ public function __construct(Manager $manager, Parser $parser) { $this->manager = $manager; $this->parser = $parser; } /** * Generate a token for a given subject. * * @param \Tymon\JWTAuth\Contracts\JWTSubject $subject * @return string */ public function fromSubject(JWTSubject $subject) { $payload = $this->makePayload($subject); return $this->manager->encode($payload)->get(); } /** * Alias to generate a token for a given user. * * @param \Tymon\JWTAuth\Contracts\JWTSubject $user * @return string */ public function fromUser(JWTSubject $user) { return $this->fromSubject($user); } /** * Refresh an expired token. * * @param bool $forceForever * @param bool $resetClaims * @return string */ public function refresh($forceForever = false, $resetClaims = false) { $this->requireToken(); return $this->manager->customClaims($this->getCustomClaims()) ->refresh($this->token, $forceForever, $resetClaims) ->get(); } /** * Invalidate a token (add it to the blacklist). * * @param bool $forceForever * @return $this */ public function invalidate($forceForever = false) { $this->requireToken(); $this->manager->invalidate($this->token, $forceForever); return $this; } /** * Alias to get the payload, and as a result checks that * the token is valid i.e. not expired or blacklisted. * * @return \Tymon\JWTAuth\Payload * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ public function checkOrFail() { return $this->getPayload(); } /** * Check that the token is valid. * * @param bool $getPayload * @return \Tymon\JWTAuth\Payload|bool */ public function check($getPayload = false) { try { $payload = $this->checkOrFail(); } catch (JWTException $e) { return false; } return $getPayload ? $payload : true; } /** * Get the token. * * @return \Tymon\JWTAuth\Token|null */ public function getToken() { if ($this->token === null) { try { $this->parseToken(); } catch (JWTException $e) { $this->token = null; } } return $this->token; } /** * Parse the token from the request. * * @return $this * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ public function parseToken() { if (! $token = $this->parser->parseToken()) { throw new JWTException('The token could not be parsed from the request'); } return $this->setToken($token); } /** * Get the raw Payload instance. * * @return \Tymon\JWTAuth\Payload */ public function getPayload() { $this->requireToken(); return $this->manager->decode($this->token); } /** * Alias for getPayload(). * * @return \Tymon\JWTAuth\Payload */ public function payload() { return $this->getPayload(); } /** * Convenience method to get a claim value. * * @param string $claim * @return mixed */ public function getClaim($claim) { return $this->payload()->get($claim); } /** * Create a Payload instance. * * @param \Tymon\JWTAuth\Contracts\JWTSubject $subject * @return \Tymon\JWTAuth\Payload */ public function makePayload(JWTSubject $subject) { return $this->factory()->customClaims($this->getClaimsArray($subject))->make(); } /** * Build the claims array and return it. * * @param \Tymon\JWTAuth\Contracts\JWTSubject $subject * @return array */ protected function getClaimsArray(JWTSubject $subject) { return array_merge( $this->getClaimsForSubject($subject), $subject->getJWTCustomClaims(), // custom claims from JWTSubject method $this->customClaims // custom claims from inline setter ); } /** * Get the claims associated with a given subject. * * @param \Tymon\JWTAuth\Contracts\JWTSubject $subject * @return array */ protected function getClaimsForSubject(JWTSubject $subject) { return array_merge([ 'sub' => $subject->getJWTIdentifier(), ], $this->lockSubject ? ['prv' => $this->hashSubjectModel($subject)] : []); } /** * Hash the subject model and return it. * * @param string|object $model * @return string */ protected function hashSubjectModel($model) { return sha1(is_object($model) ? get_class($model) : $model); } /** * Check if the subject model matches the one saved in the token. * * @param string|object $model * @return bool */ public function checkSubjectModel($model) { if (($prv = $this->payload()->get('prv')) === null) { return true; } return $this->hashSubjectModel($model) === $prv; } /** * Set the token. * * @param \Tymon\JWTAuth\Token|string $token * @return $this */ public function setToken($token) { $this->token = $token instanceof Token ? $token : new Token($token); return $this; } /** * Unset the current token. * * @return $this */ public function unsetToken() { $this->token = null; return $this; } /** * Ensure that a token is available. * * @return void * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ protected function requireToken() { if (! $this->token) { throw new JWTException('A token is required'); } } /** * Set the request instance. * * @param \Illuminate\Http\Request $request * @return $this */ public function setRequest(Request $request) { $this->parser->setRequest($request); return $this; } /** * Set whether the subject should be "locked". * * @param bool $lock * @return $this */ public function lockSubject($lock) { $this->lockSubject = $lock; return $this; } /** * Get the Manager instance. * * @return \Tymon\JWTAuth\Manager */ public function manager() { return $this->manager; } /** * Get the Parser instance. * * @return \Tymon\JWTAuth\Http\Parser\Parser */ public function parser() { return $this->parser; } /** * Get the Payload Factory. * * @return \Tymon\JWTAuth\Factory */ public function factory() { return $this->manager->getPayloadFactory(); } /** * Get the Blacklist. * * @return \Tymon\JWTAuth\Blacklist */ public function blacklist() { return $this->manager->getBlacklist(); } /** * Magically call the JWT Manager. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public function __call($method, $parameters) { if (method_exists($this->manager, $method)) { return call_user_func_array([$this->manager, $method], $parameters); } throw new BadMethodCallException("Method [$method] does not exist."); } } ================================================ FILE: src/JWTAuth.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use Tymon\JWTAuth\Contracts\Providers\Auth; use Tymon\JWTAuth\Http\Parser\Parser; /** @deprecated */ class JWTAuth extends JWT { /** * The authentication provider. * * @var \Tymon\JWTAuth\Contracts\Providers\Auth */ protected $auth; /** * Constructor. * * @param \Tymon\JWTAuth\Manager $manager * @param \Tymon\JWTAuth\Contracts\Providers\Auth $auth * @param \Tymon\JWTAuth\Http\Parser\Parser $parser * @return void */ public function __construct(Manager $manager, Auth $auth, Parser $parser) { parent::__construct($manager, $parser); $this->auth = $auth; } /** * Attempt to authenticate the user and return the token. * * @param array $credentials * @return false|string */ public function attempt(array $credentials) { if (! $this->auth->byCredentials($credentials)) { return false; } return $this->fromUser($this->user()); } /** * Authenticate a user via a token. * * @return \Tymon\JWTAuth\Contracts\JWTSubject|false */ public function authenticate() { $id = $this->getPayload()->get('sub'); if (! $this->auth->byId($id)) { return false; } return $this->user(); } /** * Alias for authenticate(). * * @return \Tymon\JWTAuth\Contracts\JWTSubject|false */ public function toUser() { return $this->authenticate(); } /** * Get the authenticated user. * * @return \Tymon\JWTAuth\Contracts\JWTSubject */ public function user() { return $this->auth->user(); } } ================================================ FILE: src/JWTGuard.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use BadMethodCallException; use Illuminate\Auth\GuardHelpers; use Illuminate\Contracts\Auth\Guard; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Http\Request; use Illuminate\Support\Traits\Macroable; use Tymon\JWTAuth\Contracts\JWTSubject; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\UserNotDefinedException; class JWTGuard implements Guard { use GuardHelpers, Macroable { __call as macroCall; } /** * The user we last attempted to retrieve. * * @var \Illuminate\Contracts\Auth\Authenticatable */ protected $lastAttempted; /** * The JWT instance. * * @var \Tymon\JWTAuth\JWT */ protected $jwt; /** * The request instance. * * @var \Illuminate\Http\Request */ protected $request; /** * Instantiate the class. * * @param \Tymon\JWTAuth\JWT $jwt * @param \Illuminate\Contracts\Auth\UserProvider $provider * @param \Illuminate\Http\Request $request * @return void */ public function __construct(JWT $jwt, UserProvider $provider, Request $request) { $this->jwt = $jwt; $this->provider = $provider; $this->request = $request; } /** * {@inheritDoc} */ public function user() { if ($this->user !== null) { return $this->user; } if ($this->jwt->setRequest($this->request)->getToken() && ($payload = $this->jwt->check(true)) && $this->validateSubject() ) { return $this->user = $this->provider->retrieveById($payload['sub']); } } /** * Get the currently authenticated user or throws an exception. * * @return \Illuminate\Contracts\Auth\Authenticatable * * @throws \Tymon\JWTAuth\Exceptions\UserNotDefinedException */ public function userOrFail() { if (! $user = $this->user()) { throw new UserNotDefinedException; } return $user; } /** * Validate a user's credentials. * * @param array $credentials * @return bool */ public function validate(array $credentials = []) { return (bool) $this->attempt($credentials, false); } /** * Attempt to authenticate the user using the given credentials and return the token. * * @param array $credentials * @param bool $login * @return bool|string */ public function attempt(array $credentials = [], $login = true) { $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); if ($this->hasValidCredentials($user, $credentials)) { return $login ? $this->login($user) : true; } return false; } /** * Create a token for a user. * * @param \Tymon\JWTAuth\Contracts\JWTSubject $user * @return string */ public function login(JWTSubject $user) { $token = $this->jwt->fromUser($user); $this->setToken($token)->setUser($user); return $token; } /** * Logout the user, thus invalidating the token. * * @param bool $forceForever * @return void */ public function logout($forceForever = false) { $this->requireToken()->invalidate($forceForever); $this->user = null; $this->jwt->unsetToken(); } /** * Refresh the token. * * @param bool $forceForever * @param bool $resetClaims * @return string */ public function refresh($forceForever = false, $resetClaims = false) { return $this->requireToken()->refresh($forceForever, $resetClaims); } /** * Invalidate the token. * * @param bool $forceForever * @return \Tymon\JWTAuth\JWT */ public function invalidate($forceForever = false) { return $this->requireToken()->invalidate($forceForever); } /** * Create a new token by User id. * * @param mixed $id * @return string|null */ public function tokenById($id) { if ($user = $this->provider->retrieveById($id)) { return $this->jwt->fromUser($user); } } /** * Log a user into the application using their credentials. * * @param array $credentials * @return bool */ public function once(array $credentials = []) { if ($this->validate($credentials)) { $this->setUser($this->lastAttempted); return true; } return false; } /** * Log the given User into the application. * * @param mixed $id * @return bool */ public function onceUsingId($id) { if ($user = $this->provider->retrieveById($id)) { $this->setUser($user); return true; } return false; } /** * Alias for onceUsingId. * * @param mixed $id * @return bool */ public function byId($id) { return $this->onceUsingId($id); } /** * Add any custom claims. * * @param array $claims * @return $this */ public function claims(array $claims) { $this->jwt->claims($claims); return $this; } /** * Get the raw Payload instance. * * @return \Tymon\JWTAuth\Payload */ public function getPayload() { return $this->requireToken()->getPayload(); } /** * Alias for getPayload(). * * @return \Tymon\JWTAuth\Payload */ public function payload() { return $this->getPayload(); } /** * Set the token. * * @param \Tymon\JWTAuth\Token|string $token * @return $this */ public function setToken($token) { $this->jwt->setToken($token); return $this; } /** * Set the token ttl. * * @param int $ttl * @return $this */ public function setTTL($ttl) { $this->jwt->factory()->setTTL($ttl); return $this; } /** * Get the user provider used by the guard. * * @return \Illuminate\Contracts\Auth\UserProvider */ public function getProvider() { return $this->provider; } /** * Set the user provider used by the guard. * * @param \Illuminate\Contracts\Auth\UserProvider $provider * @return $this */ public function setProvider(UserProvider $provider) { $this->provider = $provider; return $this; } /** * Return the currently cached user. * * @return \Illuminate\Contracts\Auth\Authenticatable|null */ public function getUser() { return $this->user; } /** * Get the current request instance. * * @return \Illuminate\Http\Request */ public function getRequest() { return $this->request ?: Request::createFromGlobals(); } /** * Set the current request instance. * * @param \Illuminate\Http\Request $request * @return $this */ public function setRequest(Request $request) { $this->request = $request; return $this; } /** * Get the token's auth factory. * * @return \Tymon\JWTAuth\Factory */ public function factory() { return $this->jwt->factory(); } /** * Get the last user we attempted to authenticate. * * @return \Illuminate\Contracts\Auth\Authenticatable */ public function getLastAttempted() { return $this->lastAttempted; } /** * Determine if the user matches the credentials. * * @param mixed $user * @param array $credentials * @return bool */ protected function hasValidCredentials($user, $credentials) { return $user !== null && $this->provider->validateCredentials($user, $credentials); } /** * Ensure the JWTSubject matches what is in the token. * * @return bool */ protected function validateSubject() { // If the provider doesn't have the necessary method // to get the underlying model name then allow. if (! method_exists($this->provider, 'getModel')) { return true; } return $this->jwt->checkSubjectModel($this->provider->getModel()); } /** * Ensure that a token is available in the request. * * @return \Tymon\JWTAuth\JWT * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ protected function requireToken() { if (! $this->jwt->setRequest($this->getRequest())->getToken()) { throw new JWTException('Token could not be parsed from the request.'); } return $this->jwt; } /** * Magically call the JWT instance. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public function __call($method, $parameters) { if (method_exists($this->jwt, $method)) { return call_user_func_array([$this->jwt, $method], $parameters); } if (static::hasMacro($method)) { return $this->macroCall($method, $parameters); } throw new BadMethodCallException("Method [$method] does not exist."); } } ================================================ FILE: src/Manager.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use Tymon\JWTAuth\Contracts\Providers\JWT as JWTContract; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenBlacklistedException; use Tymon\JWTAuth\Support\CustomClaims; use Tymon\JWTAuth\Support\RefreshFlow; class Manager { use CustomClaims, RefreshFlow; /** * The provider. * * @var \Tymon\JWTAuth\Contracts\Providers\JWT */ protected $provider; /** * The blacklist. * * @var \Tymon\JWTAuth\Blacklist */ protected $blacklist; /** * the payload factory. * * @var \Tymon\JWTAuth\Factory */ protected $payloadFactory; /** * The blacklist flag. * * @var bool */ protected $blacklistEnabled = true; /** * the persistent claims. * * @var array */ protected $persistentClaims = []; /** * Constructor. * * @param \Tymon\JWTAuth\Contracts\Providers\JWT $provider * @param \Tymon\JWTAuth\Blacklist $blacklist * @param \Tymon\JWTAuth\Factory $payloadFactory * @return void */ public function __construct(JWTContract $provider, Blacklist $blacklist, Factory $payloadFactory) { $this->provider = $provider; $this->blacklist = $blacklist; $this->payloadFactory = $payloadFactory; } /** * Encode a Payload and return the Token. * * @param \Tymon\JWTAuth\Payload $payload * @return \Tymon\JWTAuth\Token */ public function encode(Payload $payload) { $token = $this->provider->encode($payload->get()); return new Token($token); } /** * Decode a Token and return the Payload. * * @param \Tymon\JWTAuth\Token $token * @param bool $checkBlacklist * @return \Tymon\JWTAuth\Payload * * @throws \Tymon\JWTAuth\Exceptions\TokenBlacklistedException */ public function decode(Token $token, $checkBlacklist = true) { $payloadArray = $this->provider->decode($token->get()); $payload = $this->payloadFactory ->setRefreshFlow($this->refreshFlow) ->customClaims($payloadArray) ->make(); if ($checkBlacklist && $this->blacklistEnabled && $this->blacklist->has($payload)) { throw new TokenBlacklistedException('The token has been blacklisted'); } return $payload; } /** * Refresh a Token and return a new Token. * * @param \Tymon\JWTAuth\Token $token * @param bool $forceForever * @param bool $resetClaims * @return \Tymon\JWTAuth\Token */ public function refresh(Token $token, $forceForever = false, $resetClaims = false) { $this->setRefreshFlow(); $claims = $this->buildRefreshClaims($this->decode($token)); if ($this->blacklistEnabled) { // Invalidate old token $this->invalidate($token, $forceForever); } // Return the new token return $this->encode( $this->payloadFactory->customClaims($claims)->make($resetClaims) ); } /** * Invalidate a Token by adding it to the blacklist. * * @param \Tymon\JWTAuth\Token $token * @param bool $forceForever * @return bool * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ public function invalidate(Token $token, $forceForever = false) { if (! $this->blacklistEnabled) { throw new JWTException('You must have the blacklist enabled to invalidate a token.'); } return call_user_func( [$this->blacklist, $forceForever ? 'addForever' : 'add'], $this->decode($token, false) ); } /** * Build the claims to go into the refreshed token. * * @param \Tymon\JWTAuth\Payload $payload * @return array */ protected function buildRefreshClaims(Payload $payload) { // Get the claims to be persisted from the payload $persistentClaims = collect($payload->toArray()) ->only($this->persistentClaims) ->toArray(); // persist the relevant claims return array_merge( $this->customClaims, $persistentClaims, [ 'sub' => $payload['sub'], 'iat' => $payload['iat'], ] ); } /** * Get the Payload Factory instance. * * @return \Tymon\JWTAuth\Factory */ public function getPayloadFactory() { return $this->payloadFactory; } /** * Get the JWTProvider instance. * * @return \Tymon\JWTAuth\Contracts\Providers\JWT */ public function getJWTProvider() { return $this->provider; } /** * Get the Blacklist instance. * * @return \Tymon\JWTAuth\Blacklist */ public function getBlacklist() { return $this->blacklist; } /** * Set whether the blacklist is enabled. * * @param bool $enabled * @return $this */ public function setBlacklistEnabled($enabled) { $this->blacklistEnabled = $enabled; return $this; } /** * Set the claims to be persisted when refreshing a token. * * @param array $claims * @return $this */ public function setPersistentClaims(array $claims) { $this->persistentClaims = $claims; return $this; } } ================================================ FILE: src/Payload.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use ArrayAccess; use BadMethodCallException; use Countable; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Jsonable; use Illuminate\Support\Arr; use Illuminate\Support\Str; use JsonSerializable; use Tymon\JWTAuth\Claims\Claim; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Exceptions\PayloadException; use Tymon\JWTAuth\Validators\PayloadValidator; class Payload implements ArrayAccess, Arrayable, Countable, Jsonable, JsonSerializable { /** * The collection of claims. * * @var \Tymon\JWTAuth\Claims\Collection */ private $claims; /** * Build the Payload. * * @param \Tymon\JWTAuth\Claims\Collection $claims * @param \Tymon\JWTAuth\Validators\PayloadValidator $validator * @param bool $refreshFlow * @return void */ public function __construct(Collection $claims, PayloadValidator $validator, $refreshFlow = false) { $this->claims = $validator->setRefreshFlow($refreshFlow)->check($claims); } /** * Get the array of claim instances. * * @return \Tymon\JWTAuth\Claims\Collection */ public function getClaims() { return $this->claims; } /** * Checks if a payload matches some expected values. * * @param array $values * @param bool $strict * @return bool */ public function matches(array $values, $strict = false) { if (empty($values)) { return false; } $claims = $this->getClaims(); foreach ($values as $key => $value) { if (! $claims->has($key) || ! $claims->get($key)->matches($value, $strict)) { return false; } } return true; } /** * Checks if a payload strictly matches some expected values. * * @param array $values * @return bool */ public function matchesStrict(array $values) { return $this->matches($values, true); } /** * Get the payload. * * @param mixed $claim * @return mixed */ public function get($claim = null) { $claim = value($claim); if ($claim !== null) { if (is_array($claim)) { return array_map([$this, 'get'], $claim); } return Arr::get($this->toArray(), $claim); } return $this->toArray(); } /** * Get the underlying Claim instance. * * @param string $claim * @return \Tymon\JWTAuth\Claims\Claim */ public function getInternal($claim) { return $this->claims->getByClaimName($claim); } /** * Determine whether the payload has the claim (by instance). * * @param \Tymon\JWTAuth\Claims\Claim $claim * @return bool */ public function has(Claim $claim) { return $this->claims->has($claim->getName()); } /** * Determine whether the payload has the claim (by key). * * @param string $claim * @return bool */ public function hasKey($claim) { return $this->offsetExists($claim); } /** * Get the array of claims. * * @return array */ public function toArray() { return $this->claims->toPlainArray(); } /** * Convert the object into something JSON serializable. * * @return array */ #[\ReturnTypeWillChange] public function jsonSerialize() { return $this->toArray(); } /** * Get the payload as JSON. * * @param int $options * @return string */ public function toJson($options = JSON_UNESCAPED_SLASHES) { return json_encode($this->toArray(), $options); } /** * Get the payload as a string. * * @return string */ public function __toString() { return $this->toJson(); } /** * Determine if an item exists at an offset. * * @param mixed $key * @return bool */ #[\ReturnTypeWillChange] public function offsetExists($key) { return Arr::has($this->toArray(), $key); } /** * Get an item at a given offset. * * @param mixed $key * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($key) { return Arr::get($this->toArray(), $key); } /** * Don't allow changing the payload as it should be immutable. * * @param mixed $key * @param mixed $value * * @throws \Tymon\JWTAuth\Exceptions\PayloadException */ #[\ReturnTypeWillChange] public function offsetSet($key, $value) { throw new PayloadException('The payload is immutable'); } /** * Don't allow changing the payload as it should be immutable. * * @param string $key * @return void * * @throws \Tymon\JWTAuth\Exceptions\PayloadException */ #[\ReturnTypeWillChange] public function offsetUnset($key) { throw new PayloadException('The payload is immutable'); } /** * Count the number of claims. * * @return int */ #[\ReturnTypeWillChange] public function count() { return count($this->toArray()); } /** * Invoke the Payload as a callable function. * * @param mixed $claim * @return mixed */ public function __invoke($claim = null) { return $this->get($claim); } /** * Magically get a claim value. * * @param string $method * @param array $parameters * @return mixed * * @throws \BadMethodCallException */ public function __call($method, $parameters) { if (preg_match('/get(.+)\b/i', $method, $matches)) { foreach ($this->claims as $claim) { if (get_class($claim) === 'Tymon\\JWTAuth\\Claims\\'.$matches[1]) { return $claim->getValue(); } } } throw new BadMethodCallException(sprintf('The claim [%s] does not exist on the payload.', Str::after($method, 'get'))); } } ================================================ FILE: src/Providers/AbstractServiceProvider.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers; use Illuminate\Support\ServiceProvider; use Namshi\JOSE\JWS; use Tymon\JWTAuth\Blacklist; use Tymon\JWTAuth\Claims\Factory as ClaimFactory; use Tymon\JWTAuth\Console\JWTGenerateSecretCommand; use Tymon\JWTAuth\Contracts\Providers\Auth; use Tymon\JWTAuth\Contracts\Providers\JWT as JWTContract; use Tymon\JWTAuth\Contracts\Providers\Storage; use Tymon\JWTAuth\Factory; use Tymon\JWTAuth\Http\Middleware\Authenticate; use Tymon\JWTAuth\Http\Middleware\AuthenticateAndRenew; use Tymon\JWTAuth\Http\Middleware\Check; use Tymon\JWTAuth\Http\Middleware\RefreshToken; use Tymon\JWTAuth\Http\Parser\AuthHeaders; use Tymon\JWTAuth\Http\Parser\InputSource; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\Http\Parser\QueryString; use Tymon\JWTAuth\JWT; use Tymon\JWTAuth\JWTAuth; use Tymon\JWTAuth\JWTGuard; use Tymon\JWTAuth\Manager; use Tymon\JWTAuth\Providers\JWT\Lcobucci; use Tymon\JWTAuth\Providers\JWT\Namshi; use Tymon\JWTAuth\Validators\PayloadValidator; abstract class AbstractServiceProvider extends ServiceProvider { /** * The middleware aliases. * * @var array */ protected $middlewareAliases = [ 'jwt.auth' => Authenticate::class, 'jwt.check' => Check::class, 'jwt.refresh' => RefreshToken::class, 'jwt.renew' => AuthenticateAndRenew::class, ]; /** * Boot the service provider. * * @return void */ abstract public function boot(); /** * Register the service provider. * * @return void */ public function register() { $this->registerAliases(); $this->registerJWTProvider(); $this->registerAuthProvider(); $this->registerStorageProvider(); $this->registerJWTBlacklist(); $this->registerManager(); $this->registerTokenParser(); $this->registerJWT(); $this->registerJWTAuth(); $this->registerPayloadValidator(); $this->registerClaimFactory(); $this->registerPayloadFactory(); $this->registerJWTCommand(); $this->commands('tymon.jwt.secret'); } /** * Extend Laravel's Auth. * * @return void */ protected function extendAuthGuard() { $this->app['auth']->extend('jwt', function ($app, $name, array $config) { $guard = new JWTGuard( $app['tymon.jwt'], $app['auth']->createUserProvider($config['provider']), $app['request'] ); $app->refresh('request', $guard, 'setRequest'); return $guard; }); } /** * Bind some aliases. * * @return void */ protected function registerAliases() { $this->app->alias('tymon.jwt', JWT::class); $this->app->alias('tymon.jwt.auth', JWTAuth::class); $this->app->alias('tymon.jwt.provider.jwt', JWTContract::class); $this->app->alias('tymon.jwt.provider.jwt.namshi', Namshi::class); $this->app->alias('tymon.jwt.provider.jwt.lcobucci', Lcobucci::class); $this->app->alias('tymon.jwt.provider.auth', Auth::class); $this->app->alias('tymon.jwt.provider.storage', Storage::class); $this->app->alias('tymon.jwt.manager', Manager::class); $this->app->alias('tymon.jwt.blacklist', Blacklist::class); $this->app->alias('tymon.jwt.payload.factory', Factory::class); $this->app->alias('tymon.jwt.validators.payload', PayloadValidator::class); } /** * Register the bindings for the JSON Web Token provider. * * @return void */ protected function registerJWTProvider() { $this->registerNamshiProvider(); $this->registerLcobucciProvider(); $this->app->singleton('tymon.jwt.provider.jwt', function ($app) { return $this->getConfigInstance('providers.jwt'); }); } /** * Register the bindings for the Lcobucci JWT provider. * * @return void */ protected function registerNamshiProvider() { $this->app->singleton('tymon.jwt.provider.jwt.namshi', function ($app) { return new Namshi( new JWS(['typ' => 'JWT', 'alg' => $this->config('algo')]), $this->config('secret'), $this->config('algo'), $this->config('keys') ); }); } /** * Register the bindings for the Lcobucci JWT provider. * * @return void */ protected function registerLcobucciProvider() { $this->app->singleton('tymon.jwt.provider.jwt.lcobucci', function ($app) { return new Lcobucci( $this->config('secret'), $this->config('algo'), $this->config('keys') ); }); } /** * Register the bindings for the Auth provider. * * @return void */ protected function registerAuthProvider() { $this->app->singleton('tymon.jwt.provider.auth', function () { return $this->getConfigInstance('providers.auth'); }); } /** * Register the bindings for the Storage provider. * * @return void */ protected function registerStorageProvider() { $this->app->singleton('tymon.jwt.provider.storage', function () { return $this->getConfigInstance('providers.storage'); }); } /** * Register the bindings for the JWT Manager. * * @return void */ protected function registerManager() { $this->app->singleton('tymon.jwt.manager', function ($app) { $instance = new Manager( $app['tymon.jwt.provider.jwt'], $app['tymon.jwt.blacklist'], $app['tymon.jwt.payload.factory'] ); return $instance->setBlacklistEnabled((bool) $this->config('blacklist_enabled')) ->setPersistentClaims($this->config('persistent_claims')); }); } /** * Register the bindings for the Token Parser. * * @return void */ protected function registerTokenParser() { $this->app->singleton('tymon.jwt.parser', function ($app) { $parser = new Parser( $app['request'], [ new AuthHeaders, new QueryString, new InputSource, ] ); $app->refresh('request', $parser, 'setRequest'); return $parser; }); } /** * Register the bindings for the main JWT class. * * @return void */ protected function registerJWT() { $this->app->singleton('tymon.jwt', function ($app) { return (new JWT( $app['tymon.jwt.manager'], $app['tymon.jwt.parser'] ))->lockSubject($this->config('lock_subject')); }); } /** * Register the bindings for the main JWTAuth class. * * @return void */ protected function registerJWTAuth() { $this->app->singleton('tymon.jwt.auth', function ($app) { return (new JWTAuth( $app['tymon.jwt.manager'], $app['tymon.jwt.provider.auth'], $app['tymon.jwt.parser'] ))->lockSubject($this->config('lock_subject')); }); } /** * Register the bindings for the Blacklist. * * @return void */ protected function registerJWTBlacklist() { $this->app->singleton('tymon.jwt.blacklist', function ($app) { $instance = new Blacklist($app['tymon.jwt.provider.storage']); return $instance->setGracePeriod($this->config('blacklist_grace_period')) ->setRefreshTTL($this->config('refresh_ttl')); }); } /** * Register the bindings for the payload validator. * * @return void */ protected function registerPayloadValidator() { $this->app->singleton('tymon.jwt.validators.payload', function () { return (new PayloadValidator) ->setRefreshTTL($this->config('refresh_ttl')) ->setRequiredClaims($this->config('required_claims')); }); } /** * Register the bindings for the Claim Factory. * * @return void */ protected function registerClaimFactory() { $this->app->singleton('tymon.jwt.claim.factory', function ($app) { $factory = new ClaimFactory($app['request']); $app->refresh('request', $factory, 'setRequest'); return $factory->setTTL($this->config('ttl')) ->setLeeway($this->config('leeway')); }); } /** * Register the bindings for the Payload Factory. * * @return void */ protected function registerPayloadFactory() { $this->app->singleton('tymon.jwt.payload.factory', function ($app) { return new Factory( $app['tymon.jwt.claim.factory'], $app['tymon.jwt.validators.payload'] ); }); } /** * Register the Artisan command. * * @return void */ protected function registerJWTCommand() { $this->app->singleton('tymon.jwt.secret', function () { return new JWTGenerateSecretCommand; }); } /** * Helper to get the config values. * * @param string $key * @param string $default * @return mixed */ protected function config($key, $default = null) { return config("jwt.$key", $default); } /** * Get an instantiable configuration instance. * * @param string $key * @return mixed */ protected function getConfigInstance($key) { $instance = $this->config($key); if (is_string($instance)) { return $this->app->make($instance); } return $instance; } } ================================================ FILE: src/Providers/Auth/Illuminate.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers\Auth; use Illuminate\Contracts\Auth\Guard as GuardContract; use Tymon\JWTAuth\Contracts\Providers\Auth; class Illuminate implements Auth { /** * The authentication guard. * * @var \Illuminate\Contracts\Auth\Guard */ protected $auth; /** * Constructor. * * @param \Illuminate\Contracts\Auth\Guard $auth * @return void */ public function __construct(GuardContract $auth) { $this->auth = $auth; } /** * Check a user's credentials. * * @param array $credentials * @return bool */ public function byCredentials(array $credentials) { return $this->auth->once($credentials); } /** * Authenticate a user via the id. * * @param mixed $id * @return bool */ public function byId($id) { return $this->auth->onceUsingId($id); } /** * Get the currently authenticated user. * * @return mixed */ public function user() { return $this->auth->user(); } } ================================================ FILE: src/Providers/JWT/Lcobucci.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers\JWT; use Composer\InstalledVersions; use DateTimeImmutable; use DateTimeInterface; use Exception; use Illuminate\Support\Collection; use Lcobucci\JWT\Configuration; use Lcobucci\JWT\Signer; use Lcobucci\JWT\Signer\Ecdsa; use Lcobucci\JWT\Signer\Key; use Lcobucci\JWT\Signer\Key\InMemory; use Lcobucci\JWT\Signer\Rsa; use Lcobucci\JWT\Token\Builder; use Lcobucci\JWT\Token\RegisteredClaims; use Lcobucci\JWT\Validation\Constraint\SignedWith; use Tymon\JWTAuth\Contracts\Providers\JWT; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; class Lcobucci extends Provider implements JWT { /** * \Lcobucci\JWT\Signer. */ protected $signer; /** * \Lcobucci\JWT\Configuration. */ protected $config; /** * Create the Lcobucci provider. * * @param string $secret * @param string $algo * @param array $keys * @param \Lcobucci\JWT\Configuration|null $config * @return void */ public function __construct($secret, $algo, array $keys, $config = null) { parent::__construct($secret, $algo, $keys); $this->signer = $this->getSigner(); $this->config = $config ?: $this->buildConfig(); } /** * Signers that this provider supports. * * @var array */ protected $signers = [ self::ALGO_HS256 => Signer\Hmac\Sha256::class, self::ALGO_HS384 => Signer\Hmac\Sha384::class, self::ALGO_HS512 => Signer\Hmac\Sha512::class, self::ALGO_RS256 => Signer\Rsa\Sha256::class, self::ALGO_RS384 => Signer\Rsa\Sha384::class, self::ALGO_RS512 => Signer\Rsa\Sha512::class, self::ALGO_ES256 => Signer\Ecdsa\Sha256::class, self::ALGO_ES384 => Signer\Ecdsa\Sha384::class, self::ALGO_ES512 => Signer\Ecdsa\Sha512::class, ]; /** * Create a JSON Web Token. * * @param array $payload * @return string * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ public function encode(array $payload) { $builder = $this->getBuilderFromClaims($payload); try { return $builder ->getToken($this->config->signer(), $this->config->signingKey()) ->toString(); } catch (Exception $e) { throw new JWTException('Could not create token: '.$e->getMessage(), $e->getCode(), $e); } } /** * Decode a JSON Web Token. * * @param string $token * @return array * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ public function decode($token) { try { /** @var \Lcobucci\JWT\Token\Plain */ $token = $this->config->parser()->parse($token); } catch (Exception $e) { throw new TokenInvalidException('Could not decode token: '.$e->getMessage(), $e->getCode(), $e); } if (! $this->config->validator()->validate($token, ...$this->config->validationConstraints())) { throw new TokenInvalidException('Token Signature could not be verified.'); } return Collection::wrap($token->claims()->all()) ->map(function ($claim) { if ($claim instanceof DateTimeInterface) { return $claim->getTimestamp(); } return is_object($claim) && method_exists($claim, 'getValue') ? $claim->getValue() : $claim; }) ->toArray(); } /** * Create an instance of the builder with all of the claims applied. * * @param array $payload * @return \Lcobucci\JWT\Token\Builder */ protected function getBuilderFromClaims(array $payload): Builder { return array_reduce( array_keys($payload), function (Builder $builder, string $key) use ($payload): Builder { $value = $payload[$key]; return match ($key) { RegisteredClaims::ID => $builder->identifiedBy($value), RegisteredClaims::EXPIRATION_TIME => $builder->expiresAt(DateTimeImmutable::createFromFormat('U', $value)), RegisteredClaims::NOT_BEFORE => $builder->canOnlyBeUsedAfter(DateTimeImmutable::createFromFormat('U', $value)), RegisteredClaims::ISSUED_AT => $builder->issuedAt(DateTimeImmutable::createFromFormat('U', $value)), RegisteredClaims::ISSUER => $builder->issuedBy($value), RegisteredClaims::AUDIENCE => $builder->permittedFor($value), RegisteredClaims::SUBJECT => $builder->relatedTo($value), default => $builder->withClaim($key, $value), }; }, $this->config->builder() ); } /** * Build the configuration. * * @return \Lcobucci\JWT\Configuration */ protected function buildConfig(): Configuration { $config = $this->isAsymmetric() ? Configuration::forAsymmetricSigner( $this->signer, $this->getSigningKey(), $this->getVerificationKey() ) : Configuration::forSymmetricSigner($this->signer, $this->getSigningKey()); if (method_exists($config, 'withValidationConstraints')) { return $config->withValidationConstraints( new SignedWith($this->signer, $this->getVerificationKey()) ); } $config->setValidationConstraints( new SignedWith($this->signer, $this->getVerificationKey()) ); return $config; } /** * Get the signer instance. * * @return \Lcobucci\JWT\Signer * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ protected function getSigner() { if (! array_key_exists($this->algo, $this->signers)) { throw new JWTException('The given algorithm could not be found'); } $signer = $this->signers[$this->algo]; if (is_subclass_of($signer, Ecdsa::class) && $this->usingV4()) { return $signer::create(); } return new $signer(); } /** * {@inheritdoc} */ protected function isAsymmetric() { return is_subclass_of($this->signer, Rsa::class) || is_subclass_of($this->signer, Ecdsa::class); } /** * {@inheritdoc} * * @return \Lcobucci\JWT\Signer\Key * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ protected function getSigningKey() { if ($this->isAsymmetric()) { if (! $privateKey = $this->getPrivateKey()) { throw new JWTException('Private key is not set.'); } return $this->getKey($privateKey, $this->getPassphrase() ?? ''); } if (! $secret = $this->getSecret()) { throw new JWTException('Secret is not set.'); } return $this->getKey($secret); } /** * {@inheritdoc} * * @return \Lcobucci\JWT\Signer\Key * * @throws \Tymon\JWTAuth\Exceptions\JWTException */ protected function getVerificationKey() { if ($this->isAsymmetric()) { if (! $public = $this->getPublicKey()) { throw new JWTException('Public key is not set.'); } return $this->getKey($public); } if (! $secret = $this->getSecret()) { throw new JWTException('Secret is not set.'); } return $this->getKey($secret); } /** * Get the signing key instance. */ protected function getKey(string $contents, string $passphrase = ''): Key { return InMemory::plainText($contents, $passphrase); } /** * Determine if the lcobucci/jwt package version is less than 5.0.0. */ protected function usingV4(): bool { return version_compare(InstalledVersions::getPrettyVersion('lcobucci/jwt'), '5.0.0', '<'); } } ================================================ FILE: src/Providers/JWT/Provider.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers\JWT; use Illuminate\Support\Arr; abstract class Provider { const ALGO_HS256 = 'HS256'; const ALGO_HS384 = 'HS384'; const ALGO_HS512 = 'HS512'; const ALGO_RS256 = 'RS256'; const ALGO_RS384 = 'RS384'; const ALGO_RS512 = 'RS512'; const ALGO_ES256 = 'ES256'; const ALGO_ES384 = 'ES384'; const ALGO_ES512 = 'ES512'; /** * The secret. * * @var string */ protected $secret; /** * The array of keys. * * @var array */ protected $keys; /** * The used algorithm. * * @var string */ protected $algo; /** * Constructor. * * @param string $secret * @param string $algo * @param array $keys * @return void */ public function __construct($secret, $algo, array $keys) { $this->secret = $secret; $this->algo = $algo; $this->keys = $keys; } /** * Set the algorithm used to sign the token. * * @param string $algo * @return $this */ public function setAlgo($algo) { $this->algo = $algo; return $this; } /** * Get the algorithm used to sign the token. * * @return string */ public function getAlgo() { return $this->algo; } /** * Set the secret used to sign the token. * * @param string $secret * @return $this */ public function setSecret($secret) { $this->secret = $secret; return $this; } /** * Get the secret used to sign the token. * * @return string */ public function getSecret() { return $this->secret; } /** * Set the keys used to sign the token. * * @param array $keys * @return $this */ public function setKeys(array $keys) { $this->keys = $keys; return $this; } /** * Get the array of keys used to sign tokens with an asymmetric algorithm. * * @return array */ public function getKeys() { return $this->keys; } /** * Get the public key used to sign tokens with an asymmetric algorithm. * * @return string|null */ public function getPublicKey() { return Arr::get($this->keys, 'public'); } /** * Get the private key used to sign tokens with an asymmetric algorithm. * * @return string|null */ public function getPrivateKey() { return Arr::get($this->keys, 'private'); } /** * Get the passphrase used to sign tokens * with an asymmetric algorithm. * * @return string|null */ public function getPassphrase() { return Arr::get($this->keys, 'passphrase'); } /** * Get the key used to sign the tokens. * * @return string|null */ protected function getSigningKey() { return $this->isAsymmetric() ? $this->getPrivateKey() : $this->getSecret(); } /** * Get the key used to verify the tokens. * * @return string|null */ protected function getVerificationKey() { return $this->isAsymmetric() ? $this->getPublicKey() : $this->getSecret(); } /** * Determine if the algorithm is asymmetric, and thus requires a public/private key combo. * * @return bool */ abstract protected function isAsymmetric(); } ================================================ FILE: src/Providers/LaravelServiceProvider.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers; use Tymon\JWTAuth\Http\Parser\Cookies; use Tymon\JWTAuth\Http\Parser\RouteParams; class LaravelServiceProvider extends AbstractServiceProvider { /** * {@inheritdoc} */ public function boot() { $path = realpath(__DIR__.'/../../config/config.php'); $this->publishes([$path => config_path('jwt.php')], 'config'); $this->mergeConfigFrom($path, 'jwt'); $this->aliasMiddleware(); $this->extendAuthGuard(); $this->app['tymon.jwt.parser']->addParser([ new RouteParams, new Cookies($this->config('decrypt_cookies')), ]); } /** * {@inheritdoc} */ protected function registerStorageProvider() { $this->app->singleton('tymon.jwt.provider.storage', function () { $instance = $this->getConfigInstance('providers.storage'); if (method_exists($instance, 'setLaravelVersion')) { $instance->setLaravelVersion($this->app->version()); } return $instance; }); } /** * Alias the middleware. * * @return void */ protected function aliasMiddleware() { $router = $this->app['router']; $method = method_exists($router, 'aliasMiddleware') ? 'aliasMiddleware' : 'middleware'; foreach ($this->middlewareAliases as $alias => $middleware) { $router->$method($alias, $middleware); } } } ================================================ FILE: src/Providers/LumenServiceProvider.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers; use Tymon\JWTAuth\Http\Parser\LumenRouteParams; class LumenServiceProvider extends AbstractServiceProvider { /** * {@inheritdoc} */ public function boot() { $this->app->configure('jwt'); $path = realpath(__DIR__.'/../../config/config.php'); $this->mergeConfigFrom($path, 'jwt'); $this->app->routeMiddleware($this->middlewareAliases); $this->extendAuthGuard(); $this->app['tymon.jwt.parser']->addParser(new LumenRouteParams); } } ================================================ FILE: src/Providers/Storage/Illuminate.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Providers\Storage; use BadMethodCallException; use Illuminate\Contracts\Cache\Repository as CacheContract; use Psr\SimpleCache\CacheInterface as PsrCacheInterface; use Tymon\JWTAuth\Contracts\Providers\Storage; class Illuminate implements Storage { /** * The cache repository contract. * * @var \Illuminate\Contracts\Cache\Repository */ protected $cache; /** * The used cache tag. * * @var string */ protected $tag = 'tymon.jwt'; /** * @var bool */ protected $supportsTags; /** * @var string|null */ protected $laravelVersion; /** * Constructor. * * @param \Illuminate\Contracts\Cache\Repository $cache * @return void */ public function __construct(CacheContract $cache) { $this->cache = $cache; } /** * Add a new item into storage. * * @param string $key * @param mixed $value * @param int $minutes * @return void */ public function add($key, $value, $minutes) { // If the laravel version is 5.8 or higher then convert minutes to seconds. if ($this->laravelVersion !== null && is_int($minutes) && version_compare($this->laravelVersion, '5.8', '>=') ) { $minutes = $minutes * 60; } $this->cache()->put($key, $value, $minutes); } /** * Add a new item into storage forever. * * @param string $key * @param mixed $value * @return void */ public function forever($key, $value) { $this->cache()->forever($key, $value); } /** * Get an item from storage. * * @param string $key * @return mixed */ public function get($key) { return $this->cache()->get($key); } /** * Remove an item from storage. * * @param string $key * @return bool */ public function destroy($key) { return $this->cache()->forget($key); } /** * Remove all items associated with the tag. * * @return void */ public function flush() { $this->cache()->flush(); } /** * Return the cache instance with tags attached. * * @return \Illuminate\Contracts\Cache\Repository */ protected function cache() { if ($this->supportsTags === null) { $this->determineTagSupport(); } if ($this->supportsTags) { return $this->cache->tags($this->tag); } return $this->cache; } /** * Set the laravel version. */ public function setLaravelVersion($version) { $this->laravelVersion = $version; return $this; } /** * Detect as best we can whether tags are supported with this repository & store, * and save our result on the $supportsTags flag. * * @return void */ protected function determineTagSupport() { // Laravel >= 5.1.28 if (method_exists($this->cache, 'tags') || $this->cache instanceof PsrCacheInterface) { try { // Attempt the repository tags command, which throws exceptions when unsupported $this->cache->tags($this->tag); $this->supportsTags = true; } catch (BadMethodCallException $ex) { $this->supportsTags = false; } } else { // Laravel <= 5.1.27 if (method_exists($this->cache, 'getStore')) { // Check for the tags function directly on the store $this->supportsTags = method_exists($this->cache->getStore(), 'tags'); } else { // Must be using custom cache repository without getStore(), and all bets are off, // or we are mocking the cache contract (in testing), which will not create a getStore method $this->supportsTags = false; } } } } ================================================ FILE: src/Support/CustomClaims.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Support; trait CustomClaims { /** * Custom claims. * * @var array */ protected $customClaims = []; /** * Set the custom claims. * * @param array $customClaims * @return $this */ public function customClaims(array $customClaims) { $this->customClaims = $customClaims; return $this; } /** * Alias to set the custom claims. * * @param array $customClaims * @return $this */ public function claims(array $customClaims) { return $this->customClaims($customClaims); } /** * Get the custom claims. * * @return array */ public function getCustomClaims() { return $this->customClaims; } } ================================================ FILE: src/Support/RefreshFlow.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Support; trait RefreshFlow { /** * The refresh flow flag. * * @var bool */ protected $refreshFlow = false; /** * Set the refresh flow flag. * * @param bool $refreshFlow * @return $this */ public function setRefreshFlow($refreshFlow = true) { $this->refreshFlow = $refreshFlow; return $this; } } ================================================ FILE: src/Support/Utils.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Support; use Carbon\Carbon; class Utils { /** * Get the Carbon instance for the current time. * * @return \Carbon\Carbon */ public static function now() { return Carbon::now('UTC'); } /** * Get the Carbon instance for the timestamp. * * @param int $timestamp * @return \Carbon\Carbon */ public static function timestamp($timestamp) { return Carbon::createFromTimestampUTC($timestamp)->timezone('UTC'); } /** * Checks if a timestamp is in the past. * * @param int $timestamp * @param int $leeway * @return bool */ public static function isPast($timestamp, $leeway = 0) { $timestamp = static::timestamp($timestamp); return $leeway > 0 ? $timestamp->addSeconds($leeway)->isPast() : $timestamp->isPast(); } /** * Checks if a timestamp is in the future. * * @param int $timestamp * @param int $leeway * @return bool */ public static function isFuture($timestamp, $leeway = 0) { $timestamp = static::timestamp($timestamp); return $leeway > 0 ? $timestamp->subSeconds($leeway)->isFuture() : $timestamp->isFuture(); } } ================================================ FILE: src/Token.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth; use Tymon\JWTAuth\Validators\TokenValidator; class Token { /** * @var string */ private $value; /** * Create a new JSON Web Token. * * @param string $value * @return void */ public function __construct($value) { $this->value = (string) (new TokenValidator)->check($value); } /** * Get the token. * * @return string */ public function get() { return $this->value; } /** * Get the token when casting to string. * * @return string */ public function __toString() { return $this->get(); } } ================================================ FILE: src/Validators/PayloadValidator.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Validators; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Exceptions\TokenInvalidException; class PayloadValidator extends Validator { /** * The required claims. * * @var array */ protected $requiredClaims = [ 'iss', 'iat', 'exp', 'nbf', 'sub', 'jti', ]; /** * The refresh TTL. * * @var int */ protected $refreshTTL = 20160; /** * Run the validations on the payload array. * * @param \Tymon\JWTAuth\Claims\Collection $value * @return \Tymon\JWTAuth\Claims\Collection */ public function check($value) { $this->validateStructure($value); return $this->refreshFlow ? $this->validateRefresh($value) : $this->validatePayload($value); } /** * Ensure the payload contains the required claims and * the claims have the relevant type. * * @param \Tymon\JWTAuth\Claims\Collection $claims * @return void * * @throws \Tymon\JWTAuth\Exceptions\TokenInvalidException */ protected function validateStructure(Collection $claims) { if ($this->requiredClaims && ! $claims->hasAllClaims($this->requiredClaims)) { throw new TokenInvalidException('JWT payload does not contain the required claims'); } } /** * Validate the payload timestamps. * * @param \Tymon\JWTAuth\Claims\Collection $claims * @return \Tymon\JWTAuth\Claims\Collection * * @throws \Tymon\JWTAuth\Exceptions\TokenExpiredException * @throws \Tymon\JWTAuth\Exceptions\TokenInvalidException */ protected function validatePayload(Collection $claims) { return $claims->validate('payload'); } /** * Check the token in the refresh flow context. * * @param \Tymon\JWTAuth\Claims\Collection $claims * @return \Tymon\JWTAuth\Claims\Collection * * @throws \Tymon\JWTAuth\Exceptions\TokenExpiredException */ protected function validateRefresh(Collection $claims) { return $this->refreshTTL === null ? $claims : $claims->validate('refresh', $this->refreshTTL); } /** * Set the required claims. * * @param array $claims * @return $this */ public function setRequiredClaims(array $claims) { $this->requiredClaims = $claims; return $this; } /** * Set the refresh ttl. * * @param int $ttl * @return $this */ public function setRefreshTTL($ttl) { $this->refreshTTL = $ttl !== null ? (int) $ttl : null; return $this; } } ================================================ FILE: src/Validators/TokenValidator.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Validators; use Tymon\JWTAuth\Exceptions\TokenInvalidException; class TokenValidator extends Validator { /** * Check the structure of the token. * * @param string $value * @return string */ public function check($value) { return $this->validateStructure($value); } /** * @param string $token * @return string * * @throws \Tymon\JWTAuth\Exceptions\TokenInvalidException */ protected function validateStructure($token) { $parts = explode('.', $token); if (count($parts) !== 3) { throw new TokenInvalidException('Wrong number of segments'); } $parts = array_filter(array_map('trim', $parts)); if (count($parts) !== 3 || implode('.', $parts) !== $token) { throw new TokenInvalidException('Malformed token'); } return $token; } } ================================================ FILE: src/Validators/Validator.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Validators; use Tymon\JWTAuth\Contracts\Validator as ValidatorContract; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Support\RefreshFlow; abstract class Validator implements ValidatorContract { use RefreshFlow; /** * Helper function to return a boolean. * * @param array $value * @return bool */ public function isValid($value) { try { $this->check($value); } catch (JWTException $e) { return false; } return true; } /** * Run the validation. * * @param array $value * @return void */ abstract public function check($value); } ================================================ FILE: tests/AbstractTestCase.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Carbon\Carbon; use Mockery; use PHPUnit\Framework\TestCase; abstract class AbstractTestCase extends TestCase { /** * @var int */ protected $testNowTimestamp; public function setUp(): void { parent::setUp(); Carbon::setTestNow($now = Carbon::now()); $this->testNowTimestamp = $now->getTimestamp(); } public function tearDown(): void { Carbon::setTestNow(); Mockery::close(); parent::tearDown(); } } ================================================ FILE: tests/BlacklistTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Mockery; use Tymon\JWTAuth\Blacklist; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Contracts\Providers\Storage; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Validators\PayloadValidator; class BlacklistTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Contracts\Providers\Storage|\Mockery\MockInterface */ protected $storage; /** * @var \Tymon\JWTAuth\Blacklist */ protected $blacklist; /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Validators\Validator */ protected $validator; public function setUp(): void { parent::setUp(); $this->storage = Mockery::mock(Storage::class); $this->blacklist = new Blacklist($this->storage); $this->validator = Mockery::mock(PayloadValidator::class); } /** @test */ public function it_should_add_a_valid_token_to_the_blacklist() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $refreshTTL = 20161; $this->storage->shouldReceive('get') ->with('foo') ->once() ->andReturn([]); $this->storage->shouldReceive('add') ->with('foo', ['valid_until' => $this->testNowTimestamp], $refreshTTL + 1) ->once(); $this->blacklist->setRefreshTTL($refreshTTL)->add($payload); } /** @test */ public function it_should_add_a_token_with_no_exp_to_the_blacklist_forever() { $claims = [ new Subject(1), new Issuer('http://example.com'), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('forever')->with('foo', 'forever')->once(); $this->blacklist->add($payload); } /** @test */ public function it_should_return_true_when_adding_an_expired_token_to_the_blacklist() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp - 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator, true); $refreshTTL = 20161; $this->storage->shouldReceive('get') ->with('foo') ->once() ->andReturn([]); $this->storage->shouldReceive('add') ->with('foo', ['valid_until' => $this->testNowTimestamp], $refreshTTL + 1) ->once(); $this->assertTrue($this->blacklist->setRefreshTTL($refreshTTL)->add($payload)); } /** @test */ public function it_should_return_true_early_when_adding_an_item_and_it_already_exists() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp - 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator, true); $refreshTTL = 20161; $this->storage->shouldReceive('get') ->with('foo') ->once() ->andReturn(['valid_until' => $this->testNowTimestamp]); $this->storage->shouldReceive('add') ->with('foo', ['valid_until' => $this->testNowTimestamp], $refreshTTL + 1) ->never(); $this->assertTrue($this->blacklist->setRefreshTTL($refreshTTL)->add($payload)); } /** @test */ public function it_should_check_whether_a_token_has_been_blacklisted() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foobar'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('get')->with('foobar')->once()->andReturn(['valid_until' => $this->testNowTimestamp]); $this->assertTrue($this->blacklist->has($payload)); } public function blacklist_provider() { return [ [null], [0], [''], [[]], [['valid_until' => strtotime('+1day')]], ]; } /** * @test * * @dataProvider blacklist_provider * * @param mixed $result */ public function it_should_check_whether_a_token_has_not_been_blacklisted($result) { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foobar'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('get')->with('foobar')->once()->andReturn($result); $this->assertFalse($this->blacklist->has($payload)); } /** @test */ public function it_should_check_whether_a_token_has_been_blacklisted_forever() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foobar'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('get')->with('foobar')->once()->andReturn('forever'); $this->assertTrue($this->blacklist->has($payload)); } /** @test */ public function it_should_check_whether_a_token_has_been_blacklisted_when_the_token_is_not_blacklisted() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foobar'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('get')->with('foobar')->once()->andReturn(null); $this->assertFalse($this->blacklist->has($payload)); } /** @test */ public function it_should_remove_a_token_from_the_blacklist() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foobar'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('destroy')->with('foobar')->andReturn(true); $this->assertTrue($this->blacklist->remove($payload)); } /** @test */ public function it_should_set_a_custom_unique_key_for_the_blacklist() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foobar'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->storage->shouldReceive('get')->with(1)->once()->andReturn(['valid_until' => $this->testNowTimestamp]); $this->assertTrue($this->blacklist->setKey('sub')->has($payload)); $this->assertSame(1, $this->blacklist->getKey($payload)); } /** @test */ public function it_should_empty_the_blacklist() { $this->storage->shouldReceive('flush'); $this->assertTrue($this->blacklist->clear()); } /** @test */ public function it_should_set_and_get_the_blacklist_grace_period() { $this->assertInstanceOf(Blacklist::class, $this->blacklist->setGracePeriod(15)); $this->assertSame(15, $this->blacklist->getGracePeriod()); } /** @test */ public function it_should_set_and_get_the_blacklist_refresh_ttl() { $this->assertInstanceOf(Blacklist::class, $this->blacklist->setRefreshTTL(15)); $this->assertSame(15, $this->blacklist->getRefreshTTL()); } } ================================================ FILE: tests/Claims/ClaimTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Claims; use Illuminate\Contracts\Support\Arrayable; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Test\AbstractTestCase; class ClaimTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Claims\Expiration */ protected $claim; public function setUp(): void { parent::setUp(); $this->claim = new Expiration($this->testNowTimestamp); } /** @test */ public function it_should_throw_an_exception_when_passing_an_invalid_value() { $this->expectException(InvalidClaimException::class); $this->expectExceptionMessage('Invalid value provided for claim [exp]'); $this->claim->setValue('foo'); } /** @test */ public function it_should_convert_the_claim_to_an_array() { $this->assertSame(['exp' => $this->testNowTimestamp], $this->claim->toArray()); } /** @test */ public function it_should_get_the_claim_as_a_string() { $this->assertJsonStringEqualsJsonString((string) $this->claim, $this->claim->toJson()); } /** @test */ public function it_should_get_the_object_as_json() { $this->assertJsonStringEqualsJsonString(json_encode($this->claim), $this->claim->toJson()); } /** @test */ public function it_should_implement_arrayable() { $this->assertInstanceOf(Arrayable::class, $this->claim); } } ================================================ FILE: tests/Claims/CollectionTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Claims; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Test\AbstractTestCase; class CollectionTest extends AbstractTestCase { private function getCollection() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; return new Collection($claims); } /** @test */ public function it_should_sanitize_the_claims_to_associative_array() { $collection = $this->getCollection(); $this->assertSame(array_keys($collection->toArray()), ['sub', 'iss', 'exp', 'nbf', 'iat', 'jti']); } /** @test */ public function it_should_determine_if_a_collection_contains_all_the_given_claims() { $collection = $this->getCollection(); $this->assertFalse($collection->hasAllClaims(['sub', 'iss', 'exp', 'nbf', 'iat', 'jti', 'abc'])); $this->assertFalse($collection->hasAllClaims(['foo', 'bar'])); $this->assertFalse($collection->hasAllClaims([])); $this->assertTrue($collection->hasAllClaims(['sub', 'iss'])); $this->assertTrue($collection->hasAllClaims(['sub', 'iss', 'exp', 'nbf', 'iat', 'jti'])); } /** @test */ public function it_should_get_a_claim_instance_by_name() { $collection = $this->getCollection(); $this->assertInstanceOf(Expiration::class, $collection->getByClaimName('exp')); $this->assertInstanceOf(Subject::class, $collection->getByClaimName('sub')); } } ================================================ FILE: tests/Claims/DatetimeClaimTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Claims; use Carbon\Carbon; use DateInterval; use DateTime; use DateTimeImmutable; use DateTimeInterface; use Mockery; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Test\AbstractTestCase; use Tymon\JWTAuth\Validators\PayloadValidator; class DatetimeClaimTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Validators\PayloadValidator */ protected $validator; /** * @var array */ protected $claimsTimestamp; public function setUp(): void { parent::setUp(); $this->validator = Mockery::mock(PayloadValidator::class); $this->validator->shouldReceive('setRefreshFlow->check'); $this->claimsTimestamp = [ 'sub' => new Subject(1), 'iss' => new Issuer('http://example.com'), 'exp' => new Expiration($this->testNowTimestamp + 3600), 'nbf' => new NotBefore($this->testNowTimestamp), 'iat' => new IssuedAt($this->testNowTimestamp), 'jti' => new JwtId('foo'), ]; } /** @test */ public function it_should_handle_carbon_claims() { $testCarbon = Carbon::createFromTimestampUTC($this->testNowTimestamp); $testCarbonCopy = clone $testCarbon; $this->assertInstanceOf(Carbon::class, $testCarbon); $this->assertInstanceOf(Datetime::class, $testCarbon); $this->assertInstanceOf(DatetimeInterface::class, $testCarbon); $claimsDatetime = [ 'sub' => new Subject(1), 'iss' => new Issuer('http://example.com'), 'exp' => new Expiration($testCarbonCopy->addHour()), 'nbf' => new NotBefore($testCarbon), 'iat' => new IssuedAt($testCarbon), 'jti' => new JwtId('foo'), ]; $payloadTimestamp = new Payload(Collection::make($this->claimsTimestamp), $this->validator); $payloadDatetime = new Payload(Collection::make($claimsDatetime), $this->validator); $this->assertEquals($payloadTimestamp, $payloadDatetime); } /** @test */ public function it_should_handle_datetime_claims() { $testDateTime = DateTime::createFromFormat('U', $this->testNowTimestamp); $testDateTimeCopy = clone $testDateTime; $this->assertInstanceOf(DateTime::class, $testDateTime); $this->assertInstanceOf(DatetimeInterface::class, $testDateTime); $claimsDatetime = [ 'sub' => new Subject(1), 'iss' => new Issuer('http://example.com'), 'exp' => new Expiration($testDateTimeCopy->modify('+3600 seconds')), 'nbf' => new NotBefore($testDateTime), 'iat' => new IssuedAt($testDateTime), 'jti' => new JwtId('foo'), ]; $payloadTimestamp = new Payload(Collection::make($this->claimsTimestamp), $this->validator); $payloadDatetime = new Payload(Collection::make($claimsDatetime), $this->validator); $this->assertEquals($payloadTimestamp, $payloadDatetime); } /** @test */ public function it_should_handle_datetime_immutable_claims() { $testDateTimeImmutable = DateTimeImmutable::createFromFormat('U', (string) $this->testNowTimestamp); $this->assertInstanceOf(DateTimeImmutable::class, $testDateTimeImmutable); $this->assertInstanceOf(DatetimeInterface::class, $testDateTimeImmutable); $claimsDatetime = [ 'sub' => new Subject(1), 'iss' => new Issuer('http://example.com'), 'exp' => new Expiration($testDateTimeImmutable->modify('+3600 seconds')), 'nbf' => new NotBefore($testDateTimeImmutable), 'iat' => new IssuedAt($testDateTimeImmutable), 'jti' => new JwtId('foo'), ]; $payloadTimestamp = new Payload(Collection::make($this->claimsTimestamp), $this->validator); $payloadDatetime = new Payload(Collection::make($claimsDatetime), $this->validator); $this->assertEquals($payloadTimestamp, $payloadDatetime); } /** @test */ public function it_should_handle_datetinterval_claims() { $testDateInterval = new DateInterval('PT1H'); $this->assertInstanceOf(DateInterval::class, $testDateInterval); $claimsDateInterval = [ 'sub' => new Subject(1), 'iss' => new Issuer('http://example.com'), 'exp' => new Expiration($testDateInterval), 'nbf' => new NotBefore($this->testNowTimestamp), 'iat' => new IssuedAt($this->testNowTimestamp), 'jti' => new JwtId('foo'), ]; $payloadTimestamp = new Payload(Collection::make($this->claimsTimestamp), $this->validator); $payloadDateInterval = new Payload(Collection::make($claimsDateInterval), $this->validator); $this->assertEquals($payloadTimestamp, $payloadDateInterval); } } ================================================ FILE: tests/Claims/FactoryTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Claims; use Illuminate\Http\Request; use Tymon\JWTAuth\Claims\Custom; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\Factory; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Test\AbstractTestCase; use Tymon\JWTAuth\Test\Fixtures\Foo; class FactoryTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Claims\Factory */ protected $factory; public function setUp(): void { parent::setUp(); $this->factory = new Factory(Request::create('/foo', 'GET')); } /** @test */ public function it_should_set_the_request() { $factory = $this->factory->setRequest(Request::create('/bar', 'GET')); $this->assertInstanceOf(Factory::class, $factory); } /** @test */ public function it_should_set_the_ttl() { $this->assertInstanceOf(Factory::class, $this->factory->setTTL(30)); $this->assertSame(30, $this->factory->getTTL()); $this->assertInstanceOf(Factory::class, $this->factory->setTTL('60')); $this->assertSame(60, $this->factory->getTTL()); } /** @test */ public function it_should_set_the_leeway() { $this->assertInstanceOf(Factory::class, $this->factory->setLeeway(30)); $this->assertSame(30, $this->factory->getLeeway()); $this->assertInstanceOf(Factory::class, $this->factory->setLeeway('60')); $this->assertSame(60, $this->factory->getLeeway()); } /** @test */ public function it_should_get_a_defined_claim_instance_when_passing_a_name_and_value() { $this->assertInstanceOf(Subject::class, $this->factory->get('sub', 1)); $this->assertInstanceOf(Issuer::class, $this->factory->get('iss', 'http://example.com')); $this->assertInstanceOf(Expiration::class, $this->factory->get('exp', $this->testNowTimestamp + 3600)); $this->assertInstanceOf(NotBefore::class, $this->factory->get('nbf', $this->testNowTimestamp)); $this->assertInstanceOf(IssuedAt::class, $this->factory->get('iat', $this->testNowTimestamp)); $this->assertInstanceOf(JwtId::class, $this->factory->get('jti', 'foo')); } /** @test */ public function it_should_get_a_custom_claim_instance_when_passing_a_non_defined_name_and_value() { $this->assertInstanceOf(Custom::class, $this->factory->get('foo', ['bar'])); } /** @test */ public function it_should_make_a_claim_instance_with_a_value() { $iat = $this->factory->make('iat'); $this->assertSame($iat->getValue(), $this->testNowTimestamp); $this->assertInstanceOf(IssuedAt::class, $iat); $nbf = $this->factory->make('nbf'); $this->assertSame($nbf->getValue(), $this->testNowTimestamp); $this->assertInstanceOf(NotBefore::class, $nbf); $iss = $this->factory->make('iss'); $this->assertSame($iss->getValue(), 'http://localhost/foo'); $this->assertInstanceOf(Issuer::class, $iss); $exp = $this->factory->make('exp'); $this->assertSame($exp->getValue(), $this->testNowTimestamp + 3600); $this->assertInstanceOf(Expiration::class, $exp); $jti = $this->factory->make('jti'); $this->assertInstanceOf(JwtId::class, $jti); } /** @test */ public function it_should_extend_claim_factory_to_add_a_custom_claim() { $this->factory->extend('foo', Foo::class); $this->assertInstanceOf(Foo::class, $this->factory->get('foo', 'bar')); } } ================================================ FILE: tests/Claims/IssuedAtTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Claims; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Test\AbstractTestCase; class IssuedAtTest extends AbstractTestCase { /** @test */ public function it_should_throw_an_exception_when_passing_a_future_timestamp() { $this->expectException(InvalidClaimException::class); $this->expectExceptionMessage('Invalid value provided for claim [iat]'); new IssuedAt($this->testNowTimestamp + 3600); } } ================================================ FILE: tests/Claims/NotBeforeTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Claims; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Test\AbstractTestCase; class NotBeforeTest extends AbstractTestCase { /** @test */ public function it_should_throw_an_exception_when_passing_an_invalid_value() { $this->expectException(InvalidClaimException::class); $this->expectExceptionMessage('Invalid value provided for claim [nbf]'); new NotBefore('foo'); } } ================================================ FILE: tests/FactoryTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Mockery; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Custom; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\Factory as ClaimFactory; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Factory; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Validators\PayloadValidator; class FactoryTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Claims\Factory */ protected $claimFactory; /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Validators\PayloadValidator */ protected $validator; /** * @var \Tymon\JWTAuth\Factory */ protected $factory; public function setUp(): void { parent::setUp(); $this->claimFactory = Mockery::mock(ClaimFactory::class); $this->validator = Mockery::mock(PayloadValidator::class); $this->factory = new Factory($this->claimFactory, $this->validator); } /** @test */ public function it_should_return_a_payload_when_passing_an_array_of_claims() { $expTime = $this->testNowTimestamp + 3600; // these are added from default claims $this->claimFactory->shouldReceive('make')->twice()->with('iss')->andReturn(new Issuer('/foo')); $this->claimFactory->shouldReceive('make')->twice()->with('exp')->andReturn(new Expiration($expTime)); $this->claimFactory->shouldReceive('make')->twice()->with('jti')->andReturn(new JwtId('foo')); $this->claimFactory->shouldReceive('make')->twice()->with('nbf')->andReturn(new NotBefore(123)); $this->claimFactory->shouldReceive('make')->twice()->with('iat')->andReturn(new IssuedAt(123)); // custom claims that override $this->claimFactory->shouldReceive('get')->twice()->with('sub', 1)->andReturn(new Subject(1)); $this->claimFactory->shouldReceive('get')->twice()->with('jti', 'foo')->andReturn(new JwtId('foo')); $this->claimFactory->shouldReceive('get')->twice()->with('nbf', 123)->andReturn(new NotBefore(123)); $this->claimFactory->shouldReceive('get')->twice()->with('iat', 123)->andReturn(new IssuedAt(123)); $this->claimFactory->shouldReceive('getTTL')->andReturn(60); // once $claims = $this->factory->customClaims([ 'sub' => 1, 'jti' => 'foo', 'iat' => 123, 'nbf' => 123, ])->buildClaimsCollection(); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($claims); // twice $payload = $this->factory->claims(['sub' => 1, 'jti' => 'foo', 'iat' => 123, 'nbf' => 123])->make(); $this->assertSame($payload->get('sub'), 1); $this->assertSame($payload->get('iat'), 123); $this->assertSame($payload['exp'], $expTime); $this->assertSame($payload['jti'], 'foo'); $this->assertInstanceOf(Payload::class, $payload); } /** @test */ public function it_should_return_a_payload_when_chaining_claim_methods() { $this->claimFactory->shouldReceive('get')->twice()->with('sub', 1)->andReturn(new Subject(1)); $this->claimFactory->shouldReceive('get')->twice()->with('foo', 'baz')->andReturn(new Custom('foo', 'baz')); $this->claimFactory->shouldReceive('make')->twice()->with('iss')->andReturn(new Issuer('/foo')); $this->claimFactory->shouldReceive('make')->twice()->with('exp')->andReturn(new Expiration($this->testNowTimestamp + 3600)); $this->claimFactory->shouldReceive('make')->twice()->with('iat')->andReturn(new IssuedAt($this->testNowTimestamp)); $this->claimFactory->shouldReceive('make')->twice()->with('jti')->andReturn(new JwtId('foo')); $this->claimFactory->shouldReceive('make')->twice()->with('nbf')->andReturn(new NotBefore($this->testNowTimestamp)); $this->claimFactory->shouldReceive('getTTL')->andReturn(60); // once $claims = $this->factory->sub(1)->foo('baz')->buildClaimsCollection(); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($claims); // twice $payload = $this->factory->sub(1)->foo('baz')->make(); $this->assertSame($payload['sub'], 1); $this->assertSame($payload->get('jti'), 'foo'); $this->assertSame($payload->get('foo'), 'baz'); $this->assertInstanceOf(Payload::class, $payload); } /** @test */ public function it_should_return_a_payload_when_passing_miltidimensional_array_as_custom_claim_to_make_method() { // these are added from default claims $this->claimFactory->shouldReceive('make')->twice()->with('iss')->andReturn(new Issuer('/foo')); $this->claimFactory->shouldReceive('make')->twice()->with('exp')->andReturn(new Expiration($this->testNowTimestamp + 3600)); $this->claimFactory->shouldReceive('make')->twice()->with('jti')->andReturn(new JwtId('foo')); $this->claimFactory->shouldReceive('make')->twice()->with('nbf')->andReturn(new NotBefore(123)); $this->claimFactory->shouldReceive('make')->twice()->with('iat')->andReturn(new IssuedAt(123)); // custom claims that override $this->claimFactory->shouldReceive('get')->twice()->with('sub', 1)->andReturn(new Subject(1)); $this->claimFactory->shouldReceive('get')->twice()->with('foo', ['bar' => [0, 0, 0]])->andReturn(new Custom('foo', ['bar' => [0, 0, 0]])); $this->claimFactory->shouldReceive('getTTL')->andReturn(60); // once $claims = $this->factory->sub(1)->foo(['bar' => [0, 0, 0]])->buildClaimsCollection(); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($claims); // twice $payload = $this->factory->sub(1)->foo(['bar' => [0, 0, 0]])->make(); $this->assertSame($payload->get('sub'), 1); $this->assertSame($payload->get('jti'), 'foo'); $this->assertSame($payload->get('foo'), ['bar' => [0, 0, 0]]); $this->assertSame($payload->get('foo.bar'), [0, 0, 0]); $this->assertInstanceOf(Payload::class, $payload); } /** @test */ public function it_should_exclude_the_exp_claim_when_setting_ttl_to_null() { // these are added from default claims $this->claimFactory->shouldReceive('make')->twice()->with('iss')->andReturn(new Issuer('/foo')); $this->claimFactory->shouldReceive('make')->twice()->with('jti')->andReturn(new JwtId('foo')); $this->claimFactory->shouldReceive('make')->twice()->with('nbf')->andReturn(new NotBefore(123)); $this->claimFactory->shouldReceive('make')->twice()->with('iat')->andReturn(new IssuedAt(123)); // custom claims that override $this->claimFactory->shouldReceive('get')->twice()->with('sub', 1)->andReturn(new Subject(1)); $this->claimFactory->shouldReceive('setTTL')->with(null)->andReturn($this->claimFactory); $this->claimFactory->shouldReceive('getTTL')->andReturn(null); // once $claims = $this->factory->setTTL(null)->sub(1)->buildClaimsCollection(); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($claims); // twice $payload = $this->factory->setTTL(null)->sub(1)->make(); $this->assertNull($payload->get('exp')); $this->assertInstanceOf(Payload::class, $payload); } /** @test */ public function it_should_exclude_claims_from_previous_payloads() { $validator = new PayloadValidator(); $factory = new Factory($this->claimFactory, $validator); $fooClaim = new Custom('foo', 'bar'); $barClaim = new Custom('baz', 'qux'); $this->claimFactory->shouldReceive('getTTL')->andReturn(60); $this->claimFactory->shouldReceive('get')->with('foo', 'bar')->twice()->andReturn($fooClaim); $this->claimFactory->shouldReceive('get')->with('baz', 'qux')->once()->andReturn($barClaim); $validator->setRequiredClaims([]); $payload = $factory->setDefaultClaims([]) ->customClaims([ 'foo' => 'bar', 'baz' => 'qux', ])->make(); $this->assertSame($payload->get('foo'), 'bar'); $this->assertSame($payload->get('baz'), 'qux'); $payload = $factory->setDefaultClaims([])->customClaims(['foo' => 'bar'])->make(true); $this->assertSame($payload->get('foo'), 'bar'); $this->assertFalse($payload->hasKey('baz')); } /** @test */ public function it_should_set_the_default_claims() { $this->factory->setDefaultClaims(['sub', 'iat']); $this->assertSame($this->factory->getDefaultClaims(), ['sub', 'iat']); } /** @test */ public function it_should_get_payload_with_a_predefined_collection_of_claims() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = $this->factory->withClaims($collection); $this->assertInstanceOf(Payload::class, $payload); $this->assertSame($payload->get('sub'), 1); } /** @test */ public function it_should_get_the_validator() { $this->assertInstanceOf(PayloadValidator::class, $this->factory->validator()); } } ================================================ FILE: tests/Fixtures/Foo.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Fixtures; use Tymon\JWTAuth\Claims\Claim; class Foo extends Claim { /** * {@inheritdoc} */ protected $name = 'foo'; } ================================================ FILE: tests/Http/ParserTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Http; use Illuminate\Http\Request; use Illuminate\Routing\Route; use Illuminate\Support\Facades\Crypt; use Mockery; use Tymon\JWTAuth\Contracts\Http\Parser as ParserContract; use Tymon\JWTAuth\Http\Parser\AuthHeaders; use Tymon\JWTAuth\Http\Parser\Cookies; use Tymon\JWTAuth\Http\Parser\InputSource; use Tymon\JWTAuth\Http\Parser\LumenRouteParams; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\Http\Parser\QueryString; use Tymon\JWTAuth\Http\Parser\RouteParams; use Tymon\JWTAuth\Test\AbstractTestCase; class ParserTest extends AbstractTestCase { /** @test */ public function it_should_return_the_token_from_the_authorization_header() { $request = Request::create('foo', 'POST'); $request->headers->set('Authorization', 'Bearer foobar'); $parser = new Parser($request); $parser->setChain([ new QueryString, new InputSource, new AuthHeaders, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_prefixed_authentication_header() { $request = Request::create('foo', 'POST'); $request->headers->set('Authorization', 'Custom foobar'); $parser = new Parser($request); $parser->setChain([ new QueryString, new InputSource, (new AuthHeaders)->setHeaderPrefix('Custom'), new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_custom_authentication_header() { $request = Request::create('foo', 'POST'); $request->headers->set('custom_authorization', 'Bearer foobar'); $parser = new Parser($request); $parser->setChain([ new QueryString, new InputSource, (new AuthHeaders)->setHeaderName('custom_authorization'), new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_alt_authorization_headers() { $request1 = Request::create('foo', 'POST'); $request1->server->set('HTTP_AUTHORIZATION', 'Bearer foobar'); $request2 = Request::create('foo', 'POST'); $request2->server->set('REDIRECT_HTTP_AUTHORIZATION', 'Bearer foobarbaz'); $parser = new Parser($request1, [ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); $parser->setRequest($request2); $this->assertSame($parser->parseToken(), 'foobarbaz'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_ignore_non_bearer_tokens() { $request = Request::create('foo', 'POST'); $request->headers->set('Authorization', 'Basic OnBhc3N3b3Jk'); $parser = new Parser($request); $parser->setChain([ new QueryString, new InputSource, new AuthHeaders, new RouteParams, ]); $this->assertNull($parser->parseToken()); $this->assertFalse($parser->hasToken()); } /** @test */ public function it_should_not_strip_trailing_hyphens_from_the_authorization_header() { $request = Request::create('foo', 'POST'); $request->headers->set('Authorization', 'Bearer foobar--'); $parser = new Parser($request); $parser->setChain([ new QueryString, new InputSource, new AuthHeaders, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar--'); $this->assertTrue($parser->hasToken()); } /** * @test * * @dataProvider whitespaceProvider */ public function it_should_handle_excess_whitespace_from_the_authorization_header($whitespace) { $request = Request::create('foo', 'POST'); $request->headers->set('Authorization', "Bearer{$whitespace}foobar{$whitespace}"); $parser = new Parser($request); $parser->setChain([ new QueryString, new InputSource, new AuthHeaders, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } public function whitespaceProvider() { return [ 'space' => [' '], 'multiple spaces' => [' '], 'tab' => ["\t"], 'multiple tabs' => ["\t\t\t"], 'new line' => ["\n"], 'multiple new lines' => ["\n\n\n"], 'carriage return' => ["\r"], 'carriage returns' => ["\r\r\r"], 'mixture of whitespace' => ["\t \n \r \t \n"], ]; } /** @test */ public function it_should_return_the_token_from_query_string() { $request = Request::create('foo', 'GET', ['token' => 'foobar']); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_custom_query_string() { $request = Request::create('foo', 'GET', ['custom_token_key' => 'foobar']); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, (new QueryString)->setKey('custom_token_key'), new InputSource, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_query_string_not_the_input_source() { $request = Request::create('foo?token=foobar', 'POST', [], [], [], [], json_encode(['token' => 'foobarbaz'])); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_custom_query_string_not_the_custom_input_source() { $request = Request::create('foo?custom_token_key=foobar', 'POST', [], [], [], [], json_encode(['custom_token_key' => 'foobarbaz'])); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, (new QueryString)->setKey('custom_token_key'), (new InputSource)->setKey('custom_token_key'), new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_input_source() { $request = Request::create('foo', 'POST', [], [], [], [], json_encode(['token' => 'foobar'])); $request->headers->set('Content-Type', 'application/json'); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_the_custom_input_source() { $request = Request::create('foo', 'POST', [], [], [], [], json_encode(['custom_token_key' => 'foobar'])); $request->headers->set('Content-Type', 'application/json'); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, (new InputSource)->setKey('custom_token_key'), new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_an_unencrypted_cookie() { $request = Request::create('foo', 'POST', [], ['token' => 'foobar']); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, new Cookies(false), ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_a_crypted_cookie() { Crypt::shouldReceive('encrypt') ->with('foobar') ->once() ->andReturn('cryptedFoobar'); $request = Request::create('foo', 'POST', [], ['token' => Crypt::encrypt('foobar')]); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, new Cookies(true), ]); Crypt::shouldReceive('decrypt') ->with('cryptedFoobar') ->times(2) ->andReturn('foobar'); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_route() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $request->setRouteResolver(function () { return $this->getRouteMock('foobar'); }); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_the_token_from_route_with_a_custom_param() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $request->setRouteResolver(function () { return $this->getRouteMock('foobar', 'custom_route_param'); }); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, (new RouteParams)->setKey('custom_route_param'), ]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_ignore_routeless_requests() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $request->setRouteResolver(function () { // }); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertNull($parser->parseToken()); $this->assertFalse($parser->hasToken()); } /** @test */ public function it_should_ignore_lumen_request_arrays() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $request->setRouteResolver(function () { return [false, ['uses' => 'someController'], ['token' => 'foobar']]; }); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertNull($parser->parseToken()); $this->assertFalse($parser->hasToken()); } /** @test */ public function it_should_accept_lumen_request_arrays_with_special_class() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $request->setRouteResolver(function () { return [false, ['uses' => 'someController'], ['token' => 'foo.bar.baz']]; }); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new LumenRouteParams, ]); $this->assertSame($parser->parseToken(), 'foo.bar.baz'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_return_null_if_no_token_in_request() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $request->setRouteResolver(function () { return $this->getRouteMock(); }); $parser = new Parser($request); $parser->setChain([ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]); $this->assertNull($parser->parseToken()); $this->assertFalse($parser->hasToken()); } /** @test */ public function it_should_retrieve_the_chain() { $chain = [ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]; $parser = new Parser(Mockery::mock(Request::class)); $parser->setChain($chain); $this->assertSame($parser->getChain(), $chain); } /** @test */ public function it_should_retrieve_the_chain_with_alias() { $chain = [ new AuthHeaders, new QueryString, new InputSource, new RouteParams, ]; /* @var \Illuminate\Http\Request $request */ $request = Mockery::mock(Request::class); $parser = new Parser($request); $parser->setChainOrder($chain); $this->assertSame($parser->getChain(), $chain); } /** @test */ public function it_should_set_the_cookie_key() { $cookies = (new Cookies)->setKey('test'); $this->assertInstanceOf(Cookies::class, $cookies); } /** @test */ public function it_should_add_custom_parser() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $customParser = Mockery::mock(ParserContract::class); $customParser->shouldReceive('parse')->with($request)->andReturn('foobar'); $parser = new Parser($request); $parser->addParser($customParser); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } /** @test */ public function it_should_add_multiple_custom_parser() { $request = Request::create('foo', 'GET', ['foo' => 'bar']); $customParser1 = Mockery::mock(ParserContract::class); $customParser1->shouldReceive('parse')->with($request)->andReturn(false); $customParser2 = Mockery::mock(ParserContract::class); $customParser2->shouldReceive('parse')->with($request)->andReturn('foobar'); $parser = new Parser($request); $parser->addParser([$customParser1, $customParser2]); $this->assertSame($parser->parseToken(), 'foobar'); $this->assertTrue($parser->hasToken()); } protected function getRouteMock($expectedParameterValue = null, $expectedParameterName = 'token') { return Mockery::mock(Route::class) ->shouldReceive('parameter') ->with($expectedParameterName) ->andReturn($expectedParameterValue) ->getMock(); } } ================================================ FILE: tests/JWTAuthTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Illuminate\Http\Request; use Mockery; use stdClass; use Tymon\JWTAuth\Contracts\Providers\Auth; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Factory; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\JWTAuth; use Tymon\JWTAuth\Manager; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Test\Stubs\UserStub; use Tymon\JWTAuth\Token; class JWTAuthTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Manager */ protected $manager; /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Contracts\Providers\Auth */ protected $auth; /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Http\Parser\Parser */ protected $parser; /** * @var \Tymon\JWTAuth\JWTAuth */ protected $jwtAuth; public function setUp(): void { $this->manager = Mockery::mock(Manager::class); $this->auth = Mockery::mock(Auth::class); $this->parser = Mockery::mock(Parser::class); $this->jwtAuth = new JWTAuth($this->manager, $this->auth, $this->parser); } /** @test */ public function it_should_return_a_token_when_passing_a_user() { $payloadFactory = Mockery::mock(Factory::class); $payloadFactory->shouldReceive('make')->andReturn(Mockery::mock(Payload::class)); $this->manager ->shouldReceive('getPayloadFactory->customClaims') ->once() ->with(['sub' => 1, 'prv' => sha1('Tymon\JWTAuth\Test\Stubs\UserStub'), 'foo' => 'bar', 'role' => 'admin']) ->andReturn($payloadFactory); $this->manager->shouldReceive('encode->get')->once()->andReturn('foo.bar.baz'); $token = $this->jwtAuth->fromUser(new UserStub); $this->assertSame($token, 'foo.bar.baz'); } /** @test */ public function it_should_pass_provider_check_if_hash_matches() { $payloadFactory = Mockery::mock(Factory::class); $payloadFactory->shouldReceive('make')->andReturn(Mockery::mock(Payload::class)); $payloadFactory->shouldReceive('get') ->with('prv') ->andReturn(sha1('Tymon\JWTAuth\Test\Stubs\UserStub')); $this->manager->shouldReceive('decode')->once()->andReturn($payloadFactory); $this->assertTrue($this->jwtAuth->setToken('foo.bar.baz')->checkSubjectModel('Tymon\JWTAuth\Test\Stubs\UserStub')); } /** @test */ public function it_should_pass_provider_check_if_hash_matches_when_provider_is_null() { $payloadFactory = Mockery::mock(Factory::class); $payloadFactory->shouldReceive('make')->andReturn(Mockery::mock(Payload::class)); $payloadFactory->shouldReceive('get') ->with('prv') ->andReturnNull(); $this->manager->shouldReceive('decode')->once()->andReturn($payloadFactory); $this->assertTrue($this->jwtAuth->setToken('foo.bar.baz')->checkSubjectModel('Tymon\JWTAuth\Test\Stubs\UserStub')); } /** @test */ public function it_should_not_pass_provider_check_if_hash_not_match() { $payloadFactory = Mockery::mock(Factory::class); $payloadFactory->shouldReceive('make')->andReturn(Mockery::mock(Payload::class)); $payloadFactory->shouldReceive('get') ->with('prv') ->andReturn(sha1('Tymon\JWTAuth\Test\Stubs\UserStub1')); $this->manager->shouldReceive('decode')->once()->andReturn($payloadFactory); $this->assertFalse($this->jwtAuth->setToken('foo.bar.baz')->checkSubjectModel('Tymon\JWTAuth\Test\Stubs\UserStub')); } /** @test */ public function it_should_return_a_token_when_passing_valid_credentials_to_attempt_method() { $payloadFactory = Mockery::mock(Factory::class); $payloadFactory->shouldReceive('make')->andReturn(Mockery::mock(Payload::class)); $this->manager ->shouldReceive('getPayloadFactory->customClaims') ->once() ->with(['sub' => 1, 'prv' => sha1('Tymon\JWTAuth\Test\Stubs\UserStub'), 'foo' => 'bar', 'role' => 'admin']) ->andReturn($payloadFactory); $this->manager->shouldReceive('encode->get')->once()->andReturn('foo.bar.baz'); $this->auth->shouldReceive('byCredentials')->once()->andReturn(true); $this->auth->shouldReceive('user')->once()->andReturn(new UserStub); $token = $this->jwtAuth->attempt(['foo' => 'bar']); $this->assertSame($token, 'foo.bar.baz'); } /** @test */ public function it_should_return_false_when_passing_invalid_credentials_to_attempt_method() { $this->manager->shouldReceive('encode->get')->never(); $this->auth->shouldReceive('byCredentials')->once()->andReturn(false); $this->auth->shouldReceive('user')->never(); $token = $this->jwtAuth->attempt(['foo' => 'bar']); $this->assertFalse($token); } /** @test */ public function it_should_throw_an_exception_when_not_providing_a_token() { $this->expectException(JWTException::class); $this->expectExceptionMessage('A token is required'); $this->jwtAuth->toUser(); } /** @test */ public function it_should_return_the_owning_user_from_a_token_containing_an_existing_user() { $payload = Mockery::mock(Payload::class); $payload->shouldReceive('get')->once()->with('sub')->andReturn(1); $this->manager->shouldReceive('decode')->once()->andReturn($payload); $this->auth->shouldReceive('byId')->once()->with(1)->andReturn(true); $this->auth->shouldReceive('user')->once()->andReturn((object) ['id' => 1]); $user = $this->jwtAuth->setToken('foo.bar.baz')->customClaims(['foo' => 'bar'])->authenticate(); $this->assertSame($user->id, 1); } /** @test */ public function it_should_return_false_when_passing_a_token_not_containing_an_existing_user() { $payload = Mockery::mock(Payload::class); $payload->shouldReceive('get')->once()->with('sub')->andReturn(1); $this->manager->shouldReceive('decode')->once()->andReturn($payload); $this->auth->shouldReceive('byId')->once()->with(1)->andReturn(false); $this->auth->shouldReceive('user')->never(); $user = $this->jwtAuth->setToken('foo.bar.baz')->authenticate(); $this->assertFalse($user); } /** @test */ public function it_should_refresh_a_token() { $newToken = Mockery::mock(Token::class); $newToken->shouldReceive('get')->once()->andReturn('baz.bar.foo'); $this->manager->shouldReceive('customClaims->refresh')->once()->andReturn($newToken); $result = $this->jwtAuth->setToken('foo.bar.baz')->refresh(); $this->assertSame($result, 'baz.bar.foo'); } /** @test */ public function it_should_invalidate_a_token() { $token = new Token('foo.bar.baz'); $this->manager->shouldReceive('invalidate')->once()->with($token, false)->andReturn(true); $this->jwtAuth->setToken($token)->invalidate(); } /** @test */ public function it_should_force_invalidate_a_token_forever() { $token = new Token('foo.bar.baz'); $this->manager->shouldReceive('invalidate')->once()->with($token, true)->andReturn(true); $this->jwtAuth->setToken($token)->invalidate(true); } /** @test */ public function it_should_retrieve_the_token_from_the_request() { $this->parser->shouldReceive('parseToken')->andReturn('foo.bar.baz'); $this->assertInstanceOf(Token::class, $this->jwtAuth->parseToken()->getToken()); $this->assertEquals($this->jwtAuth->getToken(), 'foo.bar.baz'); } /** @test */ public function it_should_get_the_authenticated_user() { $manager = $this->jwtAuth->manager(); $this->assertInstanceOf(Manager::class, $manager); } /** @test */ public function it_should_return_false_if_the_token_is_invalid() { $this->parser->shouldReceive('parseToken')->andReturn('foo.bar.baz'); $this->manager->shouldReceive('decode')->once()->andThrow(new TokenInvalidException); $this->assertFalse($this->jwtAuth->parseToken()->check()); } /** @test */ public function it_should_return_true_if_the_token_is_valid() { $payload = Mockery::mock(Payload::class); $this->parser->shouldReceive('parseToken')->andReturn('foo.bar.baz'); $this->manager->shouldReceive('decode')->once()->andReturn($payload); $this->assertTrue($this->jwtAuth->parseToken()->check()); } /** @test */ public function it_should_throw_an_exception_when_token_not_present_in_request() { $this->expectException(JWTException::class); $this->expectExceptionMessage('The token could not be parsed from the request'); $this->parser->shouldReceive('parseToken')->andReturn(false); $this->jwtAuth->parseToken(); } /** @test */ public function it_should_return_false_when_no_token_is_set() { $this->parser->shouldReceive('parseToken')->andReturn(false); $this->assertNull($this->jwtAuth->getToken()); } /** @test */ public function it_should_magically_call_the_manager() { $this->manager->shouldReceive('getBlacklist')->andReturn(new stdClass); $blacklist = $this->jwtAuth->manager()->getBlacklist(); $this->assertInstanceOf(stdClass::class, $blacklist); } /** @test */ public function it_should_set_the_request() { $request = Request::create('/foo', 'GET', ['token' => 'some.random.token']); $this->parser->shouldReceive('setRequest')->once()->with($request); $this->parser->shouldReceive('parseToken')->andReturn('some.random.token'); $token = $this->jwtAuth->setRequest($request)->getToken(); $this->assertEquals('some.random.token', $token); } /** @test */ public function it_should_unset_the_token() { $this->parser->shouldReceive('parseToken')->andThrow(new JWTException); $token = new Token('foo.bar.baz'); $this->jwtAuth->setToken($token); $this->assertSame($this->jwtAuth->getToken(), $token); $this->jwtAuth->unsetToken(); $this->assertNull($this->jwtAuth->getToken()); } /** @test */ public function it_should_get_the_manager_instance() { $manager = $this->jwtAuth->manager(); $this->assertInstanceOf(Manager::class, $manager); } /** @test */ public function it_should_get_the_parser_instance() { $parser = $this->jwtAuth->parser(); $this->assertInstanceOf(Parser::class, $parser); } /** @test */ public function it_should_get_a_claim_value() { $payload = Mockery::mock(Payload::class); $payload->shouldReceive('get')->once()->with('sub')->andReturn(1); $this->manager->shouldReceive('decode')->once()->andReturn($payload); $this->assertSame($this->jwtAuth->setToken('foo.bar.baz')->getClaim('sub'), 1); } } ================================================ FILE: tests/JWTGuardTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Illuminate\Auth\EloquentUserProvider; use Illuminate\Http\Request; use Mockery; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\UserNotDefinedException; use Tymon\JWTAuth\Factory; use Tymon\JWTAuth\JWT; use Tymon\JWTAuth\JWTGuard; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Test\Stubs\LaravelUserStub; class JWTGuardTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\JWT|\Mockery\MockInterface */ protected $jwt; /** * @var \Illuminate\Contracts\Auth\UserProvider|\Mockery\MockInterface */ protected $provider; /** * @var \Tymon\JWTAuth\JWTGuard|\Mockery\MockInterface */ protected $guard; public function setUp(): void { parent::setUp(); $this->jwt = Mockery::mock(JWT::class); $this->provider = Mockery::mock(EloquentUserProvider::class); $this->guard = new JWTGuard($this->jwt, $this->provider, Request::create('/foo', 'GET')); } /** @test */ public function it_should_get_the_request() { $this->assertInstanceOf(Request::class, $this->guard->getRequest()); } /** @test */ public function it_should_get_the_authenticated_user_if_a_valid_token_is_provided() { $payload = Mockery::mock(Payload::class); $payload->shouldReceive('offsetGet')->once()->with('sub')->andReturn(1); $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn('foo.bar.baz'); $this->jwt->shouldReceive('check')->once()->with(true)->andReturn($payload); $this->jwt->shouldReceive('checkSubjectModel') ->once() ->with('\Tymon\JWTAuth\Test\Stubs\LaravelUserStub') ->andReturn(true); $this->provider->shouldReceive('getModel') ->once() ->andReturn('\Tymon\JWTAuth\Test\Stubs\LaravelUserStub'); $this->provider->shouldReceive('retrieveById') ->once() ->with(1) ->andReturn((object) ['id' => 1]); $this->assertSame(1, $this->guard->user()->id); // check that the user is stored on the object next time round $this->assertSame(1, $this->guard->user()->id); $this->assertTrue($this->guard->check()); // also make sure userOrFail does not fail $this->assertSame(1, $this->guard->userOrFail()->id); } /** @test */ public function it_should_get_the_authenticated_user_if_a_valid_token_is_provided_and_not_throw_an_exception() { $payload = Mockery::mock(Payload::class); $payload->shouldReceive('offsetGet')->once()->with('sub')->andReturn(1); $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn('foo.bar.baz'); $this->jwt->shouldReceive('check')->once()->with(true)->andReturn($payload); $this->jwt->shouldReceive('checkSubjectModel') ->once() ->with('\Tymon\JWTAuth\Test\Stubs\LaravelUserStub') ->andReturn(true); $this->provider->shouldReceive('getModel') ->once() ->andReturn('\Tymon\JWTAuth\Test\Stubs\LaravelUserStub'); $this->provider->shouldReceive('retrieveById') ->once() ->with(1) ->andReturn((object) ['id' => 1]); $this->assertSame(1, $this->guard->userOrFail()->id); // check that the user is stored on the object next time round $this->assertSame(1, $this->guard->userOrFail()->id); $this->assertTrue($this->guard->check()); } /** @test */ public function it_should_return_null_if_an_invalid_token_is_provided() { $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->twice()->andReturn('invalid.token.here'); $this->jwt->shouldReceive('check')->twice()->andReturn(false); $this->jwt->shouldReceive('getPayload->get')->never(); $this->provider->shouldReceive('retrieveById')->never(); $this->assertNull($this->guard->user()); // once $this->assertFalse($this->guard->check()); // twice } /** @test */ public function it_should_return_null_if_no_token_is_provided() { $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->andReturn(false); $this->jwt->shouldReceive('check')->never(); $this->jwt->shouldReceive('getPayload->get')->never(); $this->provider->shouldReceive('retrieveById')->never(); $this->assertNull($this->guard->user()); $this->assertFalse($this->guard->check()); } /** @test */ public function it_should_throw_an_exception_if_an_invalid_token_is_provided() { $this->expectException(UserNotDefinedException::class); $this->expectExceptionMessage('An error occurred'); $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->twice()->andReturn('invalid.token.here'); $this->jwt->shouldReceive('check')->twice()->andReturn(false); $this->jwt->shouldReceive('getPayload->get')->never(); $this->provider->shouldReceive('retrieveById')->never(); $this->assertFalse($this->guard->check()); // once $this->guard->userOrFail(); // twice, throws the exception } /** @test */ public function it_should_throw_an_exception_if_no_token_is_provided() { $this->expectException(UserNotDefinedException::class); $this->expectExceptionMessage('An error occurred'); $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->andReturn(false); $this->jwt->shouldReceive('check')->never(); $this->jwt->shouldReceive('getPayload->get')->never(); $this->provider->shouldReceive('retrieveById')->never(); $this->assertFalse($this->guard->check()); $this->guard->userOrFail(); // throws the exception } /** @test */ public function it_should_return_a_token_if_credentials_are_ok_and_user_is_found() { $credentials = ['foo' => 'bar', 'baz' => 'bob']; $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveByCredentials') ->once() ->with($credentials) ->andReturn($user); $this->provider->shouldReceive('validateCredentials') ->once() ->with($user, $credentials) ->andReturn(true); $this->jwt->shouldReceive('fromUser') ->once() ->with($user) ->andReturn('foo.bar.baz'); $this->jwt->shouldReceive('setToken') ->once() ->with('foo.bar.baz') ->andReturnSelf(); $this->jwt->shouldReceive('claims') ->once() ->with(['foo' => 'bar']) ->andReturnSelf(); $token = $this->guard->claims(['foo' => 'bar'])->attempt($credentials); $this->assertSame($this->guard->getLastAttempted(), $user); $this->assertSame($token, 'foo.bar.baz'); } /** @test */ public function it_should_return_true_if_credentials_are_ok_and_user_is_found_when_choosing_not_to_login() { $credentials = ['foo' => 'bar', 'baz' => 'bob']; $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveByCredentials') ->twice() ->with($credentials) ->andReturn($user); $this->provider->shouldReceive('validateCredentials') ->twice() ->with($user, $credentials) ->andReturn(true); $this->assertTrue($this->guard->attempt($credentials, false)); // once $this->assertTrue($this->guard->validate($credentials)); // twice } /** @test */ public function it_should_return_false_if_credentials_are_invalid() { $credentials = ['foo' => 'bar', 'baz' => 'bob']; $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveByCredentials') ->once() ->with($credentials) ->andReturn($user); $this->provider->shouldReceive('validateCredentials') ->once() ->with($user, $credentials) ->andReturn(false); $this->assertFalse($this->guard->attempt($credentials)); } /** @test */ public function it_should_magically_call_the_jwt_instance() { $this->jwt->shouldReceive('factory')->andReturn(Mockery::mock(Factory::class)); $this->assertInstanceOf(Factory::class, $this->guard->factory()); } /** @test */ public function it_should_logout_the_user_by_invalidating_the_token() { $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn(true); $this->jwt->shouldReceive('invalidate')->once()->andReturn(true); $this->jwt->shouldReceive('unsetToken')->once(); $this->guard->logout(); $this->assertNull($this->guard->getUser()); } /** @test */ public function it_should_refresh_the_token() { $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn(true); $this->jwt->shouldReceive('refresh')->once()->andReturn('foo.bar.baz'); $this->assertSame($this->guard->refresh(), 'foo.bar.baz'); } /** @test */ public function it_should_invalidate_the_token() { $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn(true); $this->jwt->shouldReceive('invalidate')->once()->andReturn(true); $this->assertTrue($this->guard->invalidate()); } /** @test */ public function it_should_throw_an_exception_if_there_is_no_token_present_when_required() { $this->expectException(JWTException::class); $this->expectExceptionMessage('Token could not be parsed from the request.'); $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn(false); $this->jwt->shouldReceive('refresh')->never(); $this->guard->refresh(); } /** @test */ public function it_should_generate_a_token_by_id() { $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveById') ->once() ->with(1) ->andReturn($user); $this->jwt->shouldReceive('fromUser') ->once() ->with($user) ->andReturn('foo.bar.baz'); $this->assertSame('foo.bar.baz', $this->guard->tokenById(1)); } /** @test */ public function it_should_not_generate_a_token_by_id() { $this->provider->shouldReceive('retrieveById') ->once() ->with(1) ->andReturn(null); $this->assertNull($this->guard->tokenById(1)); } /** @test */ public function it_should_authenticate_the_user_by_credentials_and_return_true_if_valid() { $credentials = ['foo' => 'bar', 'baz' => 'bob']; $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveByCredentials') ->once() ->with($credentials) ->andReturn($user); $this->provider->shouldReceive('validateCredentials') ->once() ->with($user, $credentials) ->andReturn(true); $this->assertTrue($this->guard->once($credentials)); } /** @test */ public function it_should_attempt_to_authenticate_the_user_by_credentials_and_return_false_if_invalid() { $credentials = ['foo' => 'bar', 'baz' => 'bob']; $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveByCredentials') ->once() ->with($credentials) ->andReturn($user); $this->provider->shouldReceive('validateCredentials') ->once() ->with($user, $credentials) ->andReturn(false); $this->assertFalse($this->guard->once($credentials)); } /** @test */ public function it_should_authenticate_the_user_by_id_and_return_boolean() { $user = new LaravelUserStub; $this->provider->shouldReceive('retrieveById') ->twice() ->with(1) ->andReturn($user); $this->assertTrue($this->guard->onceUsingId(1)); // once $this->assertTrue($this->guard->byId(1)); // twice } /** @test */ public function it_should_not_authenticate_the_user_by_id_and_return_false() { $this->provider->shouldReceive('retrieveById') ->twice() ->with(1) ->andReturn(null); $this->assertFalse($this->guard->onceUsingId(1)); // once $this->assertFalse($this->guard->byId(1)); // twice } /** @test */ public function it_should_create_a_token_from_a_user_object() { $user = new LaravelUserStub; $this->jwt->shouldReceive('fromUser') ->once() ->with($user) ->andReturn('foo.bar.baz'); $this->jwt->shouldReceive('setToken') ->once() ->with('foo.bar.baz') ->andReturnSelf(); $token = $this->guard->login($user); $this->assertSame('foo.bar.baz', $token); } /** @test */ public function it_should_get_the_payload() { $this->jwt->shouldReceive('setRequest')->andReturn($this->jwt); $this->jwt->shouldReceive('getToken')->once()->andReturn('foo.bar.baz'); $this->jwt->shouldReceive('getPayload')->once()->andReturn(Mockery::mock(Payload::class)); $this->assertInstanceOf(Payload::class, $this->guard->payload()); } /** @test */ public function it_should_be_macroable() { $this->guard->macro('foo', function () { return 'bar'; }); $this->assertEquals('bar', $this->guard->foo()); } } ================================================ FILE: tests/ManagerTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Mockery; use Tymon\JWTAuth\Blacklist; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Contracts\Providers\JWT; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenBlacklistedException; use Tymon\JWTAuth\Factory; use Tymon\JWTAuth\Manager; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Token; use Tymon\JWTAuth\Validators\PayloadValidator; class ManagerTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Contracts\Providers\JWT */ protected $jwt; /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Blacklist */ protected $blacklist; /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Factory */ protected $factory; /** * @var \Tymon\JWTAuth\Manager */ protected $manager; /** * @var \Mockery\MockInterface */ protected $validator; public function setUp(): void { parent::setUp(); $this->jwt = Mockery::mock(JWT::class); $this->blacklist = Mockery::mock(Blacklist::class); $this->factory = Mockery::mock(Factory::class); $this->manager = new Manager($this->jwt, $this->blacklist, $this->factory); $this->validator = Mockery::mock(PayloadValidator::class); } /** @test */ public function it_should_encode_a_payload() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $this->jwt->shouldReceive('encode')->with($payload->toArray())->andReturn('foo.bar.baz'); $token = $this->manager->encode($payload); $this->assertEquals($token, 'foo.bar.baz'); } /** @test */ public function it_should_decode_a_token() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $token = new Token('foo.bar.baz'); $this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray()); $this->factory->shouldReceive('setRefreshFlow')->andReturn($this->factory); $this->factory->shouldReceive('customClaims')->andReturn($this->factory); $this->factory->shouldReceive('make')->andReturn($payload); $this->blacklist->shouldReceive('has')->with($payload)->andReturn(false); $payload = $this->manager->decode($token); $this->assertInstanceOf(Payload::class, $payload); $this->assertSame($payload->count(), 6); } /** @test */ public function it_should_throw_exception_when_token_is_blacklisted() { $this->expectException(TokenBlacklistedException::class); $this->expectExceptionMessage('The token has been blacklisted'); $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $token = new Token('foo.bar.baz'); $this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray()); $this->factory->shouldReceive('setRefreshFlow')->andReturn($this->factory); $this->factory->shouldReceive('customClaims')->with($payload->toArray())->andReturn($this->factory); $this->factory->shouldReceive('make')->andReturn($payload); $this->blacklist->shouldReceive('has')->with($payload)->andReturn(true); $this->manager->decode($token); } /** @test */ public function it_should_refresh_a_token() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp - 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $token = new Token('foo.bar.baz'); $this->jwt->shouldReceive('decode')->twice()->with('foo.bar.baz')->andReturn($payload->toArray()); $this->jwt->shouldReceive('encode')->with($payload->toArray())->andReturn('baz.bar.foo'); $this->factory->shouldReceive('setRefreshFlow')->with(true)->andReturn($this->factory); $this->factory->shouldReceive('customClaims')->andReturn($this->factory); $this->factory->shouldReceive('make')->andReturn($payload); $this->blacklist->shouldReceive('has')->with($payload)->andReturn(false); $this->blacklist->shouldReceive('add')->once()->with($payload); $token = $this->manager->refresh($token); // $this->assertArrayHasKey('ref', $payload); $this->assertInstanceOf(Token::class, $token); $this->assertEquals('baz.bar.foo', $token); } /** @test */ public function it_should_invalidate_a_token() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $token = new Token('foo.bar.baz'); $this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray()); $this->factory->shouldReceive('setRefreshFlow')->andReturn($this->factory); $this->factory->shouldReceive('customClaims')->with($payload->toArray())->andReturn($this->factory); $this->factory->shouldReceive('make')->andReturn($payload); $this->blacklist->shouldReceive('has')->with($payload)->andReturn(false); $this->blacklist->shouldReceive('add')->with($payload)->andReturn(true); $this->manager->invalidate($token); } /** @test */ public function it_should_force_invalidate_a_token_forever() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); $payload = new Payload($collection, $this->validator); $token = new Token('foo.bar.baz'); $this->jwt->shouldReceive('decode')->once()->with('foo.bar.baz')->andReturn($payload->toArray()); $this->factory->shouldReceive('setRefreshFlow')->andReturn($this->factory); $this->factory->shouldReceive('customClaims')->with($payload->toArray())->andReturn($this->factory); $this->factory->shouldReceive('make')->andReturn($payload); $this->blacklist->shouldReceive('has')->with($payload)->andReturn(false); $this->blacklist->shouldReceive('addForever')->with($payload)->andReturn(true); $this->manager->invalidate($token, true); } /** @test */ public function it_should_throw_an_exception_when_enable_blacklist_is_set_to_false() { $this->expectException(JWTException::class); $this->expectExceptionMessage('You must have the blacklist enabled to invalidate a token.'); $token = new Token('foo.bar.baz'); $this->manager->setBlacklistEnabled(false)->invalidate($token); } /** @test */ public function it_should_get_the_payload_factory() { $this->assertInstanceOf(Factory::class, $this->manager->getPayloadFactory()); } /** @test */ public function it_should_get_the_jwt_provider() { $this->assertInstanceOf(JWT::class, $this->manager->getJWTProvider()); } /** @test */ public function it_should_get_the_blacklist() { $this->assertInstanceOf(Blacklist::class, $this->manager->getBlacklist()); } } ================================================ FILE: tests/Middleware/AbstractMiddlewareTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Middleware; use Illuminate\Http\Request; use Mockery; use Tymon\JWTAuth\JWTAuth; use Tymon\JWTAuth\Test\AbstractTestCase; abstract class AbstractMiddlewareTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\JWTAuth */ protected $auth; /** * @var \Mockery\MockInterface|\Illuminate\Http\Request */ protected $request; public function setUp(): void { parent::setUp(); $this->auth = Mockery::mock(JWTAuth::class); $this->request = Mockery::mock(Request::class); } } ================================================ FILE: tests/Middleware/AuthenticateAndRenewTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Middleware; use Illuminate\Http\Response; use Mockery; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Http\Middleware\AuthenticateAndRenew; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\Test\Stubs\UserStub; class AuthenticateAndRenewTest extends AbstractMiddlewareTest { /** * @var \Tymon\JWTAuth\Http\Middleware\Authenticate|\Tymon\JWTAuth\Http\Middleware\AuthenticateAndRenew */ protected $middleware; public function setUp(): void { parent::setUp(); $this->middleware = new AuthenticateAndRenew($this->auth); } /** @test */ public function it_should_authenticate_a_user_and_return_a_new_token() { $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andReturn(new UserStub); $this->auth->shouldReceive('refresh')->once()->andReturn('foo.bar.baz'); $response = $this->middleware->handle($this->request, function () { return new Response; }); $this->assertSame($response->headers->get('authorization'), 'Bearer foo.bar.baz'); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_token_not_provided() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(false); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_token_invalid() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andThrow(new TokenInvalidException); $this->middleware->handle($this->request, function () { // }); } } ================================================ FILE: tests/Middleware/AuthenticateTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Middleware; use Mockery; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Http\Middleware\Authenticate; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\Test\Stubs\UserStub; class AuthenticateTest extends AbstractMiddlewareTest { /** * @var \Tymon\JWTAuth\Http\Middleware\Authenticate */ protected $middleware; public function setUp(): void { parent::setUp(); $this->middleware = new Authenticate($this->auth); } /** @test */ public function it_should_authenticate_a_user() { $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andReturn(new UserStub); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_token_not_provided() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(false); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_token_invalid() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andThrow(new TokenInvalidException); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_user_not_found() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andReturn(false); $this->middleware->handle($this->request, function () { // }); } } ================================================ FILE: tests/Middleware/CheckTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Middleware; use Mockery; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Http\Middleware\Check; use Tymon\JWTAuth\Http\Parser\Parser; use Tymon\JWTAuth\Test\Stubs\UserStub; class CheckTest extends AbstractMiddlewareTest { /** * @var \Tymon\JWTAuth\Http\Middleware\Check */ protected $middleware; public function setUp(): void { parent::setUp(); $this->middleware = new Check($this->auth); } /** @test */ public function it_should_authenticate_a_user_if_a_token_is_present() { $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andReturn(new UserStub); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_unset_the_exception_if_a_token_is_present() { $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->once()->andThrow(new TokenInvalidException); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_do_nothing_if_a_token_is_not_present() { $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(false); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->authenticate')->never(); $this->middleware->handle($this->request, function () { // }); } } ================================================ FILE: tests/Middleware/RefreshTokenTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Middleware; use Illuminate\Http\Response; use Mockery; use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Http\Middleware\RefreshToken; use Tymon\JWTAuth\Http\Parser\Parser; class RefreshTokenTest extends AbstractMiddlewareTest { /** * @var \Tymon\JWTAuth\Http\Middleware\RefreshToken */ protected $middleware; public function setUp(): void { parent::setUp(); $this->middleware = new RefreshToken($this->auth); } /** @test */ public function it_should_refresh_a_token() { $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->refresh')->once()->andReturn('foo.bar.baz'); $response = $this->middleware->handle($this->request, function () { return new Response; }); $this->assertSame($response->headers->get('authorization'), 'Bearer foo.bar.baz'); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_token_not_provided() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(false); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->middleware->handle($this->request, function () { // }); } /** @test */ public function it_should_throw_an_unauthorized_exception_if_token_invalid() { $this->expectException(UnauthorizedHttpException::class); $parser = Mockery::mock(Parser::class); $parser->shouldReceive('hasToken')->once()->andReturn(true); $this->auth->shouldReceive('parser')->andReturn($parser); $this->auth->parser()->shouldReceive('setRequest')->once()->with($this->request)->andReturn($this->auth->parser()); $this->auth->shouldReceive('parseToken->refresh')->once()->andThrow(new TokenInvalidException); $this->middleware->handle($this->request, function () { // }); } } ================================================ FILE: tests/PayloadTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use BadMethodCallException; use Mockery; use Tymon\JWTAuth\Claims\Audience; use Tymon\JWTAuth\Claims\Claim; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Exceptions\PayloadException; use Tymon\JWTAuth\Payload; use Tymon\JWTAuth\Validators\PayloadValidator; class PayloadTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Tymon\JWTAuth\Validators\PayloadValidator */ protected $validator; /** * @var \Tymon\JWTAuth\Payload */ protected $payload; public function setUp(): void { parent::setUp(); $this->payload = $this->getTestPayload(); } /** * @param array $extraClaims * @return \Tymon\JWTAuth\Payload */ private function getTestPayload(array $extraClaims = []) { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; if ($extraClaims) { $claims = array_merge($claims, $extraClaims); } $collection = Collection::make($claims); $this->validator = Mockery::mock(PayloadValidator::class); $this->validator->shouldReceive('setRefreshFlow->check')->andReturn($collection); return new Payload($collection, $this->validator); } /** @test */ public function it_should_throw_an_exception_when_trying_to_add_to_the_payload() { $this->expectException(PayloadException::class); $this->expectExceptionMessage('The payload is immutable'); $this->payload['foo'] = 'bar'; } /** @test */ public function it_should_throw_an_exception_when_trying_to_remove_a_key_from_the_payload() { $this->expectException(PayloadException::class); $this->expectExceptionMessage('The payload is immutable'); unset($this->payload['foo']); } /** @test */ public function it_should_cast_the_payload_to_a_string_as_json() { $this->assertSame((string) $this->payload, json_encode($this->payload->get(), JSON_UNESCAPED_SLASHES)); $this->assertJsonStringEqualsJsonString((string) $this->payload, json_encode($this->payload->get())); } /** @test */ public function it_should_allow_array_access_on_the_payload() { $this->assertTrue(isset($this->payload['iat'])); $this->assertSame($this->payload['sub'], 1); $this->assertArrayHasKey('exp', $this->payload); } /** @test */ public function it_should_get_properties_of_payload_via_get_method() { $this->assertIsArray($this->payload->get()); $this->assertSame($this->payload->get('sub'), 1); $this->assertSame( $this->payload->get(function () { return 'jti'; }), 'foo' ); } /** @test */ public function it_should_get_multiple_properties_when_passing_an_array_to_the_get_method() { $values = $this->payload->get(['sub', 'jti']); $sub = $values[0]; $jti = $values[1]; $this->assertIsArray($values); $this->assertSame($sub, 1); $this->assertSame($jti, 'foo'); } /** @test */ public function it_should_determine_whether_the_payload_has_a_claim() { $this->assertTrue($this->payload->has(new Subject(1))); $this->assertFalse($this->payload->has(new Audience(1))); } /** @test */ public function it_should_magically_get_a_property() { $sub = $this->payload->getSubject(); $jti = $this->payload->getJwtId(); $iss = $this->payload->getIssuer(); $this->assertSame($sub, 1); $this->assertSame($jti, 'foo'); $this->assertSame($iss, 'http://example.com'); } /** @test */ public function it_should_invoke_the_instance_as_a_callable() { $payload = $this->payload; $sub = $payload('sub'); $jti = $payload('jti'); $iss = $payload('iss'); $this->assertSame($sub, 1); $this->assertSame($jti, 'foo'); $this->assertSame($iss, 'http://example.com'); $this->assertSame($payload(), $this->payload->toArray()); } /** @test */ public function it_should_throw_an_exception_when_magically_getting_a_property_that_does_not_exist() { $this->expectException(BadMethodCallException::class); $this->expectExceptionMessage('The claim [Foo] does not exist on the payload'); $this->payload->getFoo(); } /** @test */ public function it_should_get_the_claims() { $claims = $this->payload->getClaims(); $this->assertInstanceOf(Expiration::class, $claims['exp']); $this->assertInstanceOf(JwtId::class, $claims['jti']); $this->assertInstanceOf(Subject::class, $claims['sub']); $this->assertContainsOnlyInstancesOf(Claim::class, $claims); } /** @test */ public function it_should_get_the_object_as_json() { $this->assertJsonStringEqualsJsonString(json_encode($this->payload), $this->payload->toJson()); } /** @test */ public function it_should_count_the_claims() { $this->assertSame(6, $this->payload->count()); $this->assertCount(6, $this->payload); } /** @test */ public function it_should_match_values() { $values = $this->payload->toArray(); $values['sub'] = (string) $values['sub']; $this->assertTrue($this->payload->matches($values)); } /** @test */ public function it_should_match_strict_values() { $values = $this->payload->toArray(); $this->assertTrue($this->payload->matchesStrict($values)); $this->assertTrue($this->payload->matches($values, true)); } /** @test */ public function it_should_not_match_empty_values() { $this->assertFalse($this->payload->matches([])); } /** @test */ public function it_should_not_match_values() { $values = $this->payload->toArray(); $values['sub'] = 'dummy_subject'; $this->assertFalse($this->payload->matches($values)); } /** @test */ public function it_should_not_match_strict_values() { $values = $this->payload->toArray(); $values['sub'] = (string) $values['sub']; $this->assertFalse($this->payload->matchesStrict($values)); $this->assertFalse($this->payload->matches($values, true)); } /** @test */ public function it_should_not_match_a_non_existing_claim() { $values = ['foo' => 'bar']; $this->assertFalse($this->payload->matches($values)); } } ================================================ FILE: tests/Providers/Auth/IlluminateTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Providers\Auth; use Illuminate\Contracts\Auth\Guard; use Mockery; use Tymon\JWTAuth\Providers\Auth\Illuminate as Auth; use Tymon\JWTAuth\Test\AbstractTestCase; class IlluminateTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Illuminate\Contracts\Auth\Guard */ protected $authManager; /** * @var \Tymon\JWTAuth\Providers\Auth\Illuminate */ protected $auth; public function setUp(): void { parent::setUp(); $this->authManager = Mockery::mock(Guard::class); $this->auth = new Auth($this->authManager); } /** @test */ public function it_should_return_true_if_credentials_are_valid() { $this->authManager->shouldReceive('once')->once()->with(['email' => 'foo@bar.com', 'password' => 'foobar'])->andReturn(true); $this->assertTrue($this->auth->byCredentials(['email' => 'foo@bar.com', 'password' => 'foobar'])); } /** @test */ public function it_should_return_true_if_user_is_found() { $this->authManager->shouldReceive('onceUsingId')->once()->with(123)->andReturn(true); $this->assertTrue($this->auth->byId(123)); } /** @test */ public function it_should_return_false_if_user_is_not_found() { $this->authManager->shouldReceive('onceUsingId')->once()->with(123)->andReturn(false); $this->assertFalse($this->auth->byId(123)); } /** @test */ public function it_should_return_the_currently_authenticated_user() { $this->authManager->shouldReceive('user')->once()->andReturn((object) ['id' => 1]); $this->assertSame($this->auth->user()->id, 1); } } ================================================ FILE: tests/Providers/JWT/LcobucciTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Providers\JWT; use Illuminate\Support\Str; use Tymon\JWTAuth\Exceptions\JWTException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Providers\JWT\Lcobucci; use Tymon\JWTAuth\Providers\JWT\Provider; use Tymon\JWTAuth\Test\AbstractTestCase; class LcobucciTest extends AbstractTestCase { /** @test */ public function it_can_encode_claims_using_a_symmetric_key() { $payload = [ 'sub' => 1, 'exp' => $exp = $this->testNowTimestamp + 3600, 'iat' => $iat = $this->testNowTimestamp, 'iss' => '/foo', 'custom_claim' => 'foobar', ]; $token = $this->getProvider(Str::random(64), Provider::ALGO_HS256)->encode($payload); [$header, $payload, $signature] = explode('.', $token); $claims = json_decode(base64_decode($payload), true); $headerValues = json_decode(base64_decode($header), true); $this->assertEquals(Provider::ALGO_HS256, $headerValues['alg']); $this->assertIsString($signature); $this->assertEquals('1', $claims['sub']); $this->assertEquals('/foo', $claims['iss']); $this->assertEquals('foobar', $claims['custom_claim']); $this->assertEquals($exp, $claims['exp']); $this->assertEquals($iat, $claims['iat']); } /** @test */ public function it_can_encode_and_decode_a_token_using_a_symmetric_key() { $payload = [ 'sub' => 1, 'exp' => $exp = $this->testNowTimestamp + 3600, 'iat' => $iat = $this->testNowTimestamp, 'iss' => '/foo', 'custom_claim' => 'foobar', ]; $provider = $this->getProvider(Str::random(64), Provider::ALGO_HS256); $token = $provider->encode($payload); $claims = $provider->decode($token); $this->assertEquals('1', $claims['sub']); $this->assertEquals('/foo', $claims['iss']); $this->assertEquals('foobar', $claims['custom_claim']); $this->assertEquals($exp, $claims['exp']); $this->assertEquals($iat, $claims['iat']); } /** @test */ public function it_can_encode_and_decode_a_token_using_an_asymmetric_RS256_key() { $payload = [ 'sub' => 1, 'exp' => $exp = $this->testNowTimestamp + 3600, 'iat' => $iat = $this->testNowTimestamp, 'iss' => '/foo', 'custom_claim' => 'foobar', ]; $provider = $this->getProvider( Str::random(64), Provider::ALGO_RS256, ['private' => $this->getDummyPrivateKey(), 'public' => $this->getDummyPublicKey()] ); $token = $provider->encode($payload); $header = json_decode(base64_decode(head(explode('.', $token))), true); $this->assertEquals(Provider::ALGO_RS256, $header['alg']); $claims = $provider->decode($token); $this->assertEquals('1', $claims['sub']); $this->assertEquals('/foo', $claims['iss']); $this->assertEquals('foobar', $claims['custom_claim']); $this->assertEquals($exp, $claims['exp']); $this->assertEquals($iat, $claims['iat']); } /** @test */ public function it_can_encode_and_decode_a_token_with_nbf_jti_and_aud_claims() { $payload = [ 'sub' => 1, 'exp' => $exp = $this->testNowTimestamp + 3600, 'iat' => $iat = $this->testNowTimestamp, 'nbf' => $nbf = $this->testNowTimestamp, 'jti' => $jti = 'unique-token-id', 'iss' => '/foo', 'aud' => $aud = 'my-audience', 'custom_claim' => 'foobar', ]; $provider = $this->getProvider(Str::random(64), Provider::ALGO_HS256); $token = $provider->encode($payload); $claims = $provider->decode($token); $this->assertEquals('1', $claims['sub']); $this->assertEquals('/foo', $claims['iss']); $this->assertEquals('foobar', $claims['custom_claim']); $this->assertEquals($exp, $claims['exp']); $this->assertEquals($iat, $claims['iat']); $this->assertEquals($nbf, $claims['nbf']); $this->assertEquals($jti, $claims['jti']); $this->assertEquals([$aud], $claims['aud']); } /** @test */ public function it_can_encode_and_decode_a_token_using_an_asymmetric_ES256_key() { $payload = [ 'sub' => 1, 'exp' => $exp = $this->testNowTimestamp + 3600, 'iat' => $iat = $this->testNowTimestamp, 'iss' => '/foo', 'custom_claim' => 'foobar', ]; $provider = $this->getProvider( Str::random(64), Provider::ALGO_ES256, ['private' => $this->getDummyEcPrivateKey(), 'public' => $this->getDummyEcPublicKey()] ); $token = $provider->encode($payload); $header = json_decode(base64_decode(head(explode('.', $token))), true); $this->assertEquals(Provider::ALGO_ES256, $header['alg']); $claims = $provider->decode($token); $this->assertEquals('1', $claims['sub']); $this->assertEquals('/foo', $claims['iss']); $this->assertEquals('foobar', $claims['custom_claim']); $this->assertEquals($exp, $claims['exp']); $this->assertEquals($iat, $claims['iat']); } /** @test */ public function it_should_throw_an_invalid_exception_when_the_payload_could_not_be_encoded() { $this->expectException(JWTException::class); $this->expectExceptionMessage('Could not create token:'); $payload = [ 'sub' => 1, 'exp' => $this->testNowTimestamp + 3600, 'iat' => $this->testNowTimestamp, 'iss' => '/foo', 'custom_claim' => 'foobar', 'invalid_utf8' => "\xB1\x31", // cannot be encoded as JSON ]; $this->getProvider(Str::random(64), Provider::ALGO_HS256)->encode($payload); } /** @test */ public function it_should_throw_a_token_invalid_exception_when_the_token_could_not_be_decoded_due_to_a_bad_signature() { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('Token Signature could not be verified.'); // This has a different secret than the one used to encode the token $this->getProvider(Str::random(64), Provider::ALGO_HS256) ->decode('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjQ5MjYxMDY1LCJpYXQiOjE2NDkyNTc0NjUsImlzcyI6Ii9mb28iLCJjdXN0b21fY2xhaW0iOiJmb29iYXIifQ.jamiInQiin-1RUviliPjZxl0MLEnQnVTbr2sGooeXBY'); } /** @test */ public function it_should_throw_a_token_invalid_exception_when_the_token_could_not_be_decoded_due_to_tampered_token() { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('Token Signature could not be verified.'); // This sub claim for this token has been tampered with so the signature will not match $this->getProvider(Str::random(64), Provider::ALGO_HS256) ->decode('eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNjQ5MjYxMDY1LCJpYXQiOjE2NDkyNTc0NjUsImlzcyI6Ii9mb29iYXIiLCJjdXN0b21fY2xhaW0iOiJmb29iYXIifQ.jamiInQiin-1RUviliPjZxl0MLEnQnVTbr2sGooeXBY'); } /** @test */ public function it_should_throw_a_token_invalid_exception_when_the_token_could_not_be_decoded() { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('Could not decode token:'); $this->getProvider('secret', Provider::ALGO_HS256)->decode('foo.bar.baz'); } /** @test */ public function it_should_throw_an_exception_when_the_algorithm_passed_is_invalid() { $this->expectException(JWTException::class); $this->expectExceptionMessage('The given algorithm could not be found'); $this->getProvider('secret', 'INVALID_ALGO')->decode('foo.bar.baz'); } /** @test */ public function it_should_throw_an_exception_when_no_symmetric_key_is_provided_when_encoding() { $this->expectException(JWTException::class); $this->expectExceptionMessage('Secret is not set.'); $this->getProvider(null, Provider::ALGO_HS256)->encode(['sub' => 1]); } /** @test */ public function it_should_throw_an_exception_when_no_symmetric_key_is_provided_when_decoding() { $this->expectException(JWTException::class); $this->expectExceptionMessage('Secret is not set.'); $this->getProvider(null, Provider::ALGO_HS256)->decode('foo.bar.baz'); } /** @test */ public function it_should_throw_an_exception_when_no_asymmetric_public_key_is_provided() { $this->expectException(JWTException::class); $this->expectExceptionMessage('Public key is not set.'); $this->getProvider( 'does_not_matter', Provider::ALGO_RS256, ['private' => $this->getDummyPrivateKey(), 'public' => null] )->decode('foo.bar.baz'); } /** @test */ public function it_should_throw_an_exception_when_no_asymmetric_private_key_is_provided() { $this->expectException(JWTException::class); $this->expectExceptionMessage('Private key is not set.'); $this->getProvider( 'does_not_matter', Provider::ALGO_RS256, ['private' => null, 'public' => $this->getDummyPublicKey()] )->encode(['sub' => 1]); } /** @test */ public function it_should_return_the_public_key() { $provider = $this->getProvider( 'does_not_matter', Provider::ALGO_RS256, $keys = ['private' => $this->getDummyPrivateKey(), 'public' => $this->getDummyPublicKey()] ); $this->assertSame($keys['public'], $provider->getPublicKey()); } /** @test */ public function it_should_return_the_keys() { $provider = $this->getProvider( 'does_not_matter', Provider::ALGO_RS256, $keys = ['private' => $this->getDummyPrivateKey(), 'public' => $this->getDummyPublicKey()] ); $this->assertSame($keys, $provider->getKeys()); } public function getProvider($secret, $algo, array $keys = []) { return new Lcobucci($secret, $algo, $keys); } public function getDummyPrivateKey() { return file_get_contents(__DIR__.'/../Keys/id_rsa'); } public function getDummyPublicKey() { return file_get_contents(__DIR__.'/../Keys/id_rsa.pub'); } public function getDummyEcPrivateKey() { return file_get_contents(__DIR__.'/../Keys/id_ecdsa'); } public function getDummyEcPublicKey() { return file_get_contents(__DIR__.'/../Keys/id_ecdsa.pub'); } } ================================================ FILE: tests/Providers/JWT/ProviderTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Providers\JWT; use Tymon\JWTAuth\Test\AbstractTestCase; use Tymon\JWTAuth\Test\Stubs\JWTProviderStub; class ProviderTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Test\Stubs\JWTProviderStub */ protected $provider; public function setUp(): void { parent::setUp(); $this->provider = new JWTProviderStub('secret', 'HS256', []); } /** @test */ public function it_should_set_the_algo() { $this->provider->setAlgo('HS512'); $this->assertSame('HS512', $this->provider->getAlgo()); } /** @test */ public function it_should_set_the_secret() { $this->provider->setSecret('foo'); $this->assertSame('foo', $this->provider->getSecret()); } } ================================================ FILE: tests/Providers/Keys/id_ecdsa ================================================ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIPKNCQlkD3SP1JniCj2RXvGlzp8NO9sYyQr05hdFlGKboAoGCCqGSM49 AwEHoUQDQgAEhXUAmmzqpHbtmm4SY5ikZparlUyrmggNhDy8QoyNIlbrJzh6q0PF crVbOchQZ2fGp0r+6o1tDTmz0AN3XWKJXw== -----END EC PRIVATE KEY----- ================================================ FILE: tests/Providers/Keys/id_ecdsa.pub ================================================ -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhXUAmmzqpHbtmm4SY5ikZparlUyr mggNhDy8QoyNIlbrJzh6q0PFcrVbOchQZ2fGp0r+6o1tDTmz0AN3XWKJXw== -----END PUBLIC KEY----- ================================================ FILE: tests/Providers/Keys/id_rsa ================================================ -----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTvwE87MtgREYL TL4aHhQo3ZzogmxxvMUsKnPzyxRs1YrXOSOpwN0npsXarBKKVIUMNLfFODp/vnQn 2Zp06N8XG59WAOKwvC4MfxLDQkA+JXggzHlkbVoTN+dUkdYIFqSKuAPGwiWToRK2 SxEhij3rE2FON8jQZvDxZkiP9a4vxJO3OTPQwKredXFiObsXD/c3RtLFhKctjCyH OIrP0bQEsee/m7JNtG4ry6BPusN6wb+vJo5ieBYPa3c19akNq6q/nYWhplhkkJSu aOrL5xXEFzI5TvcvnXR568GVcxK8YLfFkdxpsXGt5rAbeh0h/U5kILEAqv8P9PGT ZpicKbrnAgMBAAECggEAd3yTQEQHR91/ASVfKPHMQns77eCbPVtekFusbugsMHYY EPdHbqVMpvFvOMRc+f5Tzd15ziq6qBdbCJm8lThLm4iU0z1QrpaiDZ8vgUvDYM5Y CXoZDli+uZWUTp60/n94fmb0ipZIChScsI2PrzOJWTvobvD/uso8MJydWc8zafQm uqYzygOfjFZvU4lSfgzpefhpquy0JUy5TiKRmGUnwLb3TtcsVavjsn4QmNwLYgOF 2OE+R12ex3pAKTiRE6FcnE1xFIo1GKhBa2Otgw3MDO6Gg+kn8Q4alKz6C6RRlgaH R7sYzEfJhsk/GGFTYOzXKQz2lSaStKt9wKCor04RcQKBgQDzPOu5jCTfayUo7xY2 jHtiogHyKLLObt9l3qbwgXnaD6rnxYNvCrA0OMvT+iZXsFZKJkYzJr8ZOxOpPROk 10WdOaefiwUyL5dypueSwlIDwVm+hI4Bs82MajHtzOozh+73wA+aw5rPs84Uix9w VbbwaVR6qP/BV09yJYS5kQ7fmwKBgQDe2xjywX2d2MC+qzRr+LfU+1+gq0jjhBCX WHqRN6IECB0xTnXUf9WL/VCoI1/55BhdbbEja+4btYgcXSPmlXBIRKQ4VtFfVmYB kPXeD8oZ7LyuNdCsbKNe+x1IHXDe6Wfs3L9ulCfXxeIE84wy3fd66mQahyXV9iD9 CkuifMqUpQKBgQCiydHlY1LGJ/o9tA2Ewm5Na6mrvOs2V2Ox1NqbObwoYbX62eiF 53xX5u8bVl5U75JAm+79it/4bd5RtKux9dUETbLOhwcaOFm+hM+VG/IxyzRZ2nMD 1qcpY2U5BpxzknUvYF3RMTop6edxPk7zKpp9ubCtSu+oINvtxAhY/SkcIwKBgGP1 upcImyO2GZ5shLL5eNubdSVILwV+M0LveOqyHYXZbd6z5r5OKKcGFKuWUnJwEU22 6gGNY9wh7M9sJ7JBzX9c6pwqtPcidda2AtJ8GpbOTUOG9/afNBhiYpv6OKqD3w2r ZmJfKg/qvpqh83zNezgy8nvDqwDxyZI2j/5uIx/RAoGBAMWRmxtv6H2cKhibI/aI MTJM4QRjyPNxQqvAQsv+oHUbid06VK3JE+9iQyithjcfNOwnCaoO7I7qAj9QEfJS MZQc/W/4DHJebo2kd11yoXPVTXXOuEwLSKCejBXABBY0MPNuPUmiXeU0O3Tyi37J TUKzrgcd7NvlA41Y4xKcOqEA -----END PRIVATE KEY----- ================================================ FILE: tests/Providers/Keys/id_rsa.pub ================================================ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA078BPOzLYERGC0y+Gh4U KN2c6IJscbzFLCpz88sUbNWK1zkjqcDdJ6bF2qwSilSFDDS3xTg6f750J9madOjf FxufVgDisLwuDH8Sw0JAPiV4IMx5ZG1aEzfnVJHWCBakirgDxsIlk6EStksRIYo9 6xNhTjfI0Gbw8WZIj/WuL8STtzkz0MCq3nVxYjm7Fw/3N0bSxYSnLYwshziKz9G0 BLHnv5uyTbRuK8ugT7rDesG/ryaOYngWD2t3NfWpDauqv52FoaZYZJCUrmjqy+cV xBcyOU73L510eevBlXMSvGC3xZHcabFxreawG3odIf1OZCCxAKr/D/Txk2aYnCm6 5wIDAQAB -----END PUBLIC KEY----- ================================================ FILE: tests/Providers/Storage/IlluminateTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Providers\Storage; use Illuminate\Contracts\Cache\Repository; use Mockery; use Tymon\JWTAuth\Providers\Storage\Illuminate as Storage; use Tymon\JWTAuth\Test\AbstractTestCase; use Tymon\JWTAuth\Test\Stubs\TaggedStorage; class IlluminateTest extends AbstractTestCase { /** * @var \Mockery\MockInterface|\Illuminate\Contracts\Cache\Repository */ protected $cache; /** * @var \Tymon\JWTAuth\Providers\Storage\Illuminate */ protected $storage; public function setUp(): void { parent::setUp(); $this->cache = Mockery::mock(Repository::class); $this->storage = new Storage($this->cache); } /** @test */ public function it_should_add_the_item_to_storage() { $this->cache->shouldReceive('put')->with('foo', 'bar', 10)->once(); $this->storage->add('foo', 'bar', 10); } /** @test */ public function it_should_add_the_item_to_storage_forever() { $this->cache->shouldReceive('forever')->with('foo', 'bar')->once(); $this->storage->forever('foo', 'bar'); } /** @test */ public function it_should_get_an_item_from_storage() { $this->cache->shouldReceive('get')->with('foo')->once()->andReturn(['foo' => 'bar']); $this->assertSame(['foo' => 'bar'], $this->storage->get('foo')); } /** @test */ public function it_should_remove_the_item_from_storage() { $this->cache->shouldReceive('forget')->with('foo')->once()->andReturn(true); $this->assertTrue($this->storage->destroy('foo')); } /** @test */ public function it_should_remove_all_items_from_storage() { $this->cache->shouldReceive('flush')->withNoArgs()->once(); $this->storage->flush(); } // Duplicate tests for tagged storage -------------------- /** * Replace the storage with our one above that overrides the tag flag, and * define expectations for tags() method. * * @return void */ private function emulateTags() { $this->storage = new TaggedStorage($this->cache); $this->cache->shouldReceive('tags')->with('tymon.jwt')->once()->andReturn(Mockery::self()); } /** @test */ public function it_should_add_the_item_to_tagged_storage() { $this->emulateTags(); $this->cache->shouldReceive('put')->with('foo', 'bar', 10)->once(); $this->storage->add('foo', 'bar', 10); } /** @test */ public function it_should_add_the_item_to_tagged_storage_forever() { $this->emulateTags(); $this->cache->shouldReceive('forever')->with('foo', 'bar')->once(); $this->storage->forever('foo', 'bar'); } /** @test */ public function it_should_get_an_item_from_tagged_storage() { $this->emulateTags(); $this->cache->shouldReceive('get')->with('foo')->once()->andReturn(['foo' => 'bar']); $this->assertSame(['foo' => 'bar'], $this->storage->get('foo')); } /** @test */ public function it_should_remove_the_item_from_tagged_storage() { $this->emulateTags(); $this->cache->shouldReceive('forget')->with('foo')->once()->andReturn(true); $this->assertTrue($this->storage->destroy('foo')); } /** @test */ public function it_should_remove_all_tagged_items_from_storage() { $this->emulateTags(); $this->cache->shouldReceive('flush')->withNoArgs()->once(); $this->storage->flush(); } } ================================================ FILE: tests/Stubs/JWTProviderStub.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Stubs; use Tymon\JWTAuth\Providers\JWT\Provider; class JWTProviderStub extends Provider { /** * {@inheritdoc} */ protected function isAsymmetric() { return false; } } ================================================ FILE: tests/Stubs/LaravelUserStub.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Stubs; use Illuminate\Contracts\Auth\Authenticatable; use Tymon\JWTAuth\Contracts\JWTSubject; class LaravelUserStub extends UserStub implements Authenticatable, JWTSubject { public function getAuthIdentifierName() { // } public function getAuthIdentifier() { // } public function getAuthPassword() { // } public function getRememberToken() { // } public function setRememberToken($value) { // } public function getRememberTokenName() { // } public function getAuthPasswordName() { return 'password'; } } ================================================ FILE: tests/Stubs/TaggedStorage.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Stubs; use Tymon\JWTAuth\Providers\Storage\Illuminate as Storage; class TaggedStorage extends Storage { // It's extremely challenging to test the actual functionality of the provider's // cache() function, because it relies on calling method_exists on methods that // aren't defined in the interface. Getting those conditionals to behave as expected // would be a lot of finicky work compared to verifying their functionality by hand. // So instead we'll just set this value manually... protected $supportsTags = true; } ================================================ FILE: tests/Stubs/UserStub.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Stubs; use Tymon\JWTAuth\Contracts\JWTSubject; class UserStub implements JWTSubject { public function getJWTIdentifier() { return 1; } public function getJWTCustomClaims() { return [ 'foo' => 'bar', 'role' => 'admin', ]; } } ================================================ FILE: tests/TokenTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test; use Tymon\JWTAuth\Token; class TokenTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Token */ protected $token; public function setUp(): void { parent::setUp(); $this->token = new Token('foo.bar.baz'); } /** @test */ public function it_should_return_the_token_when_casting_to_a_string() { $this->assertEquals((string) $this->token, $this->token); } /** @test */ public function it_should_return_the_token_when_calling_get_method() { $this->assertIsString($this->token->get()); } } ================================================ FILE: tests/Validators/PayloadValidatorTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Validators; use Tymon\JWTAuth\Claims\Collection; use Tymon\JWTAuth\Claims\Expiration; use Tymon\JWTAuth\Claims\IssuedAt; use Tymon\JWTAuth\Claims\Issuer; use Tymon\JWTAuth\Claims\JwtId; use Tymon\JWTAuth\Claims\NotBefore; use Tymon\JWTAuth\Claims\Subject; use Tymon\JWTAuth\Exceptions\InvalidClaimException; use Tymon\JWTAuth\Exceptions\TokenExpiredException; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Test\AbstractTestCase; use Tymon\JWTAuth\Validators\PayloadValidator; class PayloadValidatorTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Validators\PayloadValidator */ protected $validator; public function setUp(): void { parent::setUp(); $this->validator = new PayloadValidator; } /** @test */ public function it_should_return_true_when_providing_a_valid_payload() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 3600), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->assertTrue($this->validator->isValid($collection)); } /** @test */ public function it_should_throw_an_exception_when_providing_an_expired_payload() { $this->expectException(TokenExpiredException::class); $this->expectExceptionMessage('Token has expired'); $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp - 1440), new NotBefore($this->testNowTimestamp - 3660), new IssuedAt($this->testNowTimestamp - 3660), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->check($collection); } /** @test */ public function it_should_throw_an_exception_when_providing_an_invalid_nbf_claim() { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('Not Before (nbf) timestamp cannot be in the future'); $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 1440), new NotBefore($this->testNowTimestamp + 3660), new IssuedAt($this->testNowTimestamp - 3660), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->check($collection); } /** @test */ public function it_should_throw_an_exception_when_providing_an_invalid_iat_claim() { $this->expectException(InvalidClaimException::class); $this->expectExceptionMessage('Invalid value provided for claim [iat]'); $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp + 1440), new NotBefore($this->testNowTimestamp - 3660), new IssuedAt($this->testNowTimestamp + 3660), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->check($collection); } /** @test */ public function it_should_throw_an_exception_when_providing_an_invalid_payload() { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('JWT payload does not contain the required claims'); $claims = [ new Subject(1), new Issuer('http://example.com'), ]; $collection = Collection::make($claims); $this->validator->check($collection); } /** @test */ public function it_should_throw_an_exception_when_providing_an_invalid_expiry() { $this->expectException(InvalidClaimException::class); $this->expectExceptionMessage('Invalid value provided for claim [exp]'); $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration('foo'), new NotBefore($this->testNowTimestamp - 3660), new IssuedAt($this->testNowTimestamp + 3660), new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->check($collection); } /** @test */ public function it_should_set_the_required_claims() { $claims = [ new Subject(1), new Issuer('http://example.com'), ]; $collection = Collection::make($claims); $this->assertTrue($this->validator->setRequiredClaims(['iss', 'sub'])->isValid($collection)); } /** @test */ public function it_should_check_the_token_in_the_refresh_context() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp - 1000), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp - 2600), // this is LESS than the refresh ttl at 1 hour new JwtId('foo'), ]; $collection = Collection::make($claims); $this->assertTrue( $this->validator->setRefreshFlow()->setRefreshTTL(60)->isValid($collection) ); } /** @test */ public function it_should_return_true_if_the_refresh_ttl_is_null() { $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp - 1000), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp - 2600), // this is LESS than the refresh ttl at 1 hour new JwtId('foo'), ]; $collection = Collection::make($claims); $this->assertTrue( $this->validator->setRefreshFlow()->setRefreshTTL(null)->isValid($collection) ); } /** @test */ public function it_should_throw_an_exception_if_the_token_cannot_be_refreshed() { $this->expectException(TokenExpiredException::class); $this->expectExceptionMessage('Token has expired and can no longer be refreshed'); $claims = [ new Subject(1), new Issuer('http://example.com'), new Expiration($this->testNowTimestamp), new NotBefore($this->testNowTimestamp), new IssuedAt($this->testNowTimestamp - 5000), // this is MORE than the refresh ttl at 1 hour, so is invalid new JwtId('foo'), ]; $collection = Collection::make($claims); $this->validator->setRefreshFlow()->setRefreshTTL(60)->check($collection); } } ================================================ FILE: tests/Validators/TokenValidatorTest.php ================================================ * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Tymon\JWTAuth\Test\Validators; use Tymon\JWTAuth\Exceptions\TokenInvalidException; use Tymon\JWTAuth\Test\AbstractTestCase; use Tymon\JWTAuth\Validators\TokenValidator; class TokenValidatorTest extends AbstractTestCase { /** * @var \Tymon\JWTAuth\Validators\TokenValidator */ protected $validator; public function setUp(): void { parent::setUp(); $this->validator = new TokenValidator; } /** @test */ public function it_should_return_true_when_providing_a_well_formed_token() { $this->assertTrue($this->validator->isValid('one.two.three')); } /** * @test * * @dataProvider \Tymon\JWTAuth\Test\Validators\TokenValidatorTest::dataProviderMalformedTokens * * @param string $token */ public function it_should_return_false_when_providing_a_malformed_token($token) { $this->assertFalse($this->validator->isValid($token)); } /** * @test * * @dataProvider \Tymon\JWTAuth\Test\Validators\TokenValidatorTest::dataProviderMalformedTokens */ public function it_should_throw_an_exception_when_providing_a_malformed_token($token) { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('Malformed token'); $this->validator->check($token); } /** * @test * * @dataProvider \Tymon\JWTAuth\Test\Validators\TokenValidatorTest::dataProviderTokensWithWrongSegmentsNumber */ public function it_should_return_false_when_providing_a_token_with_wrong_segments_number($token) { $this->assertFalse($this->validator->isValid($token)); } /** * @test * * @dataProvider \Tymon\JWTAuth\Test\Validators\TokenValidatorTest::dataProviderTokensWithWrongSegmentsNumber */ public function it_should_throw_an_exception_when_providing_a_malformed_token_with_wrong_segments_number($token) { $this->expectException(TokenInvalidException::class); $this->expectExceptionMessage('Wrong number of segments'); $this->validator->check($token); } public function dataProviderMalformedTokens() { return [ ['one.two.'], ['.two.'], ['.two.three'], ['one..three'], ['..'], [' . . '], [' one . two . three '], ]; } public function dataProviderTokensWithWrongSegmentsNumber() { return [ ['one.two'], ['one.two.three.four'], ['one.two.three.four.five'], ]; } }