Repository: auth0/laravel-auth0
Branch: main
Commit: ddfaa0dd16a2
Files: 303
Total size: 662.8 KB
Directory structure:
gitextract_de5m7bm0/
├── .editorconfig
├── .gitattributes
├── .github/
│ ├── CODEOWNERS
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.yml
│ │ ├── config.yml
│ │ └── feature_request.yml
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── SECURITY.md
│ ├── SUPPORT.md
│ ├── actions/
│ │ ├── get-prerelease/
│ │ │ └── action.yml
│ │ ├── get-version/
│ │ │ └── action.yml
│ │ ├── publish-package/
│ │ │ └── action.yml
│ │ ├── release-create/
│ │ │ └── action.yml
│ │ ├── rl-scanner/
│ │ │ └── action.yml
│ │ ├── setup/
│ │ │ └── action.yml
│ │ ├── tag-create/
│ │ │ └── action.yml
│ │ └── tag-exists/
│ │ └── action.yml
│ ├── dependabot.yml
│ └── workflows/
│ ├── claude-code-review.yml
│ ├── matrix.json
│ ├── release.yml
│ ├── rl-scanner.yml
│ ├── snyk.yml
│ └── tests.yml
├── .gitignore
├── .php-cs-fixer.dist.php
├── .phpcs.xml.dist
├── .semgrepignore
├── .shiprc
├── .version
├── CHANGELOG.ARCHIVE.md
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── UPGRADE.md
├── codecov.yml
├── composer.json
├── config/
│ └── auth0.php
├── deprecated/
│ ├── Cache/
│ │ ├── LaravelCacheItem.php
│ │ └── LaravelCachePool.php
│ ├── Contract/
│ │ ├── Auth/
│ │ │ ├── Guard.php
│ │ │ └── User/
│ │ │ ├── Provider.php
│ │ │ └── Repository.php
│ │ ├── Auth0.php
│ │ ├── Configuration.php
│ │ ├── Entities/
│ │ │ └── Credential.php
│ │ ├── Event/
│ │ │ ├── Auth0Event.php
│ │ │ ├── Configuration/
│ │ │ │ ├── Building.php
│ │ │ │ └── Built.php
│ │ │ ├── Middleware/
│ │ │ │ ├── StatefulRequest.php
│ │ │ │ └── StatelessRequest.php
│ │ │ ├── Stateful/
│ │ │ │ ├── AuthenticationFailed.php
│ │ │ │ ├── AuthenticationSucceeded.php
│ │ │ │ ├── LoginAttempting.php
│ │ │ │ ├── TokenExpired.php
│ │ │ │ ├── TokenRefreshFailed.php
│ │ │ │ └── TokenRefreshSucceeded.php
│ │ │ └── Stateless/
│ │ │ ├── TokenVerificationAttempting.php
│ │ │ ├── TokenVerificationFailed.php
│ │ │ └── TokenVerificationSucceeded.php
│ │ ├── Exception/
│ │ │ ├── AuthenticationException.php
│ │ │ ├── GuardException.php
│ │ │ ├── SessionException.php
│ │ │ └── Stateful/
│ │ │ └── CallbackException.php
│ │ ├── Http/
│ │ │ ├── Controller/
│ │ │ │ └── Stateful/
│ │ │ │ ├── Callback.php
│ │ │ │ ├── Login.php
│ │ │ │ └── Logout.php
│ │ │ └── Middleware/
│ │ │ ├── Stateful/
│ │ │ │ ├── Authenticate.php
│ │ │ │ └── AuthenticateOptional.php
│ │ │ └── Stateless/
│ │ │ ├── Authorize.php
│ │ │ └── AuthorizeOptional.php
│ │ ├── Model/
│ │ │ ├── Stateful/
│ │ │ │ └── User.php
│ │ │ ├── Stateless/
│ │ │ │ └── User.php
│ │ │ └── User.php
│ │ └── ServiceProvider.php
│ ├── Event/
│ │ ├── Configuration/
│ │ │ ├── Building.php
│ │ │ └── Built.php
│ │ ├── Middleware/
│ │ │ ├── StatefulRequest.php
│ │ │ └── StatelessRequest.php
│ │ ├── Stateful/
│ │ │ ├── AuthenticationFailed.php
│ │ │ ├── AuthenticationSucceeded.php
│ │ │ ├── LoginAttempting.php
│ │ │ ├── TokenExpired.php
│ │ │ ├── TokenRefreshFailed.php
│ │ │ └── TokenRefreshSucceeded.php
│ │ └── Stateless/
│ │ ├── TokenVerificationAttempting.php
│ │ ├── TokenVerificationFailed.php
│ │ └── TokenVerificationSucceeded.php
│ ├── Exception/
│ │ ├── AuthenticationException.php
│ │ ├── GuardException.php
│ │ ├── SessionException.php
│ │ └── Stateful/
│ │ └── CallbackException.php
│ ├── Http/
│ │ ├── Controller/
│ │ │ └── Stateful/
│ │ │ ├── Callback.php
│ │ │ ├── Login.php
│ │ │ └── Logout.php
│ │ └── Middleware/
│ │ ├── Guard.php
│ │ ├── Stateful/
│ │ │ ├── Authenticate.php
│ │ │ └── AuthenticateOptional.php
│ │ └── Stateless/
│ │ ├── Authorize.php
│ │ └── AuthorizeOptional.php
│ ├── Model/
│ │ ├── Imposter.php
│ │ ├── Stateful/
│ │ │ └── User.php
│ │ ├── Stateless/
│ │ │ └── User.php
│ │ └── User.php
│ ├── README.md
│ └── Store/
│ └── LaravelSession.php
├── docs/
│ ├── BackchannelLogout.md
│ ├── Configuration.md
│ ├── Cookies.md
│ ├── Deployment.md
│ ├── Eloquent.md
│ ├── Events.md
│ ├── Installation.md
│ ├── Management.md
│ ├── Octane.md
│ ├── Sessions.md
│ ├── Support.md
│ ├── Telescope.md
│ └── Users.md
├── opslevel.yml
├── phpstan.neon.dist
├── phpunit.xml.dist
├── psalm.xml.dist
├── rector.php
├── src/
│ ├── Auth/
│ │ └── Guard.php
│ ├── Auth0.php
│ ├── Bridges/
│ │ ├── BridgeAbstract.php
│ │ ├── BridgeContract.php
│ │ ├── CacheBridge.php
│ │ ├── CacheBridgeAbstract.php
│ │ ├── CacheBridgeContract.php
│ │ ├── CacheItemBridge.php
│ │ ├── CacheItemBridgeAbstract.php
│ │ ├── CacheItemBridgeContract.php
│ │ ├── SessionBridge.php
│ │ ├── SessionBridgeAbstract.php
│ │ └── SessionBridgeContract.php
│ ├── Configuration.php
│ ├── ConfigurationContract.php
│ ├── Controllers/
│ │ ├── CallbackController.php
│ │ ├── CallbackControllerAbstract.php
│ │ ├── CallbackControllerContract.php
│ │ ├── ControllerAbstract.php
│ │ ├── ControllerContract.php
│ │ ├── LoginController.php
│ │ ├── LoginControllerAbstract.php
│ │ ├── LoginControllerContract.php
│ │ ├── LogoutController.php
│ │ ├── LogoutControllerAbstract.php
│ │ └── LogoutControllerContract.php
│ ├── Entities/
│ │ ├── CredentialEntity.php
│ │ ├── CredentialEntityAbstract.php
│ │ ├── CredentialEntityContract.php
│ │ ├── EntityAbstract.php
│ │ ├── EntityContract.php
│ │ ├── InstanceEntity.php
│ │ ├── InstanceEntityAbstract.php
│ │ ├── InstanceEntityContract.php
│ │ └── InstanceEntityTrait.php
│ ├── Events/
│ │ ├── Auth0EventContract.php
│ │ ├── AuthenticationFailed.php
│ │ ├── AuthenticationFailedAbstract.php
│ │ ├── AuthenticationFailedContract.php
│ │ ├── AuthenticationSucceeded.php
│ │ ├── AuthenticationSucceededAbstract.php
│ │ ├── AuthenticationSucceededContract.php
│ │ ├── Configuration/
│ │ │ ├── BuildingConfigurationEvent.php
│ │ │ ├── BuildingConfigurationEventAbstract.php
│ │ │ ├── BuildingConfigurationEventContract.php
│ │ │ ├── BuiltConfigurationEvent.php
│ │ │ ├── BuiltConfigurationEventAbstract.php
│ │ │ └── BuiltConfigurationEventContract.php
│ │ ├── EventAbstract.php
│ │ ├── EventContract.php
│ │ ├── LoginAttempting.php
│ │ ├── LoginAttemptingAbstract.php
│ │ ├── LoginAttemptingContract.php
│ │ ├── Middleware/
│ │ │ ├── StatefulMiddlewareRequest.php
│ │ │ ├── StatefulMiddlewareRequestAbstract.php
│ │ │ ├── StatefulMiddlewareRequestContract.php
│ │ │ ├── StatelessMiddlewareRequest.php
│ │ │ ├── StatelessMiddlewareRequestAbstract.php
│ │ │ └── StatelessMiddlewareRequestContract.php
│ │ ├── TokenExpired.php
│ │ ├── TokenExpiredAbstract.php
│ │ ├── TokenExpiredContract.php
│ │ ├── TokenRefreshFailed.php
│ │ ├── TokenRefreshFailedAbstract.php
│ │ ├── TokenRefreshFailedContract.php
│ │ ├── TokenRefreshSucceeded.php
│ │ ├── TokenRefreshSucceededAbstract.php
│ │ ├── TokenRefreshSucceededContract.php
│ │ ├── TokenVerificationAttempting.php
│ │ ├── TokenVerificationAttemptingAbstract.php
│ │ ├── TokenVerificationAttemptingContract.php
│ │ ├── TokenVerificationFailed.php
│ │ ├── TokenVerificationFailedAbstract.php
│ │ ├── TokenVerificationFailedContract.php
│ │ ├── TokenVerificationSucceeded.php
│ │ ├── TokenVerificationSucceededAbstract.php
│ │ └── TokenVerificationSucceededContract.php
│ ├── Events.php
│ ├── EventsContract.php
│ ├── Exceptions/
│ │ ├── AuthenticationException.php
│ │ ├── AuthenticationExceptionAbstract.php
│ │ ├── AuthenticationExceptionContract.php
│ │ ├── ControllerException.php
│ │ ├── ControllerExceptionAbstract.php
│ │ ├── ControllerExceptionContract.php
│ │ ├── Controllers/
│ │ │ ├── CallbackControllerException.php
│ │ │ ├── CallbackControllerExceptionAbstract.php
│ │ │ └── CallbackControllerExceptionContract.php
│ │ ├── ExceptionAbstract.php
│ │ ├── ExceptionContract.php
│ │ ├── GuardException.php
│ │ ├── GuardExceptionAbstract.php
│ │ ├── GuardExceptionContract.php
│ │ ├── SessionException.php
│ │ ├── SessionExceptionAbstract.php
│ │ └── SessionExceptionContract.php
│ ├── Facade/
│ │ └── Auth0.php
│ ├── Guards/
│ │ ├── AuthenticationGuard.php
│ │ ├── AuthenticationGuardContract.php
│ │ ├── AuthorizationGuard.php
│ │ ├── AuthorizationGuardContract.php
│ │ ├── GuardAbstract.php
│ │ └── GuardContract.php
│ ├── Middleware/
│ │ ├── AuthenticateMiddleware.php
│ │ ├── AuthenticateMiddlewareAbstract.php
│ │ ├── AuthenticateMiddlewareContract.php
│ │ ├── AuthenticateOptionalMiddleware.php
│ │ ├── AuthenticateOptionalMiddlewareAbstract.php
│ │ ├── AuthenticateOptionalMiddlewareContract.php
│ │ ├── AuthenticatorMiddleware.php
│ │ ├── AuthenticatorMiddlewareContract.php
│ │ ├── AuthorizeMiddleware.php
│ │ ├── AuthorizeMiddlewareAbstract.php
│ │ ├── AuthorizeMiddlewareContract.php
│ │ ├── AuthorizeOptionalMiddleware.php
│ │ ├── AuthorizeOptionalMiddlewareAbstract.php
│ │ ├── AuthorizeOptionalMiddlewareContract.php
│ │ ├── AuthorizerMiddleware.php
│ │ ├── AuthorizerMiddlewareContract.php
│ │ ├── GuardMiddleware.php
│ │ ├── GuardMiddlewareAbstract.php
│ │ ├── GuardMiddlewareContract.php
│ │ ├── MiddlewareAbstract.php
│ │ └── MiddlewareContract.php
│ ├── Service.php
│ ├── ServiceAbstract.php
│ ├── ServiceContract.php
│ ├── ServiceProvider.php
│ ├── ServiceProviderAbstract.php
│ ├── ServiceProviderContract.php
│ ├── Traits/
│ │ ├── ActingAsAuth0User.php
│ │ └── Impersonate.php
│ ├── UserProvider.php
│ ├── UserProviderAbstract.php
│ ├── UserProviderContract.php
│ ├── UserRepository.php
│ ├── UserRepositoryAbstract.php
│ ├── UserRepositoryContract.php
│ └── Users/
│ ├── ImposterUser.php
│ ├── ImposterUserContract.php
│ ├── StatefulUser.php
│ ├── StatefulUserContract.php
│ ├── StatelessUser.php
│ ├── StatelessUserContract.php
│ ├── UserAbstract.php
│ ├── UserContract.php
│ └── UserTrait.php
└── tests/
├── Pest.php
├── TestCase.php
└── Unit/
├── Auth/
│ ├── GuardStatefulTest.php
│ ├── GuardStatelessTest.php
│ └── GuardTest.php
├── Bridges/
│ ├── CacheBridgeTest.php
│ ├── CacheItemBridgeTest.php
│ └── SessionBridgeTest.php
├── ConfigurationTest.php
├── Controllers/
│ ├── CallbackControllerTest.php
│ ├── LoginControllerTest.php
│ └── LogoutControllerTest.php
├── Entities/
│ ├── CredentialEntityTest.php
│ └── InstanceEntityTest.php
├── Guards/
│ ├── AuthenticationGuardTest.php
│ └── AuthorizationGuardTest.php
├── Middleware/
│ ├── AuthenticateMiddlewareTest.php
│ ├── AuthenticateOptionalMiddlewareTest.php
│ ├── AuthenticatorMiddlewareTest.php
│ ├── AuthorizeMiddlewareTest.php
│ ├── AuthorizeOptionalMiddlewareTest.php
│ ├── AuthorizerMiddlewareTest.php
│ └── GuardMiddlewareTest.php
├── ServiceProviderTest.php
├── ServiceTest.php
├── Traits/
│ ├── ActingAsAuth0UserTest.php
│ └── ImpersonateTest.php
├── UserProviderTest.php
├── UserRepositoryTest.php
└── Users/
└── UserTest.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,yaml}]
indent_size = 2
[docker-compose.yml]
indent_size = 4
================================================
FILE: .gitattributes
================================================
.editorconfig export-ignore
.gitattributes export-ignore
.github/ export-ignore
.gitignore export-ignore
.php-cs-fixer.dist.php export-ignore
.semgrepignore export-ignore
.shiprc export-ignore
CHANGELOG.ARCHIVE.md export-ignore
CHANGELOG.md export-ignore
docs/ export-ignore
EXAMPLES.md export-ignore
examples/ export-ignore
opslevel.yml export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
psalm.xml.dist export-ignore
rector.php export-ignore
tests/ export-ignore
UPGRADE.md export-ignore
vendor/ export-ignore
================================================
FILE: .github/CODEOWNERS
================================================
* @auth0/project-dx-sdks-engineer-codeowner
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Code of Conduct
Before making any contributions to this repo, please review Auth0's [Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). By contributing, you agree to uphold this code.
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contribution Guide
- [Getting Involved](#getting-involved)
- [Support Questions](#support-questions)
- [Code Contributions](#code-contributions)
- [Security Vulnerabilities](#security-vulnerabilities)
- [Coding Style](#coding-style)
- [PHPDoc](#phpdoc)
- [Code of Conduct](#code-of-conduct)
## Getting Involved
To encourage active collaboration, Auth0 strongly encourages pull requests, not just bug reports. Pull requests will only be reviewed when marked as "ready for review" (not in the "draft" state) and all tests for new features are passing. Lingering, non-active pull requests left in the "draft" state will eventually be closed.
If you file a bug report, your issue should contain a title and a clear description of the issue. You should also include as much relevant information as possible and a code sample that demonstrates the issue. The goal of a bug report is to make it easy for yourself - and others - to replicate the bug and develop a fix.
Remember, bug reports are created in the hope that others with the same problem will be able to collaborate with you on solving it. Do not expect that the bug report will automatically see any activity or that others will jump to fix it. Creating a bug report serves to help you and others start on the path of fixing the problem. If you want to chip in, you can help out by fixing any bugs listed in our issue trackers.
## Support Questions
Auth0's GitHub issue trackers are not intended to provide integration support. Instead, please refer your questions to the [Auth0 Community](https://community.auth0.com).
## Code Contributions
You may propose new features or improvements to existing SDK behavior by creating a feature request within the repository's issue tracker. If you are willing to implement at least some of the code that would be needed to complete the feature, please fork the repository and submit a pull request.
All development should be done in individual forks using dedicated branches, and submitted against the `main` default branch.
Pull request titles must follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) rules so our changelogs can be automatically generated. Commits messages are irrelevant as they will be squashed into the Pull request's title during a merge.
The following types are allowed:
- _feat:_ A new feature
- _perf:_ A code change that improves performance
- _refactor:_ A code change that neither fixes a bug nor adds a feature
- _build:_ Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
- _ci:_ Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
- _style:_ Changes that do not affect the meaning of the code (white space, formatting, missing semi-colons, etc)
- _fix:_ A bug fix
- _security:_ A change that improves security
- _docs:_ Documentation only changes
- _test:_ Adding missing tests or correcting existing tests
## Security Vulnerabilities
If you discover a security vulnerability within this SDK, please review Auth0's [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. All security vulnerabilities will be promptly addressed.
## Unit Testing and 100% Minimum Coverage
We use [PEST](https://pestphp.com/) for testing. You can run `composer pest` to run the test suite. You can also run `composer pest:coverage` to generate a code coverage report.
We require 100% code coverage for all new features. If you are adding a new feature, please add tests to cover all of the new code. If you are fixing a bug, please add a test that reproduces the bug and then shows that it has been fixed.
Pull requests that do not meet the minimum coverage requirements will not be merged.
## Static Analysis
We use [PHPStan](https://phpstan.org) and [Psalm](https://psalm.dev/) for static analysis. You can use `composer phpstan` and `composer psalm` to run them.
## Coding Style
We use [PHP CS Fixer](https://github.com/PHP-CS-Fixer/PHP-CS-Fixer) to ensure that code styling is consistent. You can run `composer phpcs` to check for any code style issues. `composer phpcs:fix` will attempt to automatically fix the issues, but be cautious as it may not always get it right.
We also use [Rector](https://github.com/rectorphp/rector) to catch edge cases where more optimal refactoring can be made. You can run `composer rector` to check for any recommendations, and `composer rector:fix` to accept the suggestions.
It's important to note that our GitHub CI will also run these checks for pull requests, but you should run these locally first to avoid any surprises when you push your code. If you disagree with one of these recommendations, please bring it up in the pull request so we can discuss it. We may decide to adjust the styling rules if we feel it's warranted, but we prefer to avoid it if possible.
### PHPDoc
All public methods and classes should be documented with PHPDoc blocks.
Below is an example of a valid documentation block. Note that the @param attribute is followed by two spaces, the argument type, two more spaces, and finally the variable name:
```php
/**
* Register a binding with the container.
*
* @param string|array $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*
* @throws \Exception
*/
public function bind($abstract, $concrete = null, $shared = false)
{
//
}
```
## Code of Conduct
Before making any contributions to this repo, please review Auth0's [Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md). By contributing, you agree to uphold this code.
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.yml
================================================
name: Report a Bug
description: Encountering unexpected problems or unintended behavior? Let us know!
body:
- type: markdown
attributes:
value: |
**Please do not report security vulnerabilities here**. The [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues.
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: This can be reproduced using [the quickstart sample application](https://github.com/auth0-samples/laravel).
required: true
- label: I have looked at [the README](https://github.com/auth0/laravel-auth0/#readme) and have not found a solution.
required: true
- label: I have looked at [the `docs` directory](https://github.com/auth0/laravel-auth0/blob/main/docs) and have not found a solution.
required: true
- label: I have searched [previous issues](https://github.com/auth0/laravel-auth0/issues) and have not found a solution.
required: true
- label: I have searched [the Auth0 Community](https://community.auth0.com/tag/laravel) and have not found a solution.
required: true
- label: I agree to uphold [the Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
required: true
- type: dropdown
id: laravel
attributes:
label: Laravel Version
description: What version of Laravel are you using? (`composer show | grep laravel/framework`)
options:
- 10
- 9
- Other (specify below)
validations:
required: true
- type: dropdown
id: sdk
attributes:
label: SDK Version
description: What version of our SDK are you using? (`composer show | grep auth0/login`)
options:
- 7.13
- 7.12
- 7.11
- 7.10
- 7.9
- 7.8
- 7.7
- 7.6
- 7.5
- 7.4
- 7.3
- 7.2
- 7.1
- 7.0
- Other (specify below)
validations:
required: true
- type: dropdown
id: php
attributes:
label: PHP Version
description: What version of PHP are you running? (`php -v`)
options:
- PHP 8.3
- PHP 8.2
- Other (specify below)
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Description
description: Provide a description of the issue, including what you expected to happen.
validations:
required: true
- type: textarea
id: bug-reproduction
attributes:
label: How can we reproduce this issue?
description: Detail the steps taken to reproduce this error. If possible, please provide a GitHub repository to demonstrate the issue.
validations:
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Report Auth0-PHP Issues
url: https://github.com/auth0/auth0-PHP/
about: For issues relating to the Auth0-PHP SDK, please report them in that repository.
- name: Community Support
url: https://community.auth0.com/tag/laravel
about: Please ask general usage questions here.
- name: Responsible Disclosure Program
url: https://auth0.com/whitehat
about: Please do not report security vulnerabilities on the public GitHub issue tracker. The Responsible Disclosure Program details the procedure for disclosing security issues.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.yml
================================================
name: Suggest a Feature
description: Help us improve the SDK by suggest new features and improvements.
body:
- type: markdown
attributes:
value: Thanks for taking the time to help us improve this SDK!
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I agree to uphold [the Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
required: true
- type: textarea
id: feature-description
attributes:
label: Description
description: Please provide a summary of the change you'd like considered, including any relevant context.
validations:
required: true
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### Changes
### References
### Testing
### Contributor Checklist
- [ ] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [ ] I have read the [Auth0 code of conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)
================================================
FILE: .github/SECURITY.md
================================================
# Security Policy
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
## Supported Versions
Please see [our support policy](https://github.com/auth0/laravel-auth0#requirements) for information on supported versions for security releases.
## Reporting a Vulnerability
If you discover a security vulnerability within this SDK, please review Auth0's [Responsible Disclosure Program](https://auth0.com/responsible-disclosure-policy) details the procedure for disclosing security issues. All security vulnerabilities will be promptly addressed.
================================================
FILE: .github/SUPPORT.md
================================================
# Support Questions
Auth0's GitHub issue trackers are not intended to provide integration support. Instead, please refer your questions to the [Auth0 Community](https://community.auth0.com).
================================================
FILE: .github/actions/get-prerelease/action.yml
================================================
name: Return a boolean indicating if the version contains prerelease identifiers
#
# Returns a simple true/false boolean indicating whether the version indicates it's a prerelease or not.
#
# TODO: Remove once the common repo is public.
#
inputs:
version:
required: true
outputs:
prerelease:
value: ${{ steps.get_prerelease.outputs.PRERELEASE }}
runs:
using: composite
steps:
- id: get_prerelease
shell: bash
run: |
if [[ "${VERSION}" == *"beta"* || "${VERSION}" == *"alpha"* ]]; then
echo "PRERELEASE=true" >> $GITHUB_OUTPUT
else
echo "PRERELEASE=false" >> $GITHUB_OUTPUT
fi
env:
VERSION: ${{ inputs.version }}
================================================
FILE: .github/actions/get-version/action.yml
================================================
name: Return the version extracted from the branch name
#
# Returns the version from a branch name of a pull request. It expects the branch name to be in the format release/vX.Y.Z, release/X.Y.Z, release/vX.Y.Z-beta.N. etc.
#
# TODO: Remove once the common repo is public.
#
outputs:
version:
value: ${{ steps.get_version.outputs.VERSION }}
runs:
using: composite
steps:
- id: get_version
shell: bash
run: |
VERSION=$(head -1 .version)
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
================================================
FILE: .github/actions/publish-package/action.yml
================================================
name: Publish release to package manager
inputs:
token:
required: true
files:
required: false
name:
required: true
body:
required: true
tag:
required: true
commit:
required: true
draft:
default: false
required: false
prerelease:
default: false
required: false
runs:
using: composite
steps:
# Nothing to do for PHP.
- run: exit 0
shell: bash
================================================
FILE: .github/actions/release-create/action.yml
================================================
name: Create a GitHub release
#
# Creates a GitHub release with the given version.
#
# TODO: Remove once the common repo is public.
#
inputs:
token:
required: true
files:
required: false
name:
required: true
body:
required: true
tag:
required: true
commit:
required: true
draft:
default: false
required: false
prerelease:
default: false
required: false
fail_on_unmatched_files:
default: true
required: false
runs:
using: composite
steps:
- uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844
with:
body: ${{ inputs.body }}
name: ${{ inputs.name }}
tag_name: ${{ inputs.tag }}
target_commitish: ${{ inputs.commit }}
draft: ${{ inputs.draft }}
prerelease: ${{ inputs.prerelease }}
fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }}
files: ${{ inputs.files }}
env:
GITHUB_TOKEN: ${{ inputs.token }}
================================================
FILE: .github/actions/rl-scanner/action.yml
================================================
name: 'Reversing Labs Scanner'
description: 'Runs the Reversing Labs scanner on a specified artifact.'
inputs:
artifact-path:
description: 'Path to the artifact to be scanned.'
required: true
version:
description: 'Version of the artifact.'
required: true
runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install Python dependencies
shell: bash
run: |
pip install boto3 requests
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{ env.PRODSEC_TOOLS_ARN }}
aws-region: us-east-1
mask-aws-account-id: true
- name: Install RL Wrapper
shell: bash
run: |
pip install rl-wrapper>=1.0.0 --index-url "https://${{ env.PRODSEC_TOOLS_USER }}:${{ env.PRODSEC_TOOLS_TOKEN }}@a0us.jfrog.io/artifactory/api/pypi/python-local/simple"
- name: Run RL Scanner
shell: bash
env:
RLSECURE_LICENSE: ${{ env.RLSECURE_LICENSE }}
RLSECURE_SITE_KEY: ${{ env.RLSECURE_SITE_KEY }}
SIGNAL_HANDLER_TOKEN: ${{ env.SIGNAL_HANDLER_TOKEN }}
PYTHONUNBUFFERED: 1
run: |
if [ ! -f "${{ inputs.artifact-path }}" ]; then
echo "Artifact not found: ${{ inputs.artifact-path }}"
exit 1
fi
rl-wrapper \
--artifact "${{ inputs.artifact-path }}" \
--name "${{ github.event.repository.name }}" \
--version "${{ inputs.version }}" \
--repository "${{ github.repository }}" \
--commit "${{ github.sha }}" \
--build-env "github_actions" \
--suppress_output
# Check the outcome of the scanner
if [ $? -ne 0 ]; then
echo "RL Scanner failed."
echo "scan-status=failed" >> $GITHUB_ENV
exit 1
else
echo "RL Scanner passed."
echo "scan-status=success" >> $GITHUB_ENV
fi
outputs:
scan-status:
description: 'The outcome of the scan process.'
value: ${{ env.scan-status }}
================================================
FILE: .github/actions/setup/action.yml
================================================
name: Prepare PHP
description: Prepare the PHP environment
inputs:
php:
description: The PHP version to use
required: true
coverage:
description: The coverage extension to use
required: false
default: 'none'
extensions:
description: The PHP extensions to use
required: false
default: 'none, mbstring, curl, simplexml, dom, xmlwriter, xml, tokenizer, fileinfo, pdo'
runner:
description: The runner OS
required: false
default: 'ubuntu-latest'
runs:
using: composite
steps:
- name: Setup PHP
uses: shivammathur/setup-php@4bd44f22a98a19e0950cbad5f31095157cc9621b # pin@2.25.4
with:
php-version: ${{ inputs.php }}
extensions: ${{ inputs.extensions }}
coverage: ${{ inputs.coverage }}
env:
runner: ${{ inputs.runner }}
- name: Get Composer cache directory
id: composer-cache
shell: bash
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: ${{ runner.os }}-composer-${{ inputs.php }}-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-${{ inputs.php }}-
- name: Install dependencies
shell: bash
run: composer install --prefer-dist
================================================
FILE: .github/actions/tag-create/action.yml
================================================
name: Create a repository tag
#
# Creates a tag with the given version.
#
# TODO: Remove once the common repo is public.
#
inputs:
token:
required: true
tag:
required: true
runs:
using: composite
steps:
- shell: bash
run: |
git config user.name "${AUTHOR_USERNAME}"
git config user.email "${AUTHOR_EMAIL}"
env:
AUTHOR_USERNAME: ${{ github.event.pull_request.user.login }}
AUTHOR_EMAIL: ${{ github.event.pull_request.user.email }}
- shell: bash
run: |
git tag -a ${TAG_NAME} -m "Version ${TAG_NAME}"
git push --follow-tags
env:
TAG_NAME: ${{ inputs.tag }}
GITHUB_TOKEN: ${{ inputs.token }}
================================================
FILE: .github/actions/tag-exists/action.yml
================================================
name: Return a boolean indicating if a tag already exists for the repository
#
# Returns a simple true/false boolean indicating whether the tag exists or not.
#
# TODO: Remove once the common repo is public.
#
inputs:
token:
required: true
tag:
required: true
outputs:
exists:
description: 'Whether the tag exists or not'
value: ${{ steps.tag-exists.outputs.EXISTS }}
runs:
using: composite
steps:
- id: tag-exists
shell: bash
run: |
GET_API_URL="https://api.github.com/repos/${GITHUB_REPOSITORY}/git/ref/tags/${TAG_NAME}"
http_status_code=$(curl -LI $GET_API_URL -o /dev/null -w '%{http_code}\n' -s -H "Authorization: token ${GITHUB_TOKEN}")
if [ "$http_status_code" -ne "404" ] ; then
echo "EXISTS=true" >> $GITHUB_OUTPUT
else
echo "EXISTS=false" >> $GITHUB_OUTPUT
fi
env:
TAG_NAME: ${{ inputs.tag }}
GITHUB_TOKEN: ${{ inputs.token }}
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
================================================
FILE: .github/workflows/claude-code-review.yml
================================================
name: Claude Code PR Review
on:
issue_comment:
types: [ created ]
pull_request_review_comment:
types: [ created ]
pull_request_review:
types: [ submitted ]
jobs:
claude-review:
permissions:
contents: write
issues: write
pull-requests: write
id-token: write
uses: auth0/auth0-ai-pr-analyzer-gh-action/.github/workflows/claude-code-review.yml@main
================================================
FILE: .github/workflows/matrix.json
================================================
{
"include": [
{ "php": "8.2" },
{ "php": "8.3" },
{ "php": "8.4" }
]
}
================================================
FILE: .github/workflows/release.yml
================================================
name: Create GitHub Release
on:
pull_request:
types:
- closed
permissions:
contents: write
id-token: write # This is required for requesting the JWT
### TODO: Replace instances of './.github/actions/' w/ `auth0/dx-sdk-actions/` and append `@latest` after the common `dx-sdk-actions` repo is made public.
### TODO: Also remove `get-prerelease`, `get-version`, `release-create`, `tag-create` and `tag-exists` actions from this repo's .github/actions folder once the repo is public.
jobs:
rl-scanner:
uses: ./.github/workflows/rl-scanner.yml
with:
php-version: 8.2
artifact-name: 'laravel-auth0.zip'
secrets:
RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }}
RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }}
SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }}
PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }}
PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }}
PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }}
release:
if: github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/')
needs: rl-scanner
runs-on: ubuntu-latest
steps:
# Checkout the code
- uses: actions/checkout@v4
with:
fetch-depth: 0
# Get the version from the branch name
- id: get_version
uses: ./.github/actions/get-version
# Get the prerelease flag from the branch name
- id: get_prerelease
uses: ./.github/actions/get-prerelease
with:
version: ${{ steps.get_version.outputs.version }}
# Check if the tag already exists
- id: tag_exists
uses: ./.github/actions/tag-exists
with:
tag: ${{ steps.get_version.outputs.version }}
token: ${{ secrets.GITHUB_TOKEN }}
# If the tag already exists, exit with an error
- if: steps.tag_exists.outputs.exists == 'true'
run: exit 1
# Publish the release to our package manager
- uses: ./.github/actions/publish-package
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.get_version.outputs.version }}
body: ${{ github.event.pull_request.body }}
tag: ${{ steps.get_version.outputs.version }}
commit: ${{ github.sha }}
prerelease: ${{ steps.get_prerelease.outputs.prerelease }}
# Create a tag for the release
- uses: ./.github/actions/tag-create
with:
tag: ${{ steps.get_version.outputs.version }}
token: ${{ secrets.GITHUB_TOKEN }}
# Create a release for the tag
- uses: ./.github/actions/release-create
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.get_version.outputs.version }}
body: ${{ github.event.pull_request.body }}
tag: ${{ steps.get_version.outputs.version }}
commit: ${{ github.sha }}
prerelease: ${{ steps.get_prerelease.outputs.prerelease }}
================================================
FILE: .github/workflows/rl-scanner.yml
================================================
name: RL-Secure Workflow
on:
workflow_call:
inputs:
php-version:
required: true
type: string
artifact-name:
required: true
type: string
secrets:
RLSECURE_LICENSE:
required: true
RLSECURE_SITE_KEY:
required: true
SIGNAL_HANDLER_TOKEN:
required: true
PRODSEC_TOOLS_USER:
required: true
PRODSEC_TOOLS_TOKEN:
required: true
PRODSEC_TOOLS_ARN:
required: true
jobs:
rl-scanner:
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/'))
runs-on: ubuntu-latest
outputs:
scan-status: ${{ steps.rl-scan-conclusion.outcome }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha || github.ref }}
- name: Setup PHP
uses: shivammathur/setup-php@4bd44f22a98a19e0950cbad5f31095157cc9621b # pin@2.25.4
with:
php-version: ${{ inputs.php-version }}
- name: Build Laravel
shell: bash
run: |
zip -r ${{ inputs.artifact-name }} ./*
- name: Get Artifact Version
id: get_version
uses: ./.github/actions/get-version
- name: Run RL Scanner
id: rl-scan-conclusion
uses: ./.github/actions/rl-scanner
with:
artifact-path: "$(pwd)/${{ inputs.artifact-name }}"
version: "${{ steps.get_version.outputs.version }}"
env:
RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }}
RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }}
SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }}
PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }}
PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }}
PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }}
- name: Output scan result
run: echo "scan-status=${{ steps.rl-scan-conclusion.outcome }}" >> $GITHUB_ENV
================================================
FILE: .github/workflows/snyk.yml
================================================
name: Snyk
on:
merge_group:
pull_request:
types:
- opened
- synchronize
push:
branches:
- main
schedule:
- cron: "30 0 1,15 * *"
permissions:
contents: read
env:
DX_SDKS_SNYK_ORGANIZATION: 8303ea71-ac72-4ae6-9cd0-ae2f3eda82b7
DX_SDKS_SNYK_PROJECT: auth0/laravel-auth0
DX_SDKS_SNYK_TAGS: Refactoring-target:DX,Refactoring-origin:auth0-sdks
DX_SDKS_SNYK_REMOTE_REPO_URL: https://github.com/auth0/laravel-auth0
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
configure:
name: Configure
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha || github.ref }}
- id: set-matrix
run: echo "matrix=$(jq -c . < ./.github/workflows/matrix.json)" >> $GITHUB_OUTPUT
check:
needs: [configure]
name: Check for Vulnerabilities
runs-on: ubuntu-latest
steps:
- if: github.actor == 'dependabot[bot]' || github.event_name == 'merge_group'
run: exit 0
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.merge_commit_sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ fromJson(needs.configure.outputs.matrix).include[0].php }}
- run: npm install snyk -g
- if: github.ref == 'refs/heads/main'
run: snyk monitor --file=composer.lock --org=$SNYK_ORGANIZATION --project-name=$SNYK_PROJECT --project-tags=$SNYK_TAGS --remote-repo-url=$SNYK_REMOTE_REPO --target-reference="$(git branch --show-current)"
env:
SNYK_TOKEN: ${{ secrets.DX_SDKS_SNYK_TOKEN }}
SNYK_ORGANIZATION: ${{ env.DX_SDKS_SNYK_ORGANIZATION }}
SNYK_PROJECT: ${{ env.DX_SDKS_SNYK_PROJECT }}
SNYK_TAGS: ${{ env.DX_SDKS_SNYK_TAGS }}
SNYK_REMOTE_REPO: ${{ env.DX_SDKS_SNYK_REMOTE_REPO_URL }}
continue-on-error: true
- run: snyk test --file=composer.lock --org=$SNYK_ORGANIZATION --project-name=$SNYK_PROJECT --remote-repo-url=$SNYK_REMOTE_REPO
env:
SNYK_TOKEN: ${{ secrets.DX_SDKS_SNYK_TOKEN }}
SNYK_ORGANIZATION: ${{ env.DX_SDKS_SNYK_ORGANIZATION }}
SNYK_PROJECT: ${{ env.DX_SDKS_SNYK_PROJECT }}
SNYK_TAGS: ${{ env.DX_SDKS_SNYK_TAGS }}
SNYK_REMOTE_REPO: ${{ env.DX_SDKS_SNYK_REMOTE_REPO_URL }}
================================================
FILE: .github/workflows/tests.yml
================================================
name: Build and Test
on:
merge_group:
workflow_dispatch:
pull_request:
types:
- opened
- synchronize
push:
branches:
- main
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
configure:
name: Configure
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- id: set-matrix
run: echo "matrix=$(jq -c . < ./.github/workflows/matrix.json)" >> $GITHUB_OUTPUT
prepare:
name: Prepare Dependencies
needs: [configure]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
composer-normalize:
name: Composer Normalize
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
- run: composer normalize --dry-run --diff
composer-validate:
name: Composer Validate
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
- run: composer validate
pest:
name: PEST
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
coverage: pcov
- if: matrix.php == '8.2'
run: composer pest:coverage
- if: matrix.php == '8.2'
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # pin@3.1.4
with:
directory: ./coverage/
flags: unittestsvalidate
phpstan:
name: PHPStan
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
- run: composer phpstan
psalm:
name: Psalm
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
- run: composer psalm
rector:
name: Rector
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
- run: composer rector
php-cs-fixer:
name: PHP CS Fixer
needs: [configure, prepare]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.configure.outputs.matrix) }}
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
- uses: ./.github/actions/setup
with:
php: ${{ matrix.php }}
- run: composer phpcs
================================================
FILE: .gitignore
================================================
build/
vendor/
coverage/
tmp/
.idea/
.env
.DS_Store
composer.lock
composer.phar
.phpunit.result.cache
composer.local.json
composer.local.json_
.php-cs-fixer.cache
composer.local.old
.vscode
pest.log
NOTES.md
# AI tools
.claude
================================================
FILE: .php-cs-fixer.dist.php
================================================
setRiskyAllowed(true)
->setRules([
'array_indentation' => true,
'array_push' => true,
'array_syntax' => ['syntax' => 'short'],
'assign_null_coalescing_to_coalesce_equal' => true,
'backtick_to_shell_exec' => true,
'binary_operator_spaces' => true,
'blank_line_after_namespace' => true,
'blank_line_after_opening_tag' => true,
'blank_line_before_statement' => true,
'blank_line_between_import_groups' => true,
'braces' => true,
'cast_spaces' => true,
'class_attributes_separation' => ['elements' => ['const' => 'one', 'method' => 'one', 'property' => 'one', 'trait_import' => 'one', 'case' => 'one']],
'class_definition' => ['multi_line_extends_each_single_line' => true, 'single_line' => true, 'single_item_single_line' => true, 'space_before_parenthesis' => false, 'inline_constructor_arguments' => false],
'class_reference_name_casing' => true,
'clean_namespace' => true,
'combine_consecutive_issets' => true,
'combine_consecutive_unsets' => true,
'combine_nested_dirname' => true,
'comment_to_phpdoc' => ['ignored_tags' => ['codeCoverageIgnoreStart', 'codeCoverageIgnoreEnd', 'phpstan-ignore-next-line']],
'compact_nullable_typehint' => true,
'concat_space' => ['spacing' => 'one'],
'constant_case' => ['case' => 'lower'],
'curly_braces_position' => ['control_structures_opening_brace' => 'same_line', 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_functions_opening_brace' => 'same_line', 'classes_opening_brace' => 'next_line_unless_newline_at_signature_end', 'anonymous_classes_opening_brace' => 'same_line', 'allow_single_line_empty_anonymous_classes' => true, 'allow_single_line_anonymous_functions' => true],
'date_time_create_from_format_call' => true,
'date_time_immutable' => true,
'declare_equal_normalize' => ['space' => 'none'],
'declare_parentheses' => true,
'declare_strict_types' => true,
'dir_constant' => true,
'doctrine_annotation_array_assignment' => true,
'doctrine_annotation_braces' => true,
'doctrine_annotation_indentation' => true,
'doctrine_annotation_spaces' => true,
'echo_tag_syntax' => ['format' => 'long'],
'elseif' => true,
'empty_loop_body' => true,
'empty_loop_condition' => true,
'encoding' => true,
'ereg_to_preg' => true,
'error_suppression' => true,
'escape_implicit_backslashes' => true,
'explicit_indirect_variable' => true,
'explicit_string_variable' => true,
'final_class' => true,
'final_internal_class' => true,
'final_public_method_for_abstract_class' => true,
'fopen_flag_order' => true,
'fopen_flags' => true,
'full_opening_tag' => true,
'fully_qualified_strict_types' => true,
'function_declaration' => true,
'function_to_constant' => true,
'function_typehint_space' => true,
'general_phpdoc_annotation_remove' => true,
'general_phpdoc_tag_rename' => true,
'get_class_to_class_keyword' => true,
'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true],
'group_import' => true,
'heredoc_indentation' => true,
'heredoc_to_nowdoc' => true,
'implode_call' => true,
'include' => true,
'increment_style' => ['style' => 'pre'],
'indentation_type' => true,
'integer_literal_case' => true,
'is_null' => true,
'lambda_not_used_import' => true,
'line_ending' => true,
'linebreak_after_opening_tag' => true,
'list_syntax' => ['syntax' => 'short'],
'logical_operators' => true,
'lowercase_cast' => true,
'lowercase_keywords' => true,
'lowercase_static_reference' => true,
'magic_constant_casing' => true,
'magic_method_casing' => true,
'mb_str_functions' => false,
'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline', 'after_heredoc' => true],
'method_chaining_indentation' => true,
'modernize_strpos' => true,
'modernize_types_casting' => true,
'multiline_comment_opening_closing' => true,
'multiline_whitespace_before_semicolons' => true,
'native_function_casing' => true,
'native_function_invocation' => true,
'native_function_type_declaration_casing' => true,
'new_with_braces' => true,
'no_alias_functions' => true,
'no_alias_language_construct_call' => true,
'no_alternative_syntax' => true,
'no_binary_string' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_break_comment' => true,
'no_closing_tag' => true,
'no_empty_comment' => true,
'no_empty_phpdoc' => true,
'no_empty_statement' => true,
'no_extra_blank_lines' => true,
'no_homoglyph_names' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_mixed_echo_print' => true,
'no_multiline_whitespace_around_double_arrow' => true,
'no_multiple_statements_per_line' => true,
'no_php4_constructor' => true,
'no_short_bool_cast' => true,
'no_singleline_whitespace_before_semicolons' => true,
'no_space_around_double_colon' => true,
'no_spaces_after_function_name' => true,
'no_spaces_around_offset' => true,
'no_spaces_inside_parenthesis' => true,
'no_superfluous_elseif' => true,
'no_trailing_comma_in_singleline' => true,
'no_trailing_whitespace_in_comment' => true,
'no_trailing_whitespace_in_string' => true,
'no_trailing_whitespace' => true,
'no_unneeded_control_parentheses' => true,
'no_unneeded_curly_braces' => true,
'no_unneeded_final_method' => true,
'no_unneeded_import_alias' => true,
'no_unreachable_default_argument_value' => true,
'no_unset_cast' => true,
'no_unused_imports' => true,
'no_useless_concat_operator' => true,
'no_useless_else' => true,
'no_useless_nullsafe_operator' => true,
'no_useless_return' => true,
'no_useless_sprintf' => true,
'no_whitespace_before_comma_in_array' => true,
'no_whitespace_in_blank_line' => true,
'non_printable_character' => true,
'normalize_index_brace' => true,
'not_operator_with_successor_space' => true,
'nullable_type_declaration_for_default_null_value' => true,
'object_operator_without_whitespace' => true,
'octal_notation' => true,
'operator_linebreak' => true,
'ordered_class_elements' => ['sort_algorithm' => 'alpha', 'order' => ['use_trait', 'case', 'constant', 'constant_private', 'constant_protected', 'constant_public', 'property_private', 'property_private_readonly', 'property_private_static', 'property_protected', 'property_protected_readonly', 'property_protected_static', 'property_public', 'property_public_readonly', 'property_public_static', 'property_static', 'protected', 'construct', 'destruct', 'magic', 'method', 'public', 'method_public', 'method_abstract', 'method_public_abstract', 'method_public_abstract_static', 'method_public_static', 'method_static', 'method_private', 'method_private_abstract', 'method_private_abstract_static', 'method_private_static', 'method_protected', 'method_protected_abstract', 'method_protected_abstract_static', 'method_protected_static', 'phpunit', 'private', 'property']],
'ordered_imports' => ['sort_algorithm' => 'alpha', 'imports_order' => ['const', 'class', 'function']],
'ordered_interfaces' => true,
'ordered_traits' => true,
'php_unit_fqcn_annotation' => true,
'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
'phpdoc_align' => ['align' => 'vertical'],
'phpdoc_indent' => true,
'phpdoc_inline_tag_normalizer' => true,
'phpdoc_line_span' => true,
'phpdoc_no_access' => true,
'phpdoc_no_empty_return' => true,
'phpdoc_no_package' => true,
'phpdoc_no_useless_inheritdoc' => true,
'phpdoc_order_by_value' => true,
'phpdoc_order' => true,
'phpdoc_return_self_reference' => ['replacements' => ['this' => 'self']],
'phpdoc_scalar' => true,
'phpdoc_separation' => true,
'phpdoc_single_line_var_spacing' => true,
'phpdoc_summary' => true,
'phpdoc_tag_type' => true,
'phpdoc_to_comment' => ['ignored_tags' => ['var']],
'phpdoc_trim_consecutive_blank_line_separation' => true,
'phpdoc_trim' => true,
'phpdoc_types_order' => true,
'phpdoc_types' => true,
'phpdoc_var_annotation_correct_order' => true,
'phpdoc_var_without_name' => true,
'pow_to_exponentiation' => true,
'protected_to_private' => true,
'psr_autoloading' => true,
'random_api_migration' => true,
'regular_callable_call' => true,
'return_assignment' => true,
'return_type_declaration' => ['space_before' => 'none'],
'return_type_declaration' => true,
'self_accessor' => true,
'self_static_accessor' => true,
'semicolon_after_instruction' => true,
'set_type_to_cast' => true,
'short_scalar_cast' => true,
'simple_to_complex_string_variable' => true,
'simplified_if_return' => true,
'single_blank_line_at_eof' => true,
'single_blank_line_before_namespace' => true,
'single_class_element_per_statement' => true,
'single_line_after_imports' => true,
'single_line_comment_spacing' => true,
'single_line_comment_style' => ['comment_types' => ['hash']],
'single_line_throw' => true,
'single_quote' => true,
'single_space_after_construct' => true,
'single_space_around_construct' => true,
'single_trait_insert_per_statement' => true,
'space_after_semicolon' => true,
'standardize_increment' => true,
'standardize_not_equals' => true,
'statement_indentation' => true,
'static_lambda' => false,
'strict_comparison' => true,
'strict_param' => true,
'string_length_to_empty' => true,
'string_line_ending' => true,
'switch_case_semicolon_to_colon' => true,
'switch_case_space' => true,
'switch_continue_to_break' => true,
'ternary_operator_spaces' => true,
'ternary_to_elvis_operator' => true,
'ternary_to_null_coalescing' => true,
'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'arrays', 'match', 'parameters']],
'trim_array_spaces' => true,
'types_spaces' => ['space' => 'single', 'space_multiple_catch' => 'single'],
'unary_operator_spaces' => true,
'use_arrow_functions' => true,
'visibility_required' => true,
'void_return' => true,
'whitespace_after_comma_in_array' => true,
'yoda_style' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in([__DIR__ . '/src/', __DIR__ . '/deprecated/']),
);
================================================
FILE: .phpcs.xml.dist
================================================
================================================
FILE: .semgrepignore
================================================
.github/
docs/
examples/
tests/
\*.md
================================================
FILE: .shiprc
================================================
{
"files": {
"src/ServiceAbstract.php": [],
".version": []
},
"prefixVersion": false
}
================================================
FILE: .version
================================================
7.22.0
================================================
FILE: CHANGELOG.ARCHIVE.md
================================================
# Changelog Archive
This file contains changes for all versions of this package prior to the latest major, 7.0.
The changelog for the latest changes is [CHANGELOG.md](./CHANGELOG.md).
## [6.5.0](https://github.com/auth0/laravel-auth0/tree/6.5.0) (2021-10-15)
### Added
- Add SDK alias methods for passwordless endpoints [\#228](https://github.com/auth0/laravel-auth0/pull/228)
## [6.4.1](https://github.com/auth0/laravel-auth0/tree/6.4.0) (2021-08-02)
### Fixed
- Use the fully qualified facade class names [\#215](https://github.com/auth0/laravel-auth0/pull/215)
- Update auth0-PHP dependency [\#222](https://github.com/auth0/laravel-auth0/pull/222)
- Pass api_identifier config as audience to Auth0\SDK\Auth0 [\#214](https://github.com/auth0/laravel-auth0/pull/214)
## [6.4.0](https://github.com/auth0/laravel-auth0/tree/6.4.0) (2021-03-25)
### Improved
- Add support for Auth0 Organizations [\#209](https://github.com/auth0/laravel-auth0/pull/209)
## [6.3.0](https://github.com/auth0/laravel-auth0/tree/6.3.0) (2020-02-18)
### Improved
- Store changes made to the user object during the onLogin event hook [\#206](https://github.com/auth0/laravel-auth0/pull/206)
### Fixed
- Avoid throwing an error when calling getUserByUserInfo() during login callback event when the supplied profile is empty/null [\#207](https://github.com/auth0/laravel-auth0/pull/207)
## [6.2.0](https://github.com/auth0/laravel-auth0/tree/6.2.0) (2020-01-15)
### Added
- Support PHP 8.0 [\#200](https://github.com/auth0/laravel-auth0/pull/200)
### Fixed
- Fix the missing `return null;` in `getUserByIdentifier` [\#201](https://github.com/auth0/laravel-auth0/pull/201)
## [6.1.0](https://github.com/auth0/laravel-auth0/tree/6.1.0) (2020-09-17)
### Added
- Support Laravel 8 [\#190](https://github.com/auth0/laravel-auth0/pull/190)
### Fixed
- Fix composer.json whitespace issue [\#192](https://github.com/auth0/laravel-auth0/pull/192)
## [6.0.1](https://github.com/auth0/laravel-auth0/tree/6.0.1) (2020-04-28)
### Fixed
- Fix access token decoding and validation [\#183](https://github.com/auth0/laravel-auth0/pull/183)
## [6.0.0](https://github.com/auth0/laravel-auth0/tree/6.0.0) (2020-04-09)
**This is a major release and includes breaking changes!** This release also includes a major version change for the PHP SDK that it relies on. Please see the [migration guide](https://github.com/auth0/auth0-PHP/blob/master/MIGRATE-v5-TO-v7.md) for the PHP SDK for more information.
### Added
- auth0-PHP 7.0 - State and nonce handling [\#163](https://github.com/auth0/laravel-auth0/issues/163)
- Implement auth0 guard [\#166](https://github.com/auth0/laravel-auth0/pull/166)
### Improved
- Use array for Auth0JWTUser and add repo return types [\#176](https://github.com/auth0/laravel-auth0/pull/176)
- Update PHP SDK to v7.0.0 [\#162](https://github.com/auth0/laravel-auth0/pull/162)
- Bind SessionState handler interface in container [\#147](https://github.com/auth0/laravel-auth0/pull/147)
### Fixed
- Fix Laravel session management [\#174](https://github.com/auth0/laravel-auth0/pull/174)
- Cannot use actingAs unit tests functionality [\#161](https://github.com/auth0/laravel-auth0/issues/161)
## [5.4.0](https://github.com/auth0/laravel-auth0/tree/5.4.0) (2020-03-27)
### Added
- Laravel 7 support [\#167](https://github.com/auth0/laravel-auth0/pull/167)
### Fixed
- Laravel 7.0 supported release. [\#171](https://github.com/auth0/laravel-auth0/issues/171)
- Fixed PHPDocs [\#170](https://github.com/auth0/laravel-auth0/pull/170)
## [5.3.1](https://github.com/auth0/laravel-auth0/tree/5.3.1) (2019-11-14)
### Fixed
- Setting of state_handler in Auth0Service causes "Invalid state" error [\#154](https://github.com/auth0/laravel-auth0/issues/154)
- Allow store and state_handler to be passed in from config [\#156](https://github.com/auth0/laravel-auth0/pull/156)
- Add 'persist_refresh_token' key to laravel-auth0 configuration file. [\#152](https://github.com/auth0/laravel-auth0/pull/152)
- Replace `setEnvironment` with `setEnvProperty` [\#145](https://github.com/auth0/laravel-auth0/pull/145)
## [5.3.0](https://github.com/auth0/laravel-auth0/tree/5.3.0) (2019-09-26)
### Added
- Support Laravel 6 [\#139](https://github.com/auth0/laravel-auth0/pull/139)
- Feature request: Add Laravel 6 support [\#138](https://github.com/auth0/laravel-auth0/issues/138)
### Fixed
- Use LaravelSessionStore in the SessionStateHandler. [\#135](https://github.com/auth0/laravel-auth0/pull/135)
- SessionStateHandler should use LaravelSessionStore not SessionStore [\#125](https://github.com/auth0/laravel-auth0/issues/125)
## [5.2.0](https://github.com/auth0/laravel-auth0/tree/5.2.0) (2019-06-27)
### Added
- Authenticate as a Laravel API user using the Auth0 token [\#129](https://github.com/auth0/laravel-auth0/issues/129)
- Redirect to previous page after login [\#122](https://github.com/auth0/laravel-auth0/issues/122)
- Auth0User uses private variables so they cannot be accessed or overridden in child class [\#120](https://github.com/auth0/laravel-auth0/issues/120)
- API routes broken in auth0-laravel-php-web-app (and in general)? [\#117](https://github.com/auth0/laravel-auth0/issues/117)
- API returning "token algorithm not supported" [\#116](https://github.com/auth0/laravel-auth0/issues/116)
- Changing name of user identifier [\#115](https://github.com/auth0/laravel-auth0/issues/115)
- Possible to use User object functions? [\#114](https://github.com/auth0/laravel-auth0/issues/114)
- Auth0-PHP@5.3.1 breaks Laravel-Auth0 [\#108](https://github.com/auth0/laravel-auth0/issues/108)
- Extend Illuminate\Foundation\Auth\User [\#104](https://github.com/auth0/laravel-auth0/issues/104)
- [Bug] Inconsistencies with the singleton Auth0Service [\#103](https://github.com/auth0/laravel-auth0/issues/103)
- How do you combine Auth0 Lock with Laravel Auth0? [\#102](https://github.com/auth0/laravel-auth0/issues/102)
- OnLogin callback question [\#97](https://github.com/auth0/laravel-auth0/issues/97)
- Add composer.lock file [\#123](https://github.com/auth0/laravel-auth0/pull/123) ([lbalmaceda](https://github.com/lbalmaceda))
### Improved
- Change private properties to protected [\#132](https://github.com/auth0/laravel-auth0/pull/132)
- Return null instead of false in Auth0UserProvider. [\#128](https://github.com/auth0/laravel-auth0/pull/128)
- Change the visibility of the getter method from private to public [\#121](https://github.com/auth0/laravel-auth0/pull/121)
- Updated required PHP version to 5.4 in composer [\#118](https://github.com/auth0/laravel-auth0/pull/118)
- Changed arrays to use short array syntax [\#110](https://github.com/auth0/laravel-auth0/pull/110)
### Fixed
- Fix cachehandler resolving issues [\#131](https://github.com/auth0/laravel-auth0/pull/131)
- Added the Auth0Service as a singleton through the classname [\#107](https://github.com/auth0/laravel-auth0/pull/107)
- Fixed typo [\#106](https://github.com/auth0/laravel-auth0/pull/106)
## [5.1.0](https://github.com/auth0/laravel-auth0/tree/5.1.0) (2018-03-20)
### Added
- AutoDiscovery [\#91](https://github.com/auth0/laravel-auth0/pull/91) ([m1guelpf](https://github.com/m1guelpf))
- Added guzzle options to config to allow for connection options [\#88](https://github.com/auth0/laravel-auth0/pull/88)
### Improved
- Change default settings file [\#96](https://github.com/auth0/laravel-auth0/pull/96)
- Utilise Auth0->Login to ensure state validation [\#90](https://github.com/auth0/laravel-auth0/pull/90)
### Fixed
- Make code comments gender neutral [\#98](https://github.com/auth0/laravel-auth0/pull/98)
- Fix README and CHANGELOG [\#99](https://github.com/auth0/laravel-auth0/pull/99)
- pls change config arg name [\#95](https://github.com/auth0/laravel-auth0/issues/95)
## [5.0.2](https://github.com/auth0/laravel-auth0/tree/5.0.2) (2017-08-30)
### Fixed
- Use instead of to identify the Auth0 user [\#80](https://github.com/auth0/laravel-auth0/pull/80)
## [5.0.1](https://github.com/auth0/laravel-auth0/tree/5.0.1) (2017-02-23)
### Fixed
- Fixed `supported_algs` configuration name
## [5.0.0](https://github.com/auth0/laravel-auth0/tree/5.0.0) (2017-02-22)
### Fixed
- V5: update to auth0 sdk v5 [\#69](https://github.com/auth0/laravel-auth0/pull/69)
## [4.0.8](https://github.com/auth0/laravel-auth0/tree/4.0.8) (2017-01-27)
### Fixed
- Allow use of RS256 Protocol [\#63](https://github.com/auth0/wp-auth0/issues/63)
- Add RS256 to the list of supported algorithms [\#62](https://github.com/auth0/wp-auth0/issues/62)
- allow to configure the algorithm supported for token verification [\#65](https://github.com/auth0/laravel-auth0/pull/65)
## [4.0.7](https://github.com/auth0/laravel-auth0/tree/4.0.7) (2017-01-02)
### Fixed
- it should pass all the configs to the oauth client [\#64](https://github.com/auth0/laravel-auth0/pull/64)
## [4.0.6](https://github.com/auth0/laravel-auth0/tree/4.0.6) (2016-11-29)
### Fixed
- Code style & docblocks [\#56](https://github.com/auth0/laravel-auth0/pull/56)
- Adding accessor to retrieve JWT from Auth0Service [\#58](https://github.com/auth0/laravel-auth0/pull/58)
## [4.0.5](https://github.com/auth0/laravel-auth0/tree/4.0.5) (2016-11-29)
### Fixed
- Added flag for not encoded tokens + removed example [\#57](https://github.com/auth0/laravel-auth0/pull/57)
## [4.0.4](https://github.com/auth0/laravel-auth0/tree/4.0.4) (2016-11-25)
### Fixed
- Fixing config type [\#55](https://github.com/auth0/laravel-auth0/pull/55)
## [4.0.2](https://github.com/auth0/laravel-auth0/tree/4.0.2) (2016-10-03)
### Fixed
- Fixing JWTVerifier [\#54](https://github.com/auth0/laravel-auth0/pull/54)
## [4.0.1](https://github.com/auth0/laravel-auth0/tree/4.0.1) (2016-09-19)
### Fixed
- Fix error becuase of contract and class with the same name [\#52](https://github.com/auth0/laravel-auth0/pull/52)
## [4.0.0](https://github.com/auth0/laravel-auth0/tree/4.0.0) (2016-09-15)
### Improved
- Better support for Laravel 5.3: Support for Laravel Passport for token verification
Support of auth0 PHP sdk v4 with JWKs cache
### Fixed
- Merge pull request #50 from auth0/4.x.x-dev [\#50](https://github.com/auth0/laravel-auth0/pull/50)
## [3.2.1](https://github.com/auth0/laravel-auth0/tree/3.2.1) (2016-09-12)
### Fixed
- Fix for Laravel 5.2 [\#49](https://github.com/auth0/laravel-auth0/pull/49)
## [3.2.0](https://github.com/auth0/laravel-auth0/tree/3.2.0) (2016-07-11)
### Fixed
- New optional jwt middleware [\#40](https://github.com/auth0/laravel-auth0/pull/40)
## [3.1.0](https://github.com/auth0/laravel-auth0/tree/3.1.0) (2016-05-02)
### Fixed
- 3.1.0 [\#36](https://github.com/auth0/laravel-auth0/pull/36)
## [3.0.3](https://github.com/auth0/laravel-auth0/tree/3.0.3) (2016-01-28)
### Fixed
- Tag 2.2.2 breaks on Laravel 5.1 [\#30](https://github.com/auth0/laravel-auth0/issues/30)
- Conform to 5.2's Authenticatable contract [\#31](https://github.com/auth0/laravel-auth0/pull/31)
## [3.0.2](https://github.com/auth0/laravel-auth0/tree/3.0.2) (2016-01-25)
### Fixed
- Added optional persistence configuration values [\#29](https://github.com/auth0/laravel-auth0/pull/29)
## [2.2.1](https://github.com/auth0/laravel-auth0/tree/2.2.1) (2016-01-22)
### Fixed
- Create a logout route [\#25](https://github.com/auth0/laravel-auth0/issues/25)
- Auth0 SDK checks for null values instead of false [\#27](https://github.com/auth0/laravel-auth0/pull/27)
## [3.0.1](https://github.com/auth0/laravel-auth0/tree/3.0.1) (2016-01-18)
### Fixed
- updated auth0-php dependency [\#24](https://github.com/auth0/laravel-auth0/pull/24)
## [3.0.0](https://github.com/auth0/laravel-auth0/tree/3.0.0) (2016-01-06)
### Fixed
- auth0/auth0-php ~1.0 requirement doesn't support latest GuzzleHttp [\#21](https://github.com/auth0/laravel-auth0/issues/21)
- updated to be compatible with laravel 5.2 [\#23](https://github.com/auth0/laravel-auth0/pull/23)
## [2.2.0](https://github.com/auth0/laravel-auth0/tree/2.2.0) (2015-11-30)
### Fixed
- updated auth0-php dependency version [\#22](https://github.com/auth0/laravel-auth0/pull/22)
- Update login.blade.php [\#20](https://github.com/auth0/laravel-auth0/pull/20)
## [2.1.4](https://github.com/auth0/laravel-auth0/tree/2.1.4) (2015-10-27)
### Fixed
- Middleware contract has been deprecated in 5.1 [\#19](https://github.com/auth0/laravel-auth0/pull/19)
- Fixed some typo's in the comments. [\#18](https://github.com/auth0/laravel-auth0/pull/18)
- Removed note about unstable dependency from README [\#17](https://github.com/auth0/laravel-auth0/pull/17)
- Update composer instructions [\#16](https://github.com/auth0/laravel-auth0/pull/16)
- Use a tagged release of adoy/oauth2 [\#15](https://github.com/auth0/laravel-auth0/pull/15)
## [2.1.3](https://github.com/auth0/laravel-auth0/tree/2.1.3) (2015-07-17)
### Fixed
- updated jwt dependency [\#14](https://github.com/auth0/laravel-auth0/pull/14)
## [2.1.2](https://github.com/auth0/laravel-auth0/tree/2.1.2) (2015-05-15)
### Fixed
- Added override of info headers [\#13](https://github.com/auth0/laravel-auth0/pull/13)
## [2.1.1](https://github.com/auth0/laravel-auth0/tree/2.1.1) (2015-05-12)
### Fixed
- SDK Client headers spec compliant [\#11](https://github.com/auth0/laravel-auth0/issues/11)
- Support for Laravel 5? [\#6](https://github.com/auth0/laravel-auth0/issues/6)
- SDK Client headers spec compliant \#11 [\#12](https://github.com/auth0/laravel-auth0/pull/12)
## [2.1.0](https://github.com/auth0/laravel-auth0/tree/2.1.0) (2015-05-07)
### Fixed
- Upgrade to auth-php 1.0.0: Added support to API V2 [\#10](https://github.com/auth0/laravel-auth0/pull/10)
## [2.0.0](https://github.com/auth0/laravel-auth0/tree/2.0.0) (2015-04-20)
### Fixed
- Package V2 for Laravel5 [\#9](https://github.com/auth0/laravel-auth0/pull/9)
## [1.0.8](https://github.com/auth0/laravel-auth0/tree/1.0.8) (2015-04-14)
- [Full Changelog](https://github.com/auth0/laravel-auth0/compare/1.0.7...1.0.8)
## [1.0.7](https://github.com/auth0/laravel-auth0/tree/1.0.7) (2015-04-13)
### Fixed
- Fixed the way the access token is pased to the A0User [\#7](https://github.com/auth0/laravel-auth0/pull/7)
- Update README.md [\#5](https://github.com/auth0/laravel-auth0/pull/5)
## [1.0.6](https://github.com/auth0/laravel-auth0/tree/1.0.6) (2014-08-01)
- [Full Changelog](https://github.com/auth0/laravel-auth0/compare/1.0.5...1.0.6)
## [1.0.5](https://github.com/auth0/laravel-auth0/tree/1.0.5) (2014-08-01)
### Fixed
- Problem with normal laravel user table [\#4](https://github.com/auth0/laravel-auth0/issues/4)
- Update README.md [\#3](https://github.com/auth0/laravel-auth0/pull/3)
## [1.0.4](https://github.com/auth0/laravel-auth0/tree/1.0.4) (2014-05-07)
- [Full Changelog](https://github.com/auth0/laravel-auth0/compare/1.0.3...1.0.4)
## [1.0.3](https://github.com/auth0/laravel-auth0/tree/1.0.3) (2014-04-21)
- [Full Changelog](https://github.com/auth0/laravel-auth0/compare/1.0.0...1.0.3)
================================================
FILE: CHANGELOG.md
================================================
# Change Log
## [7.22.0](https://github.com/auth0/laravel-auth0/tree/7.22.0) (2026-04-08)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.21.0...7.22.0)
**Added**
- Laravel 13 support [\#491](https://github.com/auth0/laravel-auth0/pull/491) ([cosmastech](https://github.com/cosmastech))
**Fixed**
- Add missing hashPasswordForCookie to AuthenticationGuard [\#492](https://github.com/auth0/laravel-auth0/pull/492) ([steffjenl](https://github.com/steffjenl))
**Changed**
- Add PHP 8.4 to CI test matrix, cap Psalm <6.5, disable static_lambda CS rule for PHP 8.5+ compat [\#491](https://github.com/auth0/laravel-auth0/pull/491) ([kishore7snehil](https://github.com/kishore7snehil))
## [7.21.0](https://github.com/auth0/laravel-auth0/tree/7.21.0) (2026-04-01)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.20.0...7.21.0)
**Fixed**
- Security fix: Resolve CVE-2026-34236
## [7.20.0](https://github.com/auth0/laravel-auth0/tree/7.20.0) (2025-12-16)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.19.0...7.20.0)
**Fixed**
- Security fix: Resolve CVE-2025-68129
## [7.19.0](https://github.com/auth0/laravel-auth0/tree/7.19.0) (2025-10-01)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.18.0...7.19.0)
**Fixed**
- Security fix: Resolve CVE-2025-58769
## [7.18.0](https://github.com/auth0/laravel-auth0/tree/7.18.0) (2025-09-02)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.17.0...7.18.0)
**Added**
- Mixed changes: feature, fixes, and docs from community [\#477](https://github.com/auth0/laravel-auth0/pull/477) ([kishore7snehil](https://github.com/kishore7snehil))
## [7.17.0](https://github.com/auth0/laravel-auth0/tree/7.17.0) (2025-05-16)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.16.0...7.17.0)
**Fixed**
- Security fix: Resolve CVE-2025-47275
## [7.16.0](https://github.com/auth0/laravel-auth0/tree/7.16.0) (2025-04-06)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.15.0...7.16.0)
**Added**
- Laravel 12 Support [\#470](https://github.com/auth0/laravel-auth0/pull/470) ([lee-to](https://github.com/lee-to))
**Fixed**
- refactor: fix failing tests [\#471](https://github.com/auth0/laravel-auth0/pull/471) ([noevidenz](https://github.com/noevidenz))
## [7.15.0](https://github.com/auth0/laravel-auth0/tree/7.15.0) (2024-06-03)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.14.0...7.15.0)
**Changed**
- perf: Update getCredential to only refresh credential once per request [\#453](https://github.com/auth0/laravel-auth0/pull/453) ([ComputerTinker](https://github.com/ComputerTinker))
## [7.14.0](https://github.com/auth0/laravel-auth0/tree/7.14.0) (2024-04-01)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.13.0...7.14.0)
**Changed**
- refactor: add additional Telescope state check [\#447](https://github.com/auth0/laravel-auth0/pull/447) ([samuelhgf](https://github.com/samuelhgf))
- chore(deps): replace temporary `psalm-laravel-plugin` fork with official [\#448](https://github.com/auth0/laravel-auth0/pull/448) ([alies-dev](https://github.com/alies-dev))
## [7.13.0](https://github.com/auth0/laravel-auth0/tree/7.13.0) (2024-03-11)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.12.0...7.13.0)
**Added**
- Add support for Laravel 11 [\#445](https://github.com/auth0/laravel-auth0/pull/445) ([evansims](https://github.com/evansims))
**Changed**
- Verify that Telescope is enabled via configuration helper [\#444](https://github.com/auth0/laravel-auth0/pull/444) ([samuelhgf](https://github.com/samuelhgf))
## [7.12.0](https://github.com/auth0/laravel-auth0/tree/7.12.0) (2023-12-07)
[Full Changelog](https://github.com/auth0/laravel-auth0/compare/7.11.0...7.12.0)
**Added**
- Implement support for Back-Channel Logout [\#435](https://github.com/auth0/laravel-auth0/pull/435) ([evansims](https://github.com/evansims))
- Restore configurable route paths [\#436](https://github.com/auth0/laravel-auth0/pull/436) ([evansims](https://github.com/evansims))
**Fixed**
- Resolve `CacheBridgeAbstract::save()` not storing values when cache misses [\#434](https://github.com/auth0/laravel-auth0/pull/434) ([seruymt](https://github.com/seruymt))
## [7.11.0](https://github.com/auth0/laravel-auth0/tree/7.11.0) (2023-08-08)
**Added**
- Significant performance improvements by eliminating redundant user queries.
- Compatibility support for [Laravel Telescope](https://laravel.com/docs/telescope). See [docs/Telescope.md](./docs/Telescope.md) for more information.
- A refactored Events API has been introduced. See [docs/Events.md](./docs/Events.md) for more information.
- `AUTH0_SESSION_STORAGE` and `AUTH0_TRANSIENT_STORAGE` now support a `cookie` value to enable the native Auth0-PHP SDK cookie session handler. See [docs/Cookies.md](./docs/Cookies.md) for more information.
**Fixed**
- Addressed an issue where, under certain circumstances, the first user authentication attempt after a session invalidation could fail.
**Changed**
- Session regeneration/invalidation has been refactored.
- Discarded sessions are now deleted when they are invalidated by the SDK, rather than wait for Laravel to garbage collect.
- Session storage has been refactored. Session data is now stored as a JSON array in a single `auth0_session` entry in the Laravel session store, rather than in multiple keys.
**Documentation**
- A demonstration Eloquent user model and repository implementation has been added to [docs/Eloquent.md](./docs/Eloquent.md).
- A new [docs/Sessions.md](./docs/Sessions.md) document has been added for guidance on the various session driver options available.
## [7.10.1](https://github.com/auth0/laravel-auth0/tree/7.10.1) (2023-08-07)
**Fixed**
- Addressed an issue where, under certain circumstances, permissions state could be lost after authenticating.
## [7.10.0](https://github.com/auth0/laravel-auth0/tree/7.10.0) (2023-07-24)
**Added**
- Organization Name support added for Authentication API and token handling ¹
**Changed**
- Guards are now registered with the priority middleware list.
- Bumped `auth0-php` dependency version range to `^8.7`.
- Updated telemetry to indicate new `laravel` package name (previously `laravel-auth0`.)
**Fixed**
- Addressed issue where placeholder `AUTH0_` dotenv values could erroneously be interpreted as true configuration values.
> **Note**
> ¹ To use this feature, an Auth0 tenant must have support for it enabled. This feature is not yet available to all tenants.
## [7.9.1](https://github.com/auth0/laravel-auth0/tree/7.9.1) (2023-06-21)
**Fixed**
- Resolved an issue where, under certain circumstances, the AuthenticationGuard middleware could get erroneously added to the `api` middleware group, causing a session to be established in a stateless request. ([\#415](https://github.com/auth0/laravel-auth0/pull/415))
## [7.9.0](https://github.com/auth0/laravel-auth0/tree/7.9.0) (2023-06-15)
**Added**
- SDK configuration (`config/auth0.php`) now supports a `configurationPath` property for specifying a custom search path for `.auth0.*.json` and `.env*` files. ([\#407](https://github.com/auth0/laravel-auth0/pull/407))
- `Auth0\Laravel\Guards\GuardAbstract` now extends `Illuminate\Contracts\Auth\Guard`. ([\#410](https://github.com/auth0/laravel-auth0/pull/410))
**Fixed**
- Resolved host environment variables not being loaded as expected when a `.env` file is also used. ([\#408](https://github.com/auth0/laravel-auth0/pull/408))
- Resolved surrounding quote characters not being trimmed from environment variables and `.env` files during processing. ([\#409](https://github.com/auth0/laravel-auth0/pull/409))
## [7.8.1](https://github.com/auth0/laravel-auth0/tree/7.8.1) (2023-05-19)
**Fixed**
- Resolved an issue where parsing `.env` files could sometimes throw an exception when handling non-key-value pair strings. ([\#395](https://github.com/auth0/laravel-auth0/pull/395))
## [7.8.0](https://github.com/auth0/laravel-auth0/tree/7.8.0) (2023-05-18)
**Added**
- This release adds support for authenticating using **[Pushed Authorization Requests](https://www.rfc-editor.org/rfc/rfc6749)**.
- This release introduces **two new Authentication Guards** which provide a streamlined integration experience for developers that need to simultaneously support both session-based authentication and token-based endpoint authorization in their Laravel applications.
| Guard | Class | Description |
| --------------------- | ----------------------------------------------- | ----------------------------- |
| `auth0.authenticator` | `Auth0\Laravel\Auth\Guards\AuthenticationGuard` | Session-based authentication. |
| `auth0.authorizer` | `Auth0\Laravel\Auth\Guards\AuthorizationGuard` | Token-based authorization. |
- These guards are compatible with Laravel's Authentication API and support the standard `auth` middleware.
- These guards are compatible with Laravel's Authorization API and support the standard `can` middleware, and the `Guard` facade, and work with the Policies API.
- 3 new pre-built Guards are available: `scope` and `permission`, as well as a dynamic `*:*`. This enables you to verify whether the user's access token has a particular scope or (if RBAC is enabled on the Auth0 API) a particular permission. For example `Gate::check('scope', 'email')` or `Route::get(/*...*/)->can('read:messages')`.
- The SDK now automatically registers these guards to Laravel's standard `web` and `api` middleware groups, respectively. Manual Guard setup in `config/auth.php` is no longer necessary.
- The SDK now automatically registers the Authentication routes. Manual route setup in `routes/web.php` is no longer necessary.
- 2 new routing Middleware have been added: `Auth0\Laravel\Http\Middleware\AuthenticatorMiddleware` and `Auth0\Laravel\Http\Middleware\AuthorizerMiddleware`. These are automatically registered with your Laravel application, and ensure the Auth0 Guards are used for authentication for `web` routes and authorization for `api` routes, respectively. This replaces the need for the `guard` middleware or otherwise manual Guard assignment in your routes.
**Changed**
- We've introduced **a new configuration syntax**. This new syntax is more flexible and allows for more complex configuration scenarios, and introduces support for multiple guard instances. Developers using the previous syntax will have their existing configurations applied to all guards uniformly.
- The SDK can now **configure itself using a `.auth0.json` file in the project root directory**. This file can be generated [using the Auth0 CLI](./docs/Configuration.md), and provides a significantly simpler configuration experience for developers.
- The previous `auth0.guard` Guard (`Auth0\Laravel\Auth\Guard`) has been **refactored** as a lightweight wrapper around the new `AuthenticationGuard` and `AuthorizationGuard` guards.
## [7.7.0](https://github.com/auth0/laravel-auth0/tree/7.7.0) (2023-04-26)
**Added**
- `Auth0\Laravel\Auth0` now has a `management()` shortcut method for issuing Management API calls. ([\#376](https://github.com/auth0/laravel-auth0/pull/376))
- `Auth0\Laravel\Auth0\Guard` now has a `refreshUser()` method for querying `/userinfo` endpoint and refreshing the authenticated user's cached profile data. ([\#375](https://github.com/auth0/laravel-auth0/pull/375))
- `Auth0\Laravel\Http\Controller\Stateful\Login` now raises a `LoginAttempting` event, offering an opportunity to customize the authorization parameters before the login redirect is issued. ([\#382](https://github.com/auth0/laravel-auth0/pull/382))
**Changed**
- The `tokenCache`, `managementTokenCache`, `sessionStorage` and `transientStorage` configuration values now support `false` or `string` values pointing to class names (e.g. `\Some\Cache::class`) or class aliases (e.g. `cache.psr6`) registered with Laravel. ([\#381](https://github.com/auth0/laravel-auth0/pull/381))
## [7.6.0](https://github.com/auth0/laravel-auth0/tree/7.6.0) (2023-04-12)
**Added**
- `Auth0\Laravel\Http\Middleware\Guard`, new middleware that forces Laravel to route requests through a group using a specific Guard. ([\#362](https://github.com/auth0/laravel-auth0/pull/362))
**Changed**
- `Auth0\Laravel\Http\Middleware\Stateful\Authenticate` now remembers the intended route (using `redirect()->setIntendedUrl()`) before kicking off the authentication flow redirect. Users will be returned to the memorized intended route after completing their authentication flow. ([\#364](https://github.com/auth0/laravel-auth0/pull/364))
**Fixed**
- legacyGuardUserMethod behavior should use `$session`, not `$token` ([\#353](https://github.com/auth0/laravel-auth0/pull/365))
## [7.5.2](https://github.com/auth0/laravel-auth0/tree/7.5.2) (2023-04-10)
**Fixed**
- Relaxed response types from middleware to use low-level `Symfony\Component\HttpFoundation\Response` class, allowing for broader and custom response types.
## [7.5.1](https://github.com/auth0/laravel-auth0/tree/7.5.1) (2023-04-04)
**Fixed**
- Resolved an issue wherein custom user repositories could fail to be instantiated under certain circumstances.
## [7.5.0](https://github.com/auth0/laravel-auth0/tree/7.5.0) (2023-04-03)
This release includes support for Laravel 10, and major improvements to the internal state handling mechanisms of the SDK.
**Added**
- Support for Laravel 10 [#349](https://github.com/auth0/laravel-auth0/pull/349)
- New `Auth0\Laravel\Traits\Imposter` trait to allow for easier testing. [Example usage](./tests/Unit/Traits/ImpersonateTest.php)
- New Exception types have been added for more precise error-catching.
**Changed**
The following changes have no effect on the external API of this package but may affect internal usage.
- `Guard` will now more reliably detect changes in the underlying Auth0-PHP SDK session state.
- `Guard` will now more reliably sync changes back to the underlying Auth0-PHP SDK session state.
- `StateInstance` concept has been replaced by a new `Credentials` entity.
- `Guard` updated to use new `Credentials` entity as primary internal storage for user data.
- `Auth0\Laravel\Traits\ActingAsAuth0User` was updated to use new `Credentials` entity.
- The HTTP middleware has been refactored to more clearly differentiate between token and session-based identities.
- The `authenticate`, `authenticate.optional` and `authorize.optional` HTTP middleware now supports scope filtering, as `authorize` already did.
- Upgraded test suite to use PEST 2.0 framework.
- Updated test coverage to 100%.
**Fixed**
- A 'Session store not set on request' error could occur when downstream applications implemented unit testing that uses the Guard. This should be resolved now.
- `Guard` would not always honor the `provider` configuration value in `config/auth.php`.
- `Guard` is no longer defined as a Singleton to better support applications that need multi-guard configurations.
### Notes
#### Changes to `user()` behavior
This release includes a significant behavior change around the `user()` method of the Guard. Previously, by simply invoking the method, the SDK would search for any available credential (access token, device session, etc.) and automatically assign the user within the Guard. The HTTP middleware has been upgraded to handle the user assignment step, and `user()` now only returns the current state of the user assignment without altering it.
A new property has been added to the `config/auth0.php` configuration file: `behavior`. This is an array. At this time, there is a single option: `legacyGuardUserMethod`, a bool. If this value is set to true, or if the key is missing, the previously expected behavior will be applied, and `user()` will behave as it did before this release. The property defaults to `false`.
#### Changes to Guard and Provider driver aliases
We identified an issue with using identical alias naming for both the Guard and Provider singletons under Laravel 10, which has required us to rename these aliases. As previous guidance had been to instantiate these using their class names, this should not be a breaking change in most cases. However, if you had used `auth0` as the name for either the Guard or the Provider drivers, kindly note that these have changed. Please use `auth0.guard` for the Guard driver and `auth0.provider` for the Provider driver. This is a regrettable change but was necessary for adequate Laravel 10 support.
## [7.4.0](https://github.com/auth0/laravel-auth0/tree/7.4.0) (2022-12-12)
**Added**
- feat: Add `Auth0\Laravel\Event\Middleware\...` event hooks [\#340](https://github.com/auth0/laravel-auth0/pull/340)
- feat: Add `Auth0\Laravel\Event\Configuration\Building` event hook [\#339](https://github.com/auth0/laravel-auth0/pull/339)
## [7.3.0](https://github.com/auth0/laravel-auth0/tree/7.3.0) (2022-11-07)
**Added**
- add: Raise additional Laravel Auth Events [\#331](https://github.com/auth0/laravel-auth0/pull/331)
**Fixed**
- fix: `env()` incorrectly assigns `cookieExpires` to a `string` value [\#332](https://github.com/auth0/laravel-auth0/pull/332)
- fix: Auth0\Laravel\Cache\LaravelCachePool::createItem returning a cache miss [\#329](https://github.com/auth0/laravel-auth0/pull/329)
## [7.2.2](https://github.com/auth0/laravel-auth0/tree/7.2.2) (2022-10-19)
**Fixed**
- Restore `php artisan vendor:publish` command [\#321](https://github.com/auth0/laravel-auth0/pull/321)
- Bump minimum `auth0/auth0-php` version to `^8.3.4` [\#322](https://github.com/auth0/laravel-auth0/pull/322)
## [7.2.1](https://github.com/auth0/laravel-auth0/tree/7.2.1) (2022-10-13)
**Fixed**
- `Auth0\Laravel\Auth0` no longer requires a session configuration for stateless strategies, restoring previous behavior. [\#317](https://github.com/auth0/laravel-auth0/pull/317)
- The SDK now requires `^3.0` of the `psr/cache` dependency, to accommodate breaking changes made in the upstream interface (typed parameters and return types) for PHP 8.0+. [\#316](https://github.com/auth0/laravel-auth0/pull/316)
## [7.2.0](https://github.com/auth0/laravel-auth0/tree/7.2.0) (2022-10-10)
**Changed**
- `Auth0\Laravel\Store\LaravelSession` has been added as the default `sessionStorage` and `transientStorage` interfaces for the underlying [Auth0-PHP SDK](https://github.com/auth0/auth0-PHP/). The SDK now leverages the native [Laravel Session APIs](https://laravel.com/docs/9.x/session) by default. [\#307](https://github.com/auth0/laravel-auth0/pull/307)¹
- `Auth0\Laravel\Cache\LaravelCachePool` and `Auth0\Laravel\Cache\LaravelCacheItem` have been added as the default `tokenCache` and `managementTokenCache` interfaces for the underlying [Auth0-PHP SDK](https://github.com/auth0/auth0-PHP/). The SDK now leverages the native [Laravel Cache APIs](https://laravel.com/docs/9.x/cache) by default. [\#307](https://github.com/auth0/laravel-auth0/pull/307)
- `Auth0\Laravel\Auth\Guard` now supports the `viaRemember` method. [\#306](https://github.com/auth0/laravel-auth0/pull/306)
- `Auth0\Laravel\Http\Middleware\Stateless\Authorize` now returns a 401 status instead of 403 for unauthenticated users. [\#304](https://github.com/auth0/laravel-auth0/issues/304)
- PHP 8.0 is now the minimum supported runtime version. Please review the [README](README.md) for more information on support windows.
¹ This change may require your application's users to re-authenticate. You can avoid this by changing the `sessionStorage` and `transientStorage` options in your SDK configuration to their previous default instances of `Auth0\SDK\Store\CookieStore`, but it is recommended you migrate to the new `LaravelSession` default.
## [7.1.0](https://github.com/auth0/laravel-auth0/tree/7.1.0) (2022-08-08)
**Changed**
- Return interfaces instead of concrete classes [\#296](https://github.com/auth0/laravel-auth0/pull/296)
- change: Use class names for `app()` calls [\#291](https://github.com/auth0/laravel-auth0/pull/291)
**Fixed**
- Fix: `Missing Code` error on Callback Route for Octane Customers [\#297](https://github.com/auth0/laravel-auth0/pull/297)
## [7.0.1](https://github.com/auth0/laravel-auth0/tree/7.0.1) (2022-06-01)
**Fixed**
- Fixed an issue in `Auth0\Laravel\Http\Controller\Stateful\Callback` where `$errorDescription`'s value was assigned an incorrect value when an error was encountered. [\#266](https://github.com/auth0/laravel-auth0/pull/288)
## [7.0.0](https://github.com/auth0/laravel-auth0/tree/7.0.0) (2022-03-21)
Auth0 Laravel SDK v7 includes many significant changes over previous versions:
- Support for Laravel 9.
- Support for Auth0-PHP SDK 8.
- New authentication route controllers for plug-and-play login support.
- Improved authentication middleware for regular web applications.
- New authorization middleware for token-based backend API applications.
As expected with a major release, Auth0 Laravel SDK v7 includes breaking changes. Please review the [upgrade guide](UPGRADE.md) thoroughly to understand the changes required to migrate your application to v7.
### Breaking Changes
- Namespace has been updated from `Auth0\Login` to `Auth0\Laravel`
- Auth0-PHP SDK dependency updated to V8
- New configuration format
- SDK now self-registers its services and middleware
- New UserProvider API
> Changelog entries for releases prior to 8.0 have been relocated to [CHANGELOG.ARCHIVE.md](CHANGELOG.ARCHIVE.md).
================================================
FILE: LICENSE.md
================================================
The MIT License (MIT)
Copyright (c) 2023 Auth0, Inc. (https://auth0.com)
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
================================================

**The Auth0 Laravel SDK is a PHP package that integrates [Auth0](https://auth0.com) into your Laravel application.** It includes no-code user authentication, extensive Management API support, permissions-based routing access control, and more.
- [Requirements](#requirements)
- [Getting Started](#getting-started)
- [1. Install the SDK](#1-install-the-sdk)
- [2. Install the CLI](#2-install-the-cli)
- [3. Configure the SDK](#3-configure-the-sdk)
- [4. Run the Application](#4-run-the-application)
- [Documentation](#documentation)
- [QuickStarts](#quickstarts)
- [Contributing](#contributing)
- [Code of Conduct](#code-of-conduct)
- [Security](#security)
- [License](#license)
## Requirements
Your application must use a [supported Laravel version](#supported-laravel-releases), and your host environment must be running a [maintained PHP version](https://www.php.net/supported-versions.php). Please review [our support policy](./docs/Support.md) for more information.
You will also need [Composer](https://getcomposer.org/) and an [Auth0 account](https://auth0.com/signup).
### Supported Laravel Releases
The next major release of Laravel is forecasted for Q1 2025. We anticipate supporting it upon release.
| Laravel | SDK | PHP | Supported Until |
| ---------------------------------------------- | ----- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [12.x](https://laravel.com/docs/11.x/releases) | 7.15+ | [8.4](https://www.php.net/releases/8.4/en.php) | Approx. [Feb 2027](https://laravel.com/docs/12.x/releases#support-policy) (EOL for Laravel 12) |
| | | [8.2](https://www.php.net/releases/8.3/en.php) | Approx. [Dec 2025](https://www.php.net/supported-versions.php) (EOL for PHP 8.3) |
We strive to support all actively maintained Laravel releases, prioritizing support for the latest major version with our SDK. If a new Laravel major introduces breaking changes, we may have to end support for past Laravel versions earlier than planned.
Affected Laravel versions will still receive security fixes until their end-of-life date, as announced in our release notes.
### Maintenance Releases
The following releases are no longer being updated with new features by Auth0, but will continue to receive security updates through their end-of-life date.
| Laravel | SDK | PHP | Security Fixes Until |
| ---------------------------------------------- | ---------- | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
| [11.x](https://laravel.com/docs/10.x/releases) | 7.13+ | [8.4](https://www.php.net/releases/8.4/en.php) | [March 2026](https://laravel.com/docs/11.x/releases#support-policy) (EOL for Laravel 11) |
| | | [8.3](https://www.php.net/releases/8.3/en.php) | [March 2026](https://laravel.com/docs/11.x/releases#support-policy) (EOL for Laravel 11) |
| | | [8.2](https://www.php.net/releases/8.2/en.php) | [Dec 2026](https://www.php.net/supported-versions.php) (EOL for PHP 8.2) |
### Unsupported Releases
The following releases are unsupported by Auth0. While they may be suitable for some legacy applications, your mileage may vary. We recommend upgrading to a supported version as soon as possible.
| Laravel | SDK |
| -------------------------------------------- | ---------- |
| [10.x](https://laravel.com/docs/10.x/releases)| 7.5 - 7.12 |
| [9.x](https://laravel.com/docs/9.x/releases) | 7.0 - 7.12 |
| [8.x](https://laravel.com/docs/8.x/releases) | 7.0 - 7.4 |
| [7.x](https://laravel.com/docs/7.x/releases) | 5.4 - 6.5 |
| [6.x](https://laravel.com/docs/6.x/releases) | 5.3 - 6.5 |
| [5.x](https://laravel.com/docs/5.x/releases) | 2.0 - 6.1 |
| [4.x](https://laravel.com/docs/4.x/releases) | 1.x |
## Getting Started
The following is our recommended approach to getting started with the SDK. Alternatives are available in [our expanded installation guide](./docs/Installation.md).
### 1. Install the SDK
- For **new applications**, we offer a quickstart template — a version of the default Laravel 9 starter project pre-configured for use with the Auth0 SDK.
```shell
composer create-project auth0-samples/laravel auth0-laravel-app && cd auth0-laravel-app
```
- For **existing applications**, you can install the SDK using Composer.
```shell
composer require auth0/login:^7 --update-with-all-dependencies
```
In this case, you will also need to generate an SDK configuration file for your application.
```shell
php artisan vendor:publish --tag auth0
```
### 2. Install the CLI
1. Install the [Auth0 CLI](https://github.com/auth0/auth0-cli) to manage your account from the command line.
```shell
curl -sSfL https://raw.githubusercontent.com/auth0/auth0-cli/main/install.sh | sh -s -- -b .
```
Move the CLI to a directory in your `PATH` to make it available system-wide.
```shell
sudo mv ./auth0 /usr/local/bin
```
💡 If you prefer not to move the CLI, simply substitute `auth0` in the CLI steps below with `./auth0`.
Using Homebrew (macOS)
```shell
brew tap auth0/auth0-cli && brew install auth0
```
Using Scoop (Windows)
```cmd
scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
scoop install auth0
```
2. Authenticate the CLI with your Auth0 account. Choose "as a user" if prompted.
```shell
auth0 login
```
### 3. Configure the SDK
1. Register a new application with Auth0.
```shell
auth0 apps create \
--name "My Laravel Application" \
--type "regular" \
--auth-method "post" \
--callbacks "http://localhost:8000/callback" \
--logout-urls "http://localhost:8000" \
--reveal-secrets \
--no-input \
--json > .auth0.app.json
```
2. Register a new API with Auth0.
```shell
auth0 apis create \
--name "My Laravel Application API" \
--identifier "https://github.com/auth0/laravel-auth0" \
--offline-access \
--no-input \
--json > .auth0.api.json
```
3. Add the new files to `.gitignore`.
```bash
echo ".auth0.*.json" >> .gitignore
```
Using Windows PowerShell
```powershell
Add-Content .gitignore "`n.auth0.*.json"
```
Using Windows Command Prompt
```cmd
echo .auth0.*.json >> .gitignore
```
### 4. Run the Application
Boot the application using PHP's built-in web server.
```shell
php artisan serve
```
Direct your browser to [http://localhost:8000](http://localhost:8000) to experiment with the application.
- **Authentication**
Users can log in or out of the application by visiting the [`/login`](http://localhost:8000/login) or [`/logout`](http://localhost:8000/logout) routes, respectively.
- **API Authorization**
For simplicity sake, generate a test token using the CLI.
```shell
auth0 test token \
--audience %IDENTIFIER% \
--scopes "read:messages"
```
✋ Substitute %IDENTIFIER% with the identifier of the API you created in step 3 above.
Now you can send requests to the `/api` endpoints of the application, including the token as a header.
```shell
curl --request GET \
--url http://localhost:8000/api/example \
--header 'Accept: application/json' \
--header 'Authorization: Bearer %TOKEN%'
```
✋ Substitute %TOKEN% with the test token returned in the previous step.
Using Windows PowerShell
```powershell
Invoke-WebRequest http://localhost:8000/api/example `
-Headers @{'Accept' = 'application/json'; 'Authorization' = 'Bearer %TOKEN%'}
```
When you're ready to deploy your application to production, review [our deployment guide](./docs/Deployment.md) for best practices and advice on securing Laravel.
## Integration Examples
User Authentication
The SDK automatically registers all the necessary routes and authentication services within the `web` middleware group of your application to enable users to authenticate without requiring you to write any code.
| Route | Purpose |
| ----------- | ---------------------------------- |
| `/login` | Initiates the authentication flow. |
| `/logout` | Logs the user out. |
| `/callback` | Handles the callback from Auth0. |
If these routes conflict with your application architecture, you can override this default behavior by [adjusting the SDK configuration](./docs/Configuration.md#route-registration).
---
Route Authorization (Access Control)
The SDK automatically registers its authentication and authorization guards within the `web` and `api` middleware groups for your Laravel application, respectively.
For `web` routes, you can use Laravel's `auth` middleware to require that a user be authenticated to access a route.
```php
Route::get('/private', function () {
return response('Welcome! You are logged in.');
})->middleware('auth');
```
For `api` routes, you can use Laravel's `auth` middleware to require that a request be authenticated with a valid bearer token to access a route.
```php
Route::get('/api/private', function () {
return response()->json(['message' => 'Hello! You included a valid token with your request.']);
})->middleware('auth');
```
In addition to requiring that a user be authenticated, you can also require that the user have specific permissions to access a route, using Laravel's `can` middleware.
```php
Route::get('/scope', function () {
return response('You have the `read:messages` permission, and can therefore access this resource.');
})->middleware('auth')->can('read:messages');
```
Permissions require that [RBAC](https://auth0.com/docs/manage-users/access-control/rbac) be enabled within [your API settings](https://manage.auth0.com/#/apis).
---
Users and Tokens
Laravel's `Auth` Facade can be used to retrieve information about the authenticated user or token associated with a request.
For routes using the `web` middleware group in `routes/web.php`.
```php
Route::get('/', function () {
if (! auth()->check()) {
return response('You are not logged in.');
}
$user = auth()->user();
$name = $user->name ?? 'User';
$email = $user->email ?? '';
return response("Hello {$name}! Your email address is {$email}.");
});
```
For routes using the `api` middleware group in `routes/api.php`.
```php
Route::get('/', function () {
if (! auth()->check()) {
return response()->json([
'message' => 'You did not provide a token.',
]);
}
return response()->json([
'message' => 'Your token is valid; you are authorized.',
'id' => auth()->id(),
'token' => auth()?->user()?->getAttributes(),
]);
});
```
---
Management API Calls
Once you've [authorized your application to make Management API calls](./docs/Management.md#api-application-authorization), you'll be able to engage nearly any of the [Auth0 Management API endpoints](https://auth0.com/docs/api/management/v2) through the SDK.
Each API endpoint has its own SDK class which can be accessed through the Facade's `management()` factory method. For interoperability, network responses from the API are returned as [PSR-7 messages](https://www.php-fig.org/psr/psr-7/). These can be converted into native arrays using the SDK's `json()` method.
For example, to update a user's metadata, you can call the `management()->users()->update()` method.
```php
use Auth0\Laravel\Facade\Auth0;
Route::get('/colors', function () {
$colors = ['red', 'blue', 'green', 'black', 'white', 'yellow', 'purple', 'orange', 'pink', 'brown'];
// Update the authenticated user with a randomly assigned favorite color.
Auth0::management()->users()->update(
id: auth()->id(),
body: [
'user_metadata' => [
'color' => $colors[random_int(0, count($colors) - 1)]
]
]
);
// Retrieve the user's updated profile.
$profile = Auth0::management()->users()->get(auth()->id());
// Convert the PSR-7 response into a native array.
$profile = Auth0::json($profile);
// Extract some values from the user's profile.
$color = $profile['user_metadata']['color'] ?? 'unknown';
$name = auth()->user()->name;
return response("Hello {$name}! Your favorite color is {$color}.");
})->middleware('auth');
```
All the SDK's Management API methods are [documented here](./docs/Management.md).
## Documentation
- [Installation](./docs/Installation.md) — Installing the SDK and generating configuration files.
- [Configuration](./docs/Configuration.md) — Configuring the SDK using JSON files or environment variables.
- [Sessions](./docs/Sessions.md) — Guidance on deciding which Laravel Session API driver to use.
- [Cookies](./docs/Cookies.md) — Important notes about using Laravel's Cookie session driver, and alternative options.
- [Management API](./docs/Management.md) — Using the SDK to work with the [Auth0 Management API](https://auth0.com/docs/api/management/v2).
- [Users](./docs/Users.md) — Extending the SDK to support persistent storage and [Eloquent](https://laravel.com/docs/eloquent) models.
- [Events](./docs/Events.md) — Hooking into SDK [events](https://laravel.com/docs/events) to respond to specific actions.
- [Deployment](./docs/Deployment.md) — Deploying your application to production.
You may find the following integration guidance useful:
- [Laravel Eloquent](./docs/Eloquent.md) — [Eloquent ORM](https://laravel.com/docs/eloquent) is supported.
- [Laravel Octane](./docs/Octane.md) — [Octane](https://laravel.com/docs/octane) is not supported at this time.
- [Laravel Telescope](./docs/Telescope.md) — [Telescope](https://laravel.com/docs/telescope) is compatible as of SDK v7.11.0.
You may also find the following resources helpful:
- [Auth0 Documentation Hub](https://www.auth0.com/docs)
- [Auth0 Management API Explorer](https://auth0.com/docs/api/management/v2)
- [Auth0 Authentication API Explorer](https://auth0.com/docs/api/authentication)
Contributions to improve our documentation [are welcomed](https://github.com/auth0/laravel-auth0/pull).
## QuickStarts
- [Session-based Authentication](https://auth0.com/docs/quickstart/webapp/laravel) ([GitHub](https://github.com/auth0-samples/laravel))
- [Token-based Authorization](https://auth0.com/docs/quickstart/backend/laravel) ([GitHub](https://github.com/auth0-samples/laravel))
## Community
The [Auth0 Community](https://community.auth0.com) is where you can get support, ask questions, and share your projects.
## Contributing
We appreciate feedback and contributions to this library. Before you get started, please review Auth0's [General Contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md).
The [Contribution Guide](./.github/CONTRIBUTING.md) contains information about our development process and expectations, insight into how to propose bug fixes and improvements, and instructions on how to build and test changes to the library.
To provide feedback or report a bug, [please raise an issue](https://github.com/auth0/laravel-auth0/issues).
## Code of Conduct
Participants are expected to adhere to Auth0's [Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md) when interacting with this project.
## Security
If you believe you have found a security vulnerability, we encourage you to responsibly disclose this and not open a public issue. We will investigate all reports. The [Responsible Disclosure Program](https://auth0.com/whitehat) details the procedure for disclosing security issues.
## License
This library is open-sourced software licensed under the [MIT license](./LICENSE.md).
---
Auth0 is an easy-to-implement, adaptable authentication and authorization platform. To learn more, check out "Why Auth0?"
================================================
FILE: UPGRADE.md
================================================
# Upgrade Guide
## v7 Migration Guide
Auth0 Laravel SDK v7 includes many significant changes over previous versions:
- Support for Laravel 9.
- Support for Auth0-PHP SDK 8.
- New authentication route controllers for plug-and-play login support.
- Improved authentication middleware for regular web applications.
- New authorization middleware for token-based backend API applications.
As expected with a major release, Auth0 Laravel SDK v7 includes breaking changes. Please review this guide thoroughly to undrstand the changes required to migrate your application to v7.
---
### Before you begin: Updated Requirements
- Laravel 8 and Laravel 9 are supported by the Auth0 Laravel SDK v7 release.
- PHP ≥7.4 is supported by the SDK when paired with Laravel 8.
- PHP ≥8.0 is supported by the SDK when paired with Laravel 9.¹
¹ This is a requirement of Laravel itself; only PHP 8+ will be supported going forward.
---
### Breaking Changes Summary
- Namespace has been updated from `Auth0\Login` to `Auth0\Laravel`.
- The Auth0-PHP SDK dependency has been updated from V7 to V8, which [may introduce breaking API changes](https://github.com/auth0/auth0-PHP/blob/main/UPGRADE.md) that will require further changes in your app outside the scope of this Laravel SDK.
- A simplified configuration file format is present. You will need to regenerate your config file. (Instructions below.)
- Changes to application files are no longer necessary, as the SDK registers services and middleware itself. You should remove any `config/app.php` or `app/HttpKernel.php` customizations made to avoid conflicts. (Instructions below.)
---
### Migration Guidance
#### Update Configuration Scheme
- Configuration filename is now `config/auth0.php`.
- Configuration format has been updated to support Auth0-PHP SDK 8.
1. Delete any previous laravel-auth0 configuration files present in your application.
2. Use `php artisan vendor:publish --tag=auth0-config` to generate an updated config file.
3. Review new configuration instructions in the [README](README.md#configuration-the-sdk).
#### Remove `config\app.php` modifications
- Previously, the SDK required you to add service provider classes to the `providers` array in this file.
- This is no longer necessary, as the SDK now registers services itself.
1. Remove any references to the SDK in your `providers` array.
#### Remove `app\Http\Kernel.php` modifications
- Previously, the SDK required you to add middleware classes to the middleware arrays in this file.
- This is no longer necessary, as the SDK now registers these itself.
1. Remove any references to the SDK in your `middleware` arrays.
2. Update any router middleware references in your app to the types instructed in the [README](README.md#protecting-routes-with-middleware).
#### Update to new authentication routes, as appropriate
Note: This only applies to regular web application types.
- Previously, the SDK required you to write boilerplate around login, logout and callback routes.
- The SDK now provides plug-and-play middleware that handles authentication flows, appropriate for most application needs.
1. Remove any route logic around login, logout or callback routes.
2. Implement the new authentication utility routes as instructed in the [README](README.md#authentication-routes).
#### Update to new `auth0.authenticate` middleware, as appropriate
Note: This only applies to regular web application types.
- Previously, the SDK advised you to register the Auth0 authentication middleware yourself in the `app\Http\Kernel.php`, which invited you to specify custom naming schemes for these middlewares.
- The SDK now provides plug-and-play middleware with specific naming schemes.
1. Update middleware references from previous custom registrations to the new scheme, as instructed in the [README](README.md#regular-web-applications-1).
#### Update to new `auth0.authorize` middleware, as appropriate
Note: This only applies to backend api application types.
- Previously, the SDK advised you to write your own Access Token handling middleware using the `decodeJWT()` method from the Auth0 PHP SDK.
- The SDK now provides plug-and-play middleware that handles common endpoint authorization, appropriate for most application needs.
1. Remove custom JWT processing or boilerplate code, particularly those referencing `decodeJWT()` from the old Auth0 PHP SDK releases.
2. Add new `middleware()` calls to your routes that reference the new SDK authorization middleware, as instructed in the [README](README.md#backend-api-applications-1).
#### Upgrade Auth0-PHP dependency from 7 to 8, as appropriate
- Previous versions of the SDK implemented v7 of the Auth0-PHP SDK dependency.
- The SDK now uses Auth0-PHP SDK v8.
If you wrote custom code around the underlying Auth0-PHP, or otherwise made internal calls to the underlying SDK through the Laravel SDK, your application will require further upgrade steps. [Please review the upgrade guide for that SDK here.](https://github.com/auth0/auth0-PHP/blob/main/UPGRADE.md)
================================================
FILE: codecov.yml
================================================
codecov:
range: "95...100"
status:
project:
patch:
changes:
ignore:
- "src/Contract"
- "src/Event"
- "src/Exception"
- "src/Http"
- "src/Model"
================================================
FILE: composer.json
================================================
{
"name": "auth0/login",
"description": "Auth0 Laravel SDK. Straight-forward and tested methods for implementing authentication, and accessing Auth0's Management API endpoints.",
"license": "MIT",
"type": "library",
"keywords": [
"laravel",
"auth0",
"authentication",
"authorization",
"login",
"auth",
"jwt",
"json web token",
"jwk",
"json web key",
"oauth",
"openid",
"secure",
"protect",
"api"
],
"authors": [
{
"name": "Auth0",
"email": "support@auth0.com",
"homepage": "https://auth0.com/"
}
],
"homepage": "https://github.com/auth0/laravel-auth0",
"support": {
"email": "support@auth0.com",
"issues": "https://github.com/auth0/laravel-auth0/issues",
"forum": "https://community.auth0.com",
"source": "https://github.com/auth0/laravel-auth0"
},
"require": {
"php": "^8.2",
"ext-json": "*",
"auth0/auth0-php": "^8.19",
"illuminate/contracts": "^11 || ^12 || ^13",
"illuminate/http": "^11 || ^12 || ^13",
"illuminate/support": "^11 || ^12 || ^13",
"psr-discovery/all": "^1",
"psr/cache": "^2 || ^3"
},
"require-dev": {
"ergebnis/composer-normalize": "^2",
"friendsofphp/php-cs-fixer": "^3",
"larastan/larastan": "^2",
"mockery/mockery": "^1",
"orchestra/testbench": "^9",
"pestphp/pest": "^2",
"pestphp/pest-plugin-laravel": "^2",
"phpstan/phpstan": "^1",
"phpstan/phpstan-strict-rules": "^1",
"psalm/plugin-laravel": "^2.12",
"psr-mock/http": "^1",
"rector/rector": "^1",
"spatie/laravel-ray": "^1.40",
"squizlabs/php_codesniffer": "^3",
"symfony/cache": "^6 || ^7",
"vimeo/psalm": "^5 || ^6 <6.5",
"wikimedia/composer-merge-plugin": "^2"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Auth0\\Laravel\\": [
"src/",
"deprecated/"
]
}
},
"autoload-dev": {
"psr-4": {
"Auth0\\Laravel\\Tests\\": "tests/"
}
},
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true,
"pestphp/pest-plugin": true,
"php-http/discovery": false,
"wikimedia/composer-merge-plugin": true
},
"optimize-autoloader": true,
"preferred-install": "dist",
"process-timeout": 0,
"sort-packages": true
},
"extra": {
"laravel": {
"aliases": {
"Auth0": "Auth0\\Laravel\\Facade\\Auth0"
},
"providers": [
"Auth0\\Laravel\\ServiceProvider"
]
},
"merge-plugin": {
"ignore-duplicates": false,
"include": [
"composer.local.json"
],
"merge-dev": true,
"merge-extra": false,
"merge-extra-deep": false,
"merge-scripts": false,
"recurse": true,
"replace": true
}
},
"scripts": {
"pest": "@php vendor/bin/pest --order-by random --fail-on-risky --parallel",
"pest:coverage": "@php vendor/bin/pest --order-by random --fail-on-risky --coverage --parallel",
"pest:debug": "@php vendor/bin/pest --log-events-verbose-text pest.log --display-errors --fail-on-risky",
"pest:profile": "@php vendor/bin/pest --profile",
"phpcs": "@php vendor/bin/php-cs-fixer fix --dry-run --diff",
"phpcs:fix": "@php vendor/bin/php-cs-fixer fix",
"phpstan": "@php vendor/bin/phpstan analyze",
"psalm": "@php vendor/bin/psalm",
"psalm:fix": "@php vendor/bin/psalter --issues=all",
"rector": "@php vendor/bin/rector process src --dry-run",
"rector:fix": "@php vendor/bin/rector process src",
"test": [
"@pest",
"@phpstan",
"@psalm",
"@rector",
"@phpcs"
]
}
}
================================================
FILE: config/auth0.php
================================================
true,
'registerMiddleware' => true,
'registerAuthenticationRoutes' => true,
'configurationPath' => null,
'guards' => [
'default' => [
Configuration::CONFIG_STRATEGY => Configuration::get(Configuration::CONFIG_STRATEGY, SdkConfiguration::STRATEGY_NONE),
Configuration::CONFIG_DOMAIN => Configuration::get(Configuration::CONFIG_DOMAIN),
Configuration::CONFIG_CUSTOM_DOMAIN => Configuration::get(Configuration::CONFIG_CUSTOM_DOMAIN),
Configuration::CONFIG_CLIENT_ID => Configuration::get(Configuration::CONFIG_CLIENT_ID),
Configuration::CONFIG_CLIENT_SECRET => Configuration::get(Configuration::CONFIG_CLIENT_SECRET),
Configuration::CONFIG_AUDIENCE => Configuration::get(Configuration::CONFIG_AUDIENCE),
Configuration::CONFIG_ORGANIZATION => Configuration::get(Configuration::CONFIG_ORGANIZATION),
Configuration::CONFIG_USE_PKCE => Configuration::get(Configuration::CONFIG_USE_PKCE),
Configuration::CONFIG_SCOPE => Configuration::get(Configuration::CONFIG_SCOPE),
Configuration::CONFIG_RESPONSE_MODE => Configuration::get(Configuration::CONFIG_RESPONSE_MODE),
Configuration::CONFIG_RESPONSE_TYPE => Configuration::get(Configuration::CONFIG_RESPONSE_TYPE),
Configuration::CONFIG_TOKEN_ALGORITHM => Configuration::get(Configuration::CONFIG_TOKEN_ALGORITHM),
Configuration::CONFIG_TOKEN_JWKS_URI => Configuration::get(Configuration::CONFIG_TOKEN_JWKS_URI),
Configuration::CONFIG_TOKEN_MAX_AGE => Configuration::get(Configuration::CONFIG_TOKEN_MAX_AGE),
Configuration::CONFIG_TOKEN_LEEWAY => Configuration::get(Configuration::CONFIG_TOKEN_LEEWAY),
Configuration::CONFIG_TOKEN_CACHE => Configuration::get(Configuration::CONFIG_TOKEN_CACHE),
Configuration::CONFIG_TOKEN_CACHE_TTL => Configuration::get(Configuration::CONFIG_TOKEN_CACHE_TTL),
Configuration::CONFIG_HTTP_MAX_RETRIES => Configuration::get(Configuration::CONFIG_HTTP_MAX_RETRIES),
Configuration::CONFIG_HTTP_TELEMETRY => Configuration::get(Configuration::CONFIG_HTTP_TELEMETRY),
Configuration::CONFIG_MANAGEMENT_TOKEN => Configuration::get(Configuration::CONFIG_MANAGEMENT_TOKEN),
Configuration::CONFIG_MANAGEMENT_TOKEN_CACHE => Configuration::get(Configuration::CONFIG_MANAGEMENT_TOKEN_CACHE),
Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_KEY => Configuration::get(Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_KEY),
Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_ALGORITHM => Configuration::get(Configuration::CONFIG_CLIENT_ASSERTION_SIGNING_ALGORITHM),
Configuration::CONFIG_PUSHED_AUTHORIZATION_REQUEST => Configuration::get(Configuration::CONFIG_PUSHED_AUTHORIZATION_REQUEST),
Configuration::CONFIG_BACKCHANNEL_LOGOUT_CACHE => Configuration::get(Configuration::CONFIG_BACKCHANNEL_LOGOUT_CACHE),
Configuration::CONFIG_BACKCHANNEL_LOGOUT_EXPIRES => Configuration::get(Configuration::CONFIG_BACKCHANNEL_LOGOUT_EXPIRES),
],
'api' => [
Configuration::CONFIG_STRATEGY => SdkConfiguration::STRATEGY_API,
],
'web' => [
Configuration::CONFIG_STRATEGY => SdkConfiguration::STRATEGY_REGULAR,
Configuration::CONFIG_COOKIE_SECRET => Configuration::get(Configuration::CONFIG_COOKIE_SECRET, env('APP_KEY')),
Configuration::CONFIG_REDIRECT_URI => Configuration::get(Configuration::CONFIG_REDIRECT_URI, env('APP_URL') . '/callback'),
Configuration::CONFIG_SESSION_STORAGE => Configuration::get(Configuration::CONFIG_SESSION_STORAGE),
Configuration::CONFIG_SESSION_STORAGE_ID => Configuration::get(Configuration::CONFIG_SESSION_STORAGE_ID),
Configuration::CONFIG_TRANSIENT_STORAGE => Configuration::get(Configuration::CONFIG_TRANSIENT_STORAGE),
Configuration::CONFIG_TRANSIENT_STORAGE_ID => Configuration::get(Configuration::CONFIG_TRANSIENT_STORAGE_ID),
],
],
'routes' => [
Configuration::CONFIG_ROUTE_INDEX => Configuration::get(Configuration::CONFIG_ROUTE_INDEX, '/'),
Configuration::CONFIG_ROUTE_CALLBACK => Configuration::get(Configuration::CONFIG_ROUTE_CALLBACK, '/callback'),
Configuration::CONFIG_ROUTE_LOGIN => Configuration::get(Configuration::CONFIG_ROUTE_LOGIN, '/login'),
Configuration::CONFIG_ROUTE_AFTER_LOGIN => Configuration::get(Configuration::CONFIG_ROUTE_AFTER_LOGIN, '/'),
Configuration::CONFIG_ROUTE_LOGOUT => Configuration::get(Configuration::CONFIG_ROUTE_LOGOUT, '/logout'),
Configuration::CONFIG_ROUTE_AFTER_LOGOUT => Configuration::get(Configuration::CONFIG_ROUTE_AFTER_LOGOUT, '/'),
],
];
================================================
FILE: deprecated/Cache/LaravelCacheItem.php
================================================
expiration = match (true) {
null === $time => new DateTimeImmutable('now +1 year'),
is_int($time) => new DateTimeImmutable('now +' . (string) $time . ' seconds'),
$time instanceof DateInterval => (new DateTimeImmutable())->add($time),
};
return $this;
}
public function expiresAt(?DateTimeInterface $expiration): static
{
$this->expiration = $expiration ?? new DateTimeImmutable('now +1 year');
return $this;
}
public function set(mixed $value): static
{
$this->value = $value;
return $this;
}
public static function miss(string $key): self
{
return new self(
key: $key,
value: null,
hit: false,
);
}
}
================================================
FILE: deprecated/Cache/LaravelCachePool.php
================================================
has('logout_token')) {
app('auth0')->handleBackchannelLogout($request->string('logout_token', '')->trim());
}
});
```
2. **Configure your Auth0 tenant to use Backchannel Logout.** See the [Auth0 documentation](https://auth0.com/docs/authenticate/login/logout/back-channel-logout/configure-back-channel-logout) for more information on how to do this. Please ensure you point the Logout URI to the backchannel route we just added to your application.
Note: If your application's configuration assigns `false` to the `backchannelLogoutCache` SDK configuration property, this feature will be disabled entirely.
================================================
FILE: docs/Configuration.md
================================================
# Configuration
- [SDK Configuration](#sdk-configuration)
- [JSON Configuration Files](#json-configuration-files)
- [Environment Variables](#environment-variables)
- [Order of Priority](#order-of-priority)
- [Default Behavior](#default-behavior)
- [Guard Registration](#guard-registration)
- [Middleware Registration](#middleware-registration)
- [Route Registration](#route-registration)
- [Auth0 Configuration](#auth0-configuration)
- [Auth0 Applications](#auth0-applications)
- [Creating Applications with the CLI](#creating-applications-with-the-cli)
- [Creating Applications Manually](#creating-applications-manually)
- [Modifying Applications with the CLI](#modifying-applications-using-the-cli)
- [Modifying Applications Manually](#modifying-applications-manually)
- [Auth0 APIs](#auth0-apis)
- [Creating APIs with the CLI](#creating-apis-with-the-cli)
- [Creating APIs Manually](#creating-apis-manually)
- [Modifying APIs with the CLI](#modifying-apis-using-the-cli)
- [Modifying APIs Manually](#modifying-apis-manually)
## SDK Configuration
This guide addresses v2 of the SDK configuration format. You can determine which version you are using by evaluating the constant prepended to the returned array in your application's `config/auth0.php` file, prefixed with `Configuration::VERSION_`. For example:
```php
return Configuration::VERSION_2 + [
// ...
];
```
If you do not see such a value, you are most likely using an outdated configuration format, and should upgrade by running `php artisan vendor:publish --tag auth0 --force` from your project directory. You will lose any alterations you have made to this file in the process.
### JSON Configuration Files
The preferred method of SDK configuration is to use JSON exported from the [Auth0 CLI](https://auth0.com/docs/cli). This allows you to use the CLI to manage your Auth0 configuration, and then export the configuration to JSON for use by the SDK.
The SDK will look for the following files in the project directory, in the order listed:
- `auth0.json`
- `auth0..json`
- `auth0.api.json`
- `auth0.app.json`
- `auth0.api..json`
- `auth0.app..json`
Where `` is the value of Laravel's `APP_ENV` environment variable (if set.) Duplicate keys in the files listed above will be overwritten in the order listed.
### Environment Variables
The SDK also supports configuration using environment variables. These can be defined within the host environment, or using so-called dotenv (`.env`, or `.env.*`) files in the project directory.
| Variable | Description |
| --------------------- | ---------------------------------------------------------------------------------------------------- |
| `AUTH0_DOMAIN` | `String (FQDN)` The Auth0 domain for your tenant. |
| `AUTH0_CUSTOM_DOMAIN` | `String (FQDN)` The Auth0 custom domain for your tenant, if set. |
| `AUTH0_CLIENT_ID` | `String` The Client ID for your Auth0 application. |
| `AUTH0_CLIENT_SECRET` | `String` The Client Secret for your Auth0 application. |
| `AUTH0_AUDIENCE` | `String (comma-delimited list)` The audiences for your application. |
| `AUTH0_SCOPE` | `String (comma-delimited list)` The scopes for your application. Defaults to 'openid,profile,email'. |
| `AUTH0_ORGANIZATION` | `String (comma-delimited list)` The organizations for your application. |
The following environment variables are supported, but should not be adjusted unless you know what you are doing:
| Variable | Description |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AUTH0_USE_PKCE` | Boolean. Whether to use PKCE for the authorization flow. Defaults to `true`. |
| `AUTH0_RESPONSE_MODE` | `String` The response mode to use for the authorization flow. Defaults to `query`. |
| `AUTH0_RESPONSE_TYPE` | `String` The response type to use for the authorization flow. Defaults to `code`. |
| `AUTH0_TOKEN_ALGORITHM` | `String` The algorithm to use for the ID token. Defaults to `RS256`. |
| `AUTH0_TOKEN_JWKS_URI` | `String (URL)` The URI to use to retrieve the JWKS for the ID token. Defaults to `https:///.well-known/jwks.json`. |
| `AUTH0_TOKEN_MAX_AGE` | `Integer` The maximum age of a token, in seconds. No default value is assigned. |
| `AUTH0_TOKEN_LEEWAY` | `Integer` The leeway to use when validating a token, in seconds. Defaults to `60` (1 minute). |
| `AUTH0_TOKEN_CACHE` | `String (class name)` A PSR-6 class to use for caching JWKS responses. |
| `AUTH0_TOKEN_CACHE_TTL` | `Integer` The TTL to use for caching JWKS responses. Defaults to `60` (1 minute). |
| `AUTH0_HTTP_MAX_RETRIES` | `Integer` The maximum number of times to retry a failed HTTP request. Defaults to `3`. |
| `AUTH0_HTTP_TELEMETRY` | `Boolean` Whether to send telemetry data with HTTP requests to Auth0. Defaults to `true`. |
| `AUTH0_SESSION_STORAGE` | `String (class name)` The `StoreInterface` class to use for storing session data. Defaults to using Laravel's native Sessions API. |
| `AUTH0_SESSION_STORAGE_ID` | `String` The namespace to use for storing session data. Defaults to `auth0_session`. |
| `AUTH0_TRANSIENT_STORAGE` | `String (class name)` The `StoreInterface` class to use for storing temporary session data. Defaults to using Laravel's native Sessions API. |
| `AUTH0_TRANSIENT_STORAGE_ID` | `String` The namespace to use for storing temporary session data. Defaults to `auth0_transient`. |
| `AUTH0_MANAGEMENT_TOKEN` | `String` The Management API token to use for the Management API client. If one is not provided, the SDK will attempt to create one for you. |
| `AUTH0_MANAGEMENT_TOKEN_CACHE` | `Integer` A PSR-6 class to use for caching Management API tokens. |
| `AUTH0_CLIENT_ASSERTION_SIGNING_KEY` | `String` The key to use for signing client assertions. |
| `AUTH0_CLIENT_ASSERTION_SIGNING_ALGORITHM` | `String` The algorithm to use for signing client assertions. Defaults to `RS256`. |
| `AUTH0_PUSHED_AUTHORIZATION_REQUEST` | `Boolean` Whether the SDK should use Pushed Authorization Requests during authentication. Note that your tenant must have this feature enabled. Defaults to `false`. |
| `AUTH0_BACKCHANNEL_LOGOUT_CACHE` | `String (class name)` A PSR-6 class to use for caching backchannel logout tokens. |
| `AUTH0_BACKCHANNEL_LOGOUT_EXPIRES` | `Integer` How long (in seconds) to cache a backchannel logout token. Defaults to `2592000` (30 days). |
### Order of Priority
The SDK collects configuration data from multiple potential sources, in the following order:
- `.auth0.json` files
- `.env` (dotenv) files
- Host environment variables
> **Note:**
> In the filenames listed below, `%APP_ENV%` is replaced by the application's configured `APP_ENV` environment variable, if one is set.
It begins by loading matching JSON configuration files from the project's root directory, in the following order:
- `.auth0.json`
- `.auth0.%APP_ENV%.json`
- `.auth0.api.json`
- `.auth0.app.json`
- `.auth0.api.%APP_ENV%.json`
- `.auth0.app.%APP_ENV%.json`
It then loads configuration data from available `.env` (dotenv) configuration files, in the following order.
- `.env`
- `.env.auth0`
- `.env.%APP_ENV%`
- `.env.%APP_ENV%.auth0`
Finally, it loads environment variables from the host environment.
Duplicate configuration data is overwritten by the value from the last source loaded. For example, if the `AUTH0_DOMAIN` environment variable is set in both the `.env` file and the host environment, the value from the host environment will be used.
Although JSON configuration keys are different from their associated environment variable counterparts, these are translated automatically by the SDK. For example, the `domain` key in the JSON configuration files is translated to the `AUTH0_DOMAIN` environment variable.
### Default Behavior
#### Guard Registration
By default, the SDK will register the Authentication and Authorization guards with your Laravel application, as well as a compatible [User Provider](./Users.md).
You can disable this behavior by setting `registerGuards` to `false` in your `config/auth0.php` file.
```php
return Configuration::VERSION_2 + [
'registerGuards' => false,
// ...
];
```
To register the guards manually, update the arrays in your `config/auth.php` file to include the following additions:
```php
'guards' => [
'auth0-session' => [
'driver' => 'auth0.authenticator',
'provider' => 'auth0-provider',
'configuration' => 'web',
],
'auth0-api' => [
'driver' => 'auth0.authorizer',
'provider' => 'auth0-provider',
'configuration' => 'api',
],
],
'providers' => [
'auth0-provider' => [
'driver' => 'auth0.provider',
'repository' => 'auth0.repository',
],
],
```
#### Middleware Registration
By default, the SDK will register the Authentication and Authorization guards within your application's `web` and `api` middleware groups.
You can disable this behavior by setting `registerMiddleware` to `false` in your `config/auth0.php` file.
```php
return Configuration::VERSION_2 + [
'registerMiddleware' => false,
// ...
];
```
To register the middleware manually, update your `app/Http/Kernel.php` file and include the following additions:
```php
protected $middlewareGroups = [
'web' => [
\Auth0\Laravel\Middleware\AuthenticatorMiddleware::class,
// ...
],
'api' => [
\Auth0\Laravel\Middleware\AuthorizerMiddleware::class,
// ...
],
];
```
Alternatively, you can assign the guards to specific routes by using the `Auth` facade. For `routes/web.php`, add the following before any routes:
```php
Auth::shouldUse('auth0-session');
```
For `routes/api.php`, add the following before any routes:
```php
Auth::shouldUse('auth0-api');
```
#### Route Registration
By default, the SDK will register the following routes for authentication:
| Method | URI | Name | Controller | Purpose |
| ------ | ----------- | ---------- | ---------------------------------------------- | ---------------------------------- |
| `GET` | `/login` | `login` | `Auth0\Laravel\Controllers\LoginController` | Initiates the authentication flow. |
| `GET` | `/logout` | `logout` | `Auth0\Laravel\Controllers\LogoutController` | Logs the user out. |
| `GET` | `/callback` | `callback` | `Auth0\Laravel\Controllers\CallbackController` | Handles the callback from Auth0. |
You can disable this behavior by setting `registerAuthenticationRoutes` to `false` in your `config/auth0.php` file.
```php
return Configuration::VERSION_2 + [
'registerAuthenticationRoutes' => false,
// ...
];
```
If you've disabled the automatic registration of routes, you must register the routes manually for authentication to work.
```php
use Auth0\Laravel\Controllers\{LoginController, LogoutController, CallbackController};
Route::group(['middleware' => ['guard:auth0-session']], static function (): void {
Route::get('/login', LoginController::class)->name('login');
Route::get('/logout', LogoutController::class)->name('logout');
Route::get('/callback', CallbackController::class)->name('callback');
});
```
Or you can call the SDK Facade's `routes()` method in your `routes/web.php` file:
```php
Auth0::routes();
```
- These must be registered within the `web` middleware group, as they rely on sessions.
- Requests must be routed through the SDK's Authenticator guard.
## Auth0 Configuration
The following guidance is provided to help you configure your Auth0 tenant for use with the SDK. It is not intended to be a comprehensive guide to configuring Auth0. Please refer to the [Auth0 documentation](https://auth0.com/docs) for more information.
### Auth0 Applications
#### Creating Applications with the CLI
Use the CLI's `apps create` command to create a new Auth0 Application:
```shell
auth0 apps create \
--name "My Laravel Application" \
--type "regular" \
--auth-method "post" \
--callbacks "http://localhost:8000/callback" \
--logout-urls "http://localhost:8000" \
--reveal-secrets \
--no-input
```
If you are configuring the SDK for this application, make note of the `client_id` and `client_secret` values returned by the command. Follow the guidance in the [configuration guide](#configuration) to configure the SDK using these values.
The following parameters used in this example are of note:
- `--type` - The [application type](https://auth0.com/docs/get-started/applications).
- For Laravel applications, this should always be set to `regular`.
- `--auth-method` - This represents the 'Token Endpoint Authentication Method' used for authentication.
- For Laravel applications, this should always be set to `post`.
- `--callbacks` - The callback URLs to use for authentication.
- In development, this should be set to `http://localhost:8000/callback` or as appropriate.
- In production, adjust this value to match your application's Internet-accessible URL for its`/callback`` route.
- This value can be a comma-separated list of URLs.
- `--logout-urls` - The logout URLs to use for authentication.
- In development, this should be set to `http://localhost:8000` or as appropriate.
- In production, adjust this value to match where your application redirects end users after logging out. The value should be an Internet-accessible URL.
- This value can be a comma-separated list of URLs.
Please refer to the [CLI documentation](https://auth0.github.io/auth0-cli/auth0_apps_create.html) for additional information on the `apps create` command.
#### Modifying Applications using the CLI
Use the CLI's `apps update` command to create a new Auth0 API:
```shell
auth0 apps update %CLIENT_ID% \
--name "My Updated Laravel Application" \
--callbacks "https://production/callback,http://localhost:8000/callback" \
--logout-urls "https://production/logout,http://localhost:8000" \
--no-input
```
Substitute `%CLIENT_ID%` with your application's Client ID. Depending on how you configured the SDK, this value can be found:
- In the `.auth0.app.json` file in your project's root directory, as the `client_id` property value.
- In the `.env` file in your project's root directory, as the `AUTH0_CLIENT_ID` property value.
- As the `AUTH0_CLIENT_ID` environment variable.
- Evaluating the output from the CLI's `apps list` command.
Please refer to the [CLI documentation](https://auth0.github.io/auth0-cli/auth0_apps_update.html) for additional information on the `apps update` command.
#### Creating Applications Manually
1. Log in to your [Auth0 Dashboard](https://manage.auth0.com/).
2. Click the **Applications** menu item in the left navigation bar.
3. Click the **Create Application** button.
4. Enter a name for your application.
5. Select **Regular Web Applications** as the application type.
6. Click the **Create** button.
7. Click the **Settings** tab.
8. Set the **Token Endpoint Authentication Method** to `POST`.
9. Set the **Allowed Callback URLs** to `http://localhost:8000/callback` or as appropriate.
10. Set the **Allowed Logout URLs** to `http://localhost:8000` or as appropriate.
11. Click the **Save Changes** button.
#### Modifying Applications Manually
1. Log in to your [Auth0 Dashboard](https://manage.auth0.com/).
2. Click the **Applications** menu item in the left navigation bar.
3. Click the name of the application you wish to modify.
4. Click the **Settings** tab.
5. Modify the properties you wish to update as appropriate.
6. Click the **Save Changes** button.
### Auth0 APIs
#### Creating APIs with the CLI
Use the CLI's `apis create` command to create a new Auth0 API:
```shell
auth0 apis create \
--name "My Laravel Application API" \
--identifier "https://github.com/auth0/laravel-auth0" \
--offline-access \
--no-input
```
If you are configuring the SDK for this API, make note of the `identifier` you used here. Follow the guidance in the [configuration guide](#configuration) to configure the SDK using this value.
The following parameters are of note:
- `--identifier` - The [unique identifier](https://auth0.com/docs/get-started/apis/api-settings#general-settings) for your API, sometimes referred to as the `audience`. This can be any value you wish, but it must be unique within your account. It cannot be changed later.
- `--offline-access` - This enables the use of [Refresh Tokens](https://auth0.com/docs/tokens/refresh-tokens) for your API. This is not required for the SDK to function.
Please refer to the [CLI documentation](https://auth0.github.io/auth0-cli/auth0_apis_create.html) for additional information on the `apis create` command.
#### Modifying APIs using the CLI
Use the CLI's `apis update` command to create a new Auth0 API:
```shell
auth0 apis update %IDENTIFIER% \
--name "My Updated Laravel Application API" \
--token-lifetime 6100 \
--offline-access=false \
--scopes "letter:write,letter:read" \
--no-input
```
Substitute `%IDENTIFIER%` with your API's unique identifier.
Please refer to the [CLI documentation](https://auth0.github.io/auth0-cli/auth0_apis_update.html) for additional information on the `apis update` command.
#### Creating APIs Manually
1. Log in to your [Auth0 Dashboard](https://manage.auth0.com/).
2. Click the **APIs** menu item in the left navigation bar.
3. Click the **Create API** button.
4. Enter a name for your API.
5. Enter a unique identifier for your API. This can be any value you wish, but it must be unique within your account. It cannot be changed later.
6. Click the **Create** button.
#### Modifying APIs Manually
1. Log in to your [Auth0 Dashboard](https://manage.auth0.com/).
2. Click the **APIs** menu item in the left navigation bar.
3. Click the name of the API you wish to modify.
4. Modify the properties you wish to update as appropriate.
5. Click the **Save Changes** button.
Additional information on Auth0 application settings [can be found here](https://auth0.com/docs/get-started/applications/application-settings).
================================================
FILE: docs/Cookies.md
================================================
# Cookies
We strongly recommend using the `database` or `redis` session drivers, but realize this is not always a viable option for all developers or use cases. The Auth0 Laravel SDK supports cookies for storing authentication state, but there are notable drawbacks to be aware of.
## Laravel's Cookie Session Driver
As noted in our [sessions documentation](./Sessions.md), Laravel's `cookie` session driver is not a reliable option for production applications as it suffers from a number of notable drawbacks:
- Browsers impose a size limit of 4 KB on individual cookies, which can quickly be exceeded by storing session data.
- Laravel's cookie driver unfortunately does not "chunk" (split up) larger cookies into multiple cookies, so it is impossible to store more than the noted 4 KB of total session data.
- Most web servers and load balancers require additional configuration to accept and deliver larger cookie headers.
## Auth0 PHP SDK's Custom Cookie Session Handler
The underlying [Auth0 PHP SDK](https://github.com/auth0/auth0-PHP) (which the Auth0 Laravel SDK is built upon) includes a powerful custom cookie session handler that supports chunking of larger cookies. This approach will enable you to securely and reliably store larger authentication states for your users.
It is important to note that this approach is incompatible with [Octane](./Octane.md) due to the way it delivers cookie headers.
To enable this feature, assign a `cookie` string value to the `AUTH0_SESSION_STORAGE` and `AUTH0_TRANSIENT_STORAGE` environment variables (or your `.env` file.)
```ini
# Persistent session data:
AUTH0_SESSION_STORAGE=cookie
# Temporary session data (used only during authentication):
AUTH0_TRANSIENT_STORAGE=cookie
```
This will override the SDK's default behavior of using the Laravel Sessions API, and instead use the integrated Auth0 PHP SDK's custom cookie session handler. Please note:
- When this feature is enabled, all properties of cookie storage (like `sameSite`, `secure`, and so forth) must be configured independently. This approach does not use Laravel's settings. Please refer to the [Auth0 PHP SDK's documentation](https://github.com/auth0/auth0-PHP) for guidance on how to configure these.
- By default your Laravel application's `APP_KEY` will be used to encrypt the cookie data. You can change this by assigning the `AUTH0_COOKIE_SECRET` environment variable (or your `.env` file) a string. If you do this, please ensure you are using an adequately long secure secret.
- Please ensure your server is configured to deliver and accept cookies prefixed with `auth0_session_` and `auth0_transient_` followed by a series of numbers (beginning with 0). These are the divided content body of the authenticated session data.
### Increasing Server Cookies Header Sizes
You may need to configure your web server or load balancer to accept and deliver larger cookie headers. For example, if you are using Nginx you will need to set the `large_client_header_buffers` directive to a value greater than the default of 4 KB.
```nginx
large_client_header_buffers 4 16k;
```
Please refer to your web server or load balancer's documentation for more information.
### Reminder on Octane Compatibility
As noted above, the Auth0 PHP SDK's custom cookie session handler is incompatible with [Octane](./Octane.md) due to the way it delivers cookie headers. If you are using Octane, you must use the Laravel Sessions API with a `database` or `redis` driver.
================================================
FILE: docs/Deployment.md
================================================
# Deployment
When you're preparing to deploy your application to production, there are some basic steps you can take to make sure your application is running as smoothly and securely as possible. In this guide, we'll cover some starting points for making sure your application is deployed properly.
- [Auth0 Configuration](#auth0-configuration)
- [TLS / HTTPS](#tls--https)
- [Cookies](#cookies)
- [Server Configuration](#server-configuration)
- [Caddy](#caddy)
- [Nginx](#nginx)
- [Apache](#apache)
- [Optimization](#optimization)
- [Autoloader](#autoloader)
- [Dependencies](#dependencies)
- [Caching Configuration](#caching-configuration)
- [Caching Events](#caching-events)
- [Caching Routes](#caching-routes)
- [Caching Views](#caching-views)
- [Debug Mode](#debug-mode)
## Auth0 Configuration
When migrating your Laravel application from local development to production, you will need to update your Auth0 application's configuration to reflect the new URLs for your application. You can do this by logging into the [Auth0 Dashboard](https://manage.auth0.com/) and updating the following fields:
- **Allowed Callback URLs**: The URL that Auth0 will redirect to after the user authenticates. This should be set to the Internet-accessible URL of your application's `/callback` route.
- **Allowed Logout URLs**: The URL that Auth0 will redirect to after the user logs out. This should be set to an appropriate Internet-accessible URL of your application.
Note that you can include multiple URLs in these fields by separating them with commas, for example `https://example.com/callback,http://localhost:8000/callback`.
See [the configuration guide](/docs/configuration.md) for additional guidance on updating configuration properties.
## TLS / HTTPS
Auth0 requires that all applications use TLS/HTTPS. This is a requirement for all applications, regardless of whether they are running in production or development, with the exception of applications running on `localhost`. If you are running your application in a development environment, you can use a self-signed certificate. However, you should ensure that your application is running over TLS/HTTPS in production.
Let's Encrypt is a great option for obtaining free TLS/HTTPS certificates for your application. You can find instructions for obtaining a certificate for your server at [https://letsencrypt.org/getting-started/](https://letsencrypt.org/getting-started/).
## Cookies
Depending on the integration approach, you may encounter instances where the cookies delivered by the application exceed the default allowances of your web server. This can result in errors such as `400 Bad Request`. If you encounter this issue, you should increase the header size limits of your web server to accommodate the larger cookies. The server configurations below include examples of how to do this for common web servers.
You should also ensure your application's `config/session.php` file is configured securely. The default configuration provided by Laravel is a great starting point, but you should double check that the `secure` option is set to `true`, that the `same_site` option is set to `lax` or `strict`, and the `http_only` option is set to `true`.
## Server Configuration
Please ensure, like all the example configurations provided below, that your web server directs all requests to your application's `public/index.php` file. You should **never** attempt to move the `index.php` file to your project's root, as serving the application from the project root will expose many sensitive configuration files to the public Internet.
### Caddy
```nginx
example.com {
root * /var/www/example.com/public
encode zstd gzip
file_server
limits {
header 4kb
}
header {
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
}
php_fastcgi unix//var/run/php/php8.2-fpm.sock
}
```
### Nginx
```nginx
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/example.com/public;
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "SAMEORIGIN";
large_client_header_buffers 4 32k;
index index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
```
### Apache
```apache
ServerName example.com
ServerAdmin admin@example.com
DocumentRoot /var/www/html/example.com/public
LimitRequestFieldSize 16384
AllowOverride All
Header set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options SAMEORIGIN
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
```
## Optimization
### Autoloader
When deploying to production, make sure that you are optimizing Composer's class autoloader map so Composer can quickly find the proper file to load for a given class:
```shell
composer install --optimize-autoloader --no-dev
```
Be sure to use the `--no-dev` option in production. This will prevent Composer from installing any development dependencies your project's dependencies may have.
### Dependencies
You should include your `composer.lock` file in your project's source control repository. Fo project's dependencies can be installed much faster with this file is present. Your production environment does not run `composer update` directly. Instead, you can run the `composer update` command locally when you want to update your dependencies, and then commit the updated `composer.lock` file to your repository. Be sure you are running the same major PHP version as your production environment, to avoid introducing compatibility issues.
Because the `composer.lock` file includes specific versions of your dependencies, other developers on your team will be using the same versions of the dependencies as you. This will help prevent bugs and compatibility issues from appearing in production that aren't present during development.
### Caching Configuration
When deploying your application to production, you should make sure that you run the config:cache Artisan command during your deployment process:
```shell
php artisan config:cache
```
This command will combine all of Laravel's configuration files into a single, cached file, which greatly reduces the number of trips the framework must make to the filesystem when loading your configuration values.
### Caching Events
If your application is utilizing event discovery, you should cache your application's event to listener mappings during your deployment process. This can be accomplished by invoking the event:cache Artisan command during deployment:
```shell
php artisan event:cache
```
### Caching Routes
If you are building a large application with many routes, you should make sure that you are running the route:cache Artisan command during your deployment process:
```shell
php artisan route:cache
```
This command reduces all of your route registrations into a single method call within a cached file, improving the performance of route registration when registering hundreds of routes.
### Caching Views
When deploying your application to production, you should make sure that you run the view:cache Artisan command during your deployment process:
```shell
php artisan view:cache
```
This command precompiles all your Blade views so they are not compiled on demand, improving the performance of each request that returns a view.
## Debug Mode
The debug option in your `config/app.php` configuration file determines how much information about an error is actually displayed to the user. By default, this option is set to respect the value of the `APP_DEBUG` environment variable, which is stored in your application's .env file.
**In your production environment, this value should always be `false`. If the `APP_DEBUG` variable is set to `true` in production, you risk exposing sensitive configuration values to your application's end users.**
================================================
FILE: docs/Eloquent.md
================================================
# Laravel Eloquent
By default, the SDK does not include any Eloquent models or database support. You can adapt the SDK to your application's needs by adding your own Eloquent models and database support.
## Creating a User Model
Begin by creating a new Eloquent model class. You can use the `make:model` Artisan command to do this. Laravel ships with default user model named `User`, so we'll use the `--force` flag to overwrite it with our custom one.
Please ensure you have a backup of your existing `User` model before running this command, as it will overwrite your existing model.
```bash
php artisan make:model User --force
```
Next, open your `app/Models/User.php` file and modify it match the following example. Notably, we'll add a support for a new `auth0` attribute. This attribute will be used to store the user's Auth0 ID, which is used to uniquely identify the user in Auth0.
```php
'datetime',
];
}
```
Next, create a migration to update your application's `users` table schema to support these changes. Create a new migration file:
```bash
php artisan make:migration update_users_table --table=users
```
Openly the newly created migration file (found under `database/migrations` and ending in `update_users_table.php`) and update to match the following example:
```php
string('auth0')->nullable();
$table->boolean('email_verified')->default(false);
$table->unique('auth0', 'users_auth0_unique');
});
}
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropUnique('users_auth0_unique');
$table->dropColumn('auth0');
$table->dropColumn('email_verified');
});
}
};
```
Now run the migration:
```bash
php artisan migrate
```
## Creating a User Repository
You'll need to create a new user repository class to handle the creation and retrieval of your Eloquent user models from your database table.
Create a new repository class in your application at `app/Repositories/UserRepository.php`, and update it to match the following example:
```php
"https://example.auth0.com/",
"aud" => "https://api.example.com/calendar/v1/",
"sub" => "auth0|123456",
"exp" => 1458872196,
"iat" => 1458785796,
"scope" => "read write",
];
*/
$identifier = $user['sub'] ?? $user['auth0'] ?? null;
if (null === $identifier) {
return null;
}
return User::where('auth0', $identifier)->first();
}
public function fromSession(array $user): ?Authenticatable
{
/*
$user = [ // Example of a decoded ID token
"iss" => "http://example.auth0.com",
"aud" => "client_id",
"sub" => "auth0|123456",
"exp" => 1458872196,
"iat" => 1458785796,
"name" => "Jane Doe",
"email" => "janedoe@example.com",
];
*/
// Determine the Auth0 identifier for the user from the $user array.
$identifier = $user['sub'] ?? $user['auth0'] ?? null;
// Collect relevant user profile information from the $user array for use later.
$profile = [
'auth0' => $identifier,
'name' => $user['name'] ?? '',
'email' => $user['email'] ?? '',
'email_verified' => in_array($user['email_verified'], [1, true], true),
];
// Check if a cache of the user exists in memory to avoid unnecessary database queries.
$cached = $this->withoutRecording(fn () => Cache::get('auth0_user_' . $identifier));
if ($cached) {
// Immediately return a cached user if one exists.
return $cached;
}
$user = null;
// Check if the user exists in the database by Auth0 identifier.
if (null !== $identifier) {
$user = User::where('auth0', $identifier)->first();
}
// Optional: if the user does not exist in the database by Auth0 identifier, you could fallback to matching by email.
if (null === $user && isset($user['email'])) {
$user = User::where('email', $user['email'])->first();
}
// If a user was found, check if any updates to the local record are required.
if (null !== $user) {
$updates = [];
if ($user->auth0 !== $profile['auth0']) {
$updates['auth0'] = $profile['auth0'];
}
if ($user->name !== $profile['name']) {
$updates['name'] = $profile['name'];
}
if ($user->email !== $profile['email']) {
$updates['email'] = $profile['email'];
}
$emailVerified = in_array($user->email_verified, [1, true], true);
if ($emailVerified !== $profile['email_verified']) {
$updates['email_verified'] = $profile['email_verified'];
}
if ([] !== $updates) {
$user->update($updates);
$user->save();
}
if ([] === $updates && null !== $cached) {
return $user;
}
}
if (null === $user) {
// Local password column is not necessary or used by Auth0 authentication, but may be expected by some applications/packages.
$profile['password'] = Hash::make(Str::random(32));
// Create the user.
$user = User::create($profile);
}
// Cache the user for 30 seconds.
$this->withoutRecording(fn () => Cache::put('auth0_user_' . $identifier, $user, 30));
return $user;
}
/**
* Workaround for Laravel Telescope potentially causing an infinite loop.
* @link https://github.com/auth0/laravel-auth0/tree/main/docs/Telescope.md
*
* @param callable $callback
*/
private function withoutRecording($callback): mixed
{
$telescope = '\Laravel\Telescope\Telescope';
if (class_exists($telescope)) {
return "$telescope"::withoutRecording($callback);
}
return call_user_func($callback);
}
}
```
Finally, update your application's `config/auth.php` file to configure the SDK to query your new user provider during authentication requests.
```php
'providers' => [
'auth0-provider' => [
'driver' => 'auth0.provider',
'repository' => \App\Repositories\UserRepository::class,
],
],
```
================================================
FILE: docs/Events.md
================================================
# Events
- [Introduction](#introduction)
- [SDK Controller Events](#sdk-controller-events)
- [Login Events](#login-events)
- [Callback Events](#callback-events)
- [Logout Events](#logout-events)
- [Deprecated SDK Events](#deprecated-sdk-events)
- [Authentication Middleware Events](#authentication-middleware-events)
- [Authorization Middleware Events](#authorization-middleware-events)
## Introduction
Your application can listen to events raised by the SDK, and respond to them if desired. For example, you might want to log the user's information to a database when they log in.
To listen for these events, you must first create a listener class within your application. These usually live in your `app/Listeners` directory. The following example shows how to listen for the `Illuminate\Auth\Events\Login` event:
```php
namespace App\Listeners;
use Illuminate\Auth\Events\Login;
final class LogSuccessfulLogin
{
public function handle(Login $event): void
{
// Log the event to a database.
}
}
```
You should also register your event listeners in your application's `app/Providers/EventServiceProvider.php` file, for example:
```php
use Illuminate\Auth\Events\Login;
use App\Listeners\LogSuccessfulLogin;
use Illuminate\Support\Facades\Event;
public function boot(): void
{
Event::listen(
Login::class, // The event class.
[LogSuccessfulLogin::class, 'handle'] // Your listener class and method.
);
}
```
You can learn more about working with the Laravel event system in the [Laravel documentation](https://laravel.com/docs/events).
## SDK Controller Events
### Login Events
During user authentication triggered by `Auth0\Laravel\Controllers\LoginController` (the `/login` route, by default) the following events may be raised:
| Event | Description |
| -------------------------------------- | -------------------------------------------------------------------------------------------- |
| `Illuminate\Auth\Events\Login` | Raised when a user is logging in. The model of the user is provided with the event. |
| `Auth0\Laravel\Events\LoginAttempting` | Raised before the login redirect is issued, allowing an opportunity to customize parameters. |
### Callback Events
During user authentication callback triggered by `Auth0\Laravel\Controllers\CallbackController` (the `/callback` route, by default) the following events may be raised:
| Event | Description |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Illuminate\Auth\Events\Attempting` | Raised when a user is returned to the application after authenticating with Auth0. This is raised before verification of the authentication process begins. |
| `Illuminate\Auth\Events\Failed` | Raised when authentication with Auth0 failed. The reason is provided with the event as an array. |
| `Auth0\Laravel\Events\AuthenticationFailed` | Raised when authentication with Auth0 failed. This provides an opportunity to intercept the exception thrown by the middleware, by using the event's `setThrowException()` method to `false`. You can also customize the type of exception thrown using `setException()`. |
| `Illuminate\Auth\Events\Validated` | Raised when authentication was successful, but immediately before the user's session is established. |
| `Auth0\Laravel\Events\AuthenticationSucceeded` | Raised when authentication was successful. The model of the authenticated user is provided with the event. |
### Logout Events
During user logout by `Auth0\Laravel\Controllers\LogoutController` (the `/logout` route, by default) the following events may be raised:
| Event | Description |
| ------------------------------- | ------------------------------------------------------------------------------------ |
| `Illuminate\Auth\Events\Logout` | Raised when a user is logging out. The model of the user is provided with the event. |
## Deprecated SDK Events
The following events are deprecated and will be removed in a future release. They are replaced by the events listed in the previous section.
### Authentication Middleware Events
During request handling with `Auth0\Laravel\Middleware\AuthenticateMiddleware` or `Auth0\Laravel\Middleware\AuthenticateOptionalMiddleware` the following events may be raised:
| Event | Description |
| ----------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `Auth0\Laravel\Events\Middleware\StatefulMiddlewareRequest` | Raised when a request is being handled by a session-based ('stateful') middleware. |
### Authorization Middleware Events
During request handling with `Auth0\Laravel\Middleware\AuthorizeMiddleware` or `Auth0\Laravel\Middleware\AuthorizeOptionalMiddleware` middleware, the following events may be raised:
| Event | Description |
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `Auth0\Laravel\Events\Middleware\StatelessMiddlewareRequest` | Raised when a request is being handled by an access token-based ('stateless') middleware. |
| `Auth0\Laravel\Events\TokenVerificationAttempting` | Raised before an access token is attempted to be verified. The encoded token string is provided with the event. |
| `Auth0\Laravel\Events\TokenVerificationSucceeded` | Raised when an access token is successfully verified. The decoded token contents are provided with the event. |
| `Auth0\Laravel\Events\TokenVerificationFailed` | Raised when an access token cannot be verified. The reason (as a string) is provided with the event. |
================================================
FILE: docs/Installation.md
================================================
# Installation
- [Prerequisites](#prerequisites)
- [Install the SDK](#install-the-sdk)
- [Using Quickstart (Recommended)](#using-quickstart-recommended)
- [Installation with Composer](#installation-with-composer)
- [Create a Laravel Application](#create-a-laravel-application)
- [Install the SDK](#install-the-sdk-1)
- [Install the CLI](#install-the-cli)
- [Authenticate the CLI](#authenticate-the-cli)
- [Configure the SDK](#configure-the-sdk)
- [Using JSON (Recommended)](#using-json-recommended)
- [Using Environment Variables](#using-environment-variables)
## Prerequisites
Your application must use the [latest supported Laravel version](https://endoflife.date/laravel), and your host environment must be running a [supported PHP version](https://www.php.net/supported-versions.php). Please review [our support policy](./docs/Support.md) for more information. You will also need [Composer 2.0+](https://getcomposer.org/) and an [Auth0 account](https://auth0.com/signup).
## Install the SDK
Ensure that your development environment has [supported versions](#prerequisites) of PHP and [Composer](https://getcomposer.org/) installed. If you're using macOS, PHP and Composer can be installed via [Homebrew](https://brew.sh/). It's also advisable to [install Node and NPM](https://nodejs.org/).
### Using Quickstart (Recommended)
- Create a new Laravel 9 project pre-configured with the SDK:
```shell
composer create-project auth0-samples/laravel auth0-laravel-app
```
### Installation with Composer
#### Create a Laravel Application
- If you do not already have one, you can Create a new Laravel 9 application with the following command:
```shell
composer create-project laravel/laravel:^9.0 auth0-laravel-app
```
#### Install the SDK
1. Run the following command from your project directory to install the SDK:
```shell
composer require auth0/login:^7.8 --update-with-all-dependencies
```
2. Generate an SDK configuration file for your application:
```shell
php artisan vendor:publish --tag auth0
```
## Install the CLI
Install the [Auth0 CLI](https://github.com/auth0/auth0-cli) to create and manage Auth0 resources from the command line.
- macOS with [Homebrew](https://brew.sh/):
```shell
brew tap auth0/auth0-cli && brew install auth0
```
- Linux or macOS:
```shell
curl -sSfL https://raw.githubusercontent.com/auth0/auth0-cli/main/install.sh | sh -s -- -b .
sudo mv ./auth0 /usr/local/bin
```
- Windows with [Scoop](https://scoop.sh/):
```cmd
scoop bucket add auth0 https://github.com/auth0/scoop-auth0-cli.git
scoop install auth0
```
### Authenticate the CLI
- Authenticate the CLI with your Auth0 account. Choose "as a user," and follow the prompts.
```shell
auth0 login
```
## Configure the SDK
### Using JSON (Recommended)
1. Register a new application with Auth0:
```shell
auth0 apps create \
--name "My Laravel Application" \
--type "regular" \
--auth-method "post" \
--callbacks "http://localhost:8000/callback" \
--logout-urls "http://localhost:8000" \
--reveal-secrets \
--no-input \
--json > .auth0.app.json
```
2. Register a new API with Auth0:
```shell
auth0 apis create \
--name "My Laravel Application API" \
--identifier "https://github.com/auth0/laravel-auth0" \
--offline-access \
--no-input \
--json > .auth0.api.json
```
3. Add the new files to `.gitignore`:
Linux and macOS:
```bash
echo ".auth0.*.json" >> .gitignore
```
Windows PowerShell:
```powershell
Add-Content .gitignore "`n.auth0.*.json"
```
Windows Command Prompt:
```cmd
echo .auth0.*.json >> .gitignore
```
### Using Environment Variables
1. Register a new application with Auth0:
```shell
auth0 apps create \
--name "My Laravel Application" \
--type "regular" \
--auth-method "post" \
--callbacks "http://localhost:8000/callback" \
--logout-urls "http://localhost:8000" \
--reveal-secrets \
--no-input
```
Make a note of the `client_id` and `client_secret` values in the output.
2. Register a new API with Auth0:
```shell
auth0 apis create \
--name "My Laravel Application API" \
--identifier "https://github.com/auth0/laravel-auth0" \
--offline-access \
--no-input
```
3. Open the `.env` file found inside your project directory, and add the following lines, replacing the values with the ones you noted in the previous steps:
```ini
# The Auth0 domain for your tenant (e.g. tenant.region.auth0.com):
AUTH0_DOMAIN=...
# The application `client_id` you noted above:
AUTH0_CLIENT_ID=...
# The application `client_secret` you noted above:
AUTH0_CLIENT_SECRET=...
# The API `identifier` you used above:
AUTH0_AUDIENCE=...
```
Additional configuration environment variables can be found in the [configuration guide](./Configuration.md#environment-variables).
================================================
FILE: docs/Management.md
================================================
# Management API
The Auth0 Laravel SDK provides easy-to-use methods to access Auth0's Management API endpoints. Nearly every endpoint of the Management API is available to use with your Laravel application. For more information about any of these endpoints, see the [Management API Explorer](https://auth0.com/docs/api/management/v2).
The Management API class can be accessed through the `management()` method on the Auth0 Laravel SDK service. You can pull the Auth0 SDK instance from the Laravel service container using dependency injection, or use the `Auth0` facade. Once you have an instance, you can call any of the [available endpoints](#available-endpoints).
```php
use Auth0\Laravel\Facade\Auth0;
Auth0::management();
```
## API Application Authorization
Before making Management API calls you must permit your application to communicate with the Management API. This can be done from the [Auth0 Dashboard's API page](https://manage.auth0.com/#/apis/), choosing `Auth0 Management API`, and selecting the 'Machine to Machine Applications' tab. Authorize your Laravel application, and then click the down arrow to choose the scopes you wish to grant.
## Available endpoints
- [Actions](#actions)
- [Attack Protection](#attack-protection)
- [Blacklists](#blacklists)
- [ClientGrants](#client-grants)
- [Clients](#clients)
- [Connections](#connections)
- [Device Credentials](#device-credentials)
- [Emails](#emails)
- [Email Templates](#email-templates)
- [Grants](#grants)
- [Guardian](#guardian)
- [Jobs](#jobs)
- [Logs](#logs)
- [Log Streams](#log-streams)
- [Organizations](#organizations)
- [Resource Servers](#resource-servers)
- [Roles](#roles)
- [Rules](#rules)
- [Stats](#stats)
- [Tenants](#tenants)
- [Tickets](#tickets)
- [User Blocks](#user-blocks)
- [Users](#users)
- [Users by Email](#users-by-email)
### Actions
The [/api/v2/actions](https://auth0.com/docs/api/management/v2#!/Actions) endpoint class is accessible from the `actions()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | --------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `GET` | […/actions/actions](https://auth0.com/docs/api/management/v2#!/Actions/get_actions) | `getAll()` |
| `POST` | […/actions/actions](https://auth0.com/docs/api/management/v2#!/Actions/post_action) | `create(body: [])` |
| `GET` | […/actions/actions/{id}](https://auth0.com/docs/api/management/v2#!/Actions/get_action) | `get(id: '...')` |
| `PATCH` | […/actions/actions/{id}](https://auth0.com/docs/api/management/v2#!/Actions/patch_action) | `update(id: '...', body: [])` |
| `DELETE` | […/actions/actions/{id}](https://auth0.com/docs/api/management/v2#!/Actions/delete_action) | `delete(id: '...')` |
| `POST` | […/actions/actions/{id}/test](https://auth0.com/docs/api/management/v2#!/Actions/post_test_action) | `test(id: '...')` |
| `POST` | […/actions/actions/{id}/deploy](https://auth0.com/docs/api/management/v2#!/Actions/post_deploy_action) | `deploy(id: '...')` |
| `GET` | […/actions/actions/{actionId}/versions](https://auth0.com/docs/api/management/v2#!/Actions/get_action_versions) | `getVersions(actionId: '...')` |
| `GET` | […/actions/actions/{actionId}/versions/{id}](https://auth0.com/docs/api/management/v2#!/Actions/get_action_version) | `getVersion(id: '...', actionId: '...')` |
| `POST` | […/actions/actions/{actionId}/versions/{id}/deploy](https://auth0.com/docs/api/management/v2#!/Actions/post_deploy_draft_version) | `rollbackVersion(id: '...', actionId: '...')` |
| `GET` | […/actions/executions/{id}](https://auth0.com/docs/api/management/v2#!/Actions/get_execution) | `getExecution(id: '...')` |
| `GET` | […/actions/triggers](https://auth0.com/docs/api/management/v2#!/Actions/get_triggers) | `getTriggers()` |
| `GET` | […/actions/triggers/{triggerId}/bindings](https://auth0.com/docs/api/management/v2#!/Actions/get_bindings) | `getTriggerBindings(triggerId: '...')` |
| `PATCH` | […/actions/triggers/{triggerId}/bindings](https://auth0.com/docs/api/management/v2#!/Actions/patch_bindings) | `updateTriggerBindings(triggerId: '...', body: [])` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Actions class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Actions.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$actions = $management->actions();
// Retrieves the first batch of results results.
$results = $actions->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($actions->getResponsePaginator() as $action) {
// Do something with the action.
}
```
### Attack Protection
The [/api/v2/attack-protection](https://auth0.com/docs/api/management/v2#!/Attack_Protection) endpoint class is accessible from the `attackProtection()` method on the Management API class.
| Method | Endpoint | SDK Method |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
| `GET` | […/attack-protection/breached-password-detection](https://auth0.com/docs/api/management/v2#!/Attack_Protection/get_breached_password_detection) | `getBreachedPasswordDetection()` |
| `PATCH` | […/attack-protection/breached-password-detection](https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_breached_password_detection) | `updateBreachedPasswordDetection(body: [])` |
| `GET` | […/attack-protection/brute-force-protection](https://auth0.com/docs/api/management/v2#!/Attack_Protection/get_brute_force_protection) | `getBruteForceProtection()` |
| `PATCH` | […/attack-protection/brute-force-protection](https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_brute_force_protection) | `updateBruteForceProtection(body: [])` |
| `GET` | […/attack-protection/suspicious-ip-throttling](https://auth0.com/docs/api/management/v2#!/Attack_Protection/get_suspicious_ip_throttling) | `getSuspiciousIpThrottling()` |
| `PATCH` | […/attack-protection/suspicious-ip-throttling](https://auth0.com/docs/api/management/v2#!/Attack_Protection/patch_suspicious_ip_throttling) | `updateSuspiciousIpThrottling(body: [])` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\AttackProtection class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/AttackProtection.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$attackProtection = $management->attackProtection();
// Get the current configuration.
$response = $attackProtection->getBreachedPasswordDetection();
// Print the response body.
dd(HttpResponse::decode($response));
// {
// "enabled": true,
// "shields": [
// "block",
// "admin_notification"
// ],
// "admin_notification_frequency": [
// "immediately",
// "weekly"
// ],
// "method": "standard",
// "stage": {
// "pre-user-registration": {
// "shields": [
// "block",
// "admin_notification"
// ]
// }
// }
// }
```
### Blacklists
The [/api/v2/blacklists](https://auth0.com/docs/api/management/v2#!/Blacklists) endpoint class is accessible from the `blacklists()` method on the Management API class.
| Method | Endpoint | SDK Method |
| ------ | ---------------------------------------------------------------------------------------- | -------------------- |
| `GET` | […/blacklists/tokens](https://auth0.com/docs/api/management/v2#!/Blacklists/get_tokens) | `get()` |
| `POST` | […/blacklists/tokens](https://auth0.com/docs/api/management/v2#!/Blacklists/post_tokens) | `create(jti: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Blacklists class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Blacklists.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$blacklists = $management->blacklists();
$response = $blacklists->create('some-jti');
if ($response->getStatusCode() === 201) {
// Token was successfully blacklisted.
// Retrieve all blacklisted tokens.
$results = $blacklists->get();
// You can then iterate (and auto-paginate) through all available results.
foreach ($blacklists->getResponsePaginator() as $blacklistedToken) {
// Do something with the blacklisted token.
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "aud": "...",
// "jti": "some-jti"
// }
// ]
}
```
### Client Grants
The [/api/v2/client-grants](https://auth0.com/docs/api/management/v2#!/Client_Grants) endpoint class is accessible from the `clientGrants()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| `GET` | […/client-grants](https://auth0.com/docs/api/management/v2#!/Client_Grants/get_client_grants) | `getAll()` |
| `GET` | […/client-grants](https://auth0.com/docs/api/management/v2#!/Client_Grants/get_client_grants) | `getAllByAudience(audience: '...')` |
| `GET` | […/client-grants](https://auth0.com/docs/api/management/v2#!/Client_Grants/get_client_grants) | `getAllByClientId(clientId: '...')` |
| `POST` | […/client-grants](https://auth0.com/docs/api/management/v2#!/Client_Grants/post_client_grants) | `create(clientId: '...', audience: '...')` |
| `PATCH` | […/client-grants/{id}](https://auth0.com/docs/api/management/v2#!/Client_Grants/patch_client_grants_by_id) | `update(grantId: '...')` |
| `DELETE` | […/client-grants/{id}](https://auth0.com/docs/api/management/v2#!/Client_Grants/delete_client_grants_by_id) | `delete(grantId: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\ClientGrants class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/ClientGrants.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$clientGrants = $management->clientGrants();
// Retrieves the first batch of results results.
$results = $clientGrants->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($clientGrants->getResponsePaginator() as $clientGrant) {
// Do something with the client grant.
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "",
// "client_id": "",
// "audience": "",
// "scope": [
// ""
// ]
// }
// ]
```
### Clients
The [/api/v2/clients](https://auth0.com/docs/api/management/v2#!/Clients) endpoint class is accessible from the `clients()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | ----------------------------------------------------------------------------------------- | --------------------- |
| `GET` | […/clients](https://auth0.com/docs/api/management/v2#!/Clients/get_clients) | `getAll()` |
| `POST` | […/clients](https://auth0.com/docs/api/management/v2#!/Clients/post_clients) | `create(name: '...')` |
| `GET` | […/clients/{id}](https://auth0.com/docs/api/management/v2#!/Clients/get_clients_by_id) | `get(id: '...')` |
| `PATCH` | […/clients/{id}](https://auth0.com/docs/api/management/v2#!/Clients/patch_clients_by_id) | `update(id: '...')` |
| `DELETE` | […/clients/{id}](https://auth0.com/docs/api/management/v2#!/Clients/delete_clients_by_id) | `delete(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Clients class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Clients.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$clients = $management->clients();
// Retrieves the first batch of results results.
$results = $clients->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($clients->getResponsePaginator() as $client) {
// Do something with the client.
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "client_id": "",
// "tenant": "",
// "name": "",
// ...
// }
// ]
```
### Connections
The [/api/v2/connections](https://auth0.com/docs/api/management/v2#!/Connections) endpoint class is accessible from the `connections()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------- |
| `GET` | […/connections](https://auth0.com/docs/api/management/v2#!/Connections/get_connections) | `getAll()` |
| `POST` | […/connections](https://auth0.com/docs/api/management/v2#!/Connections/post_connections) | `create(name: '...', strategy: '...')` |
| `GET` | […/connections/{id}](https://auth0.com/docs/api/management/v2#!/Connections/get_connections_by_id) | `get(id: '...')` |
| `PATCH` | […/connections/{id}](https://auth0.com/docs/api/management/v2#!/Connections/patch_connections_by_id) | `update(id: '...')` |
| `DELETE` | […/connections/{id}](https://auth0.com/docs/api/management/v2#!/Connections/delete_connections_by_id) | `delete(id: '...')` |
| `DELETE` | […/connections/{id}/users](https://auth0.com/docs/api/management/v2#!/Connections/delete_users_by_email) | `deleteUser(id: '...', email: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Connections class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Connections.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$connections = $management->connections();
// Retrieves the first batch of results results.
$results = $connections->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($connections->getResponsePaginator() as $connection) {
// Do something with the connection.
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "name": "",
// "display_name": "",
// "options": {},
// "id": "",
// "strategy": "",
// "realms": [
// ""
// ],
// "is_domain_connection": false,
// "metadata": {}
// }
// ]
```
### Device Credentials
The [/api/v2/device-credentials](https://auth0.com/docs/api/management/v2#!/Device_Credentials) endpoint class is accessible from the `deviceCredentials()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `GET` | […/device-credentials](https://auth0.com/docs/api/management/v2#!/Device_Credentials/get_device_credentials) | `get(userId: '...')` |
| `POST` | […/device-credentials](https://auth0.com/docs/api/management/v2#!/Device_Credentials/post_device_credentials) | `create(deviceName: '...', type: '...', value: '...', deviceId: '...')` |
| `DELETE` | […/device-credentials/{id}](https://auth0.com/docs/api/management/v2#!/Device_Credentials/delete_device_credential_by_id) | `delete(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\DeviceCredentials class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/DeviceCredentials.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$deviceCredentials = $management->deviceCredentials();
// Retrieves the first batch of results results.
$results = $deviceCredentials->get('user_id');
// You can then iterate (and auto-paginate) through all available results.
foreach ($deviceCredentials->getResponsePaginator() as $deviceCredential) {
// Do something with the device credential.
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "",
// "device_name": "",
// "device_id": "",
// "type": "",
// "user_id": "",
// "client_id": ""
// }
// ]
```
### Emails
The [/api/v2/emails](https://auth0.com/docs/api/management/v2#!/Emails) endpoint class is accessible from the `emails()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | -------------------------------------------------------------------------------------- | ---------------------------------------------- |
| `GET` | […/emails/provider](https://auth0.com/docs/api/management/v2#!/Emails/get_provider) | `getProvider()` |
| `POST` | […/emails/provider](https://auth0.com/docs/api/management/v2#!/Emails/post_provider) | `createProvider(name: '...', credentials: [])` |
| `PATCH` | […/emails/provider](https://auth0.com/docs/api/management/v2#!/Emails/patch_provider) | `updateProvider(name: '...', credentials: [])` |
| `DELETE` | […/emails/provider](https://auth0.com/docs/api/management/v2#!/Emails/delete_provider) | `deleteProvider()` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Emails class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Emails.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$endpoint = $management->emails();
// Configure the email provider.
$endpoint->createProvider(
name: 'smtp',
credentials: [
'smtp_host' => '...',
'smtp_port' => 587,
'smtp_user' => '...',
'smtp_pass' => '...',
],
body: [
'enabled' => true,
'default_from_address' => 'sender@auth0.com',
]
)
// Retrieves the configuration of the email provider.
$provider = $endpoint->getProvider();
// Print the configuration.
dd(HttpResponse::decode($provider));
// {
// "name": "smtp",
// "enabled": true,
// "default_from_address": "sender@auth0.com",
// "credentials": {
// 'smtp_host' => '...',
// 'smtp_port' => 587,
// 'smtp_user' => '...',
// 'smtp_pass' => '...',
// },
// "settings": {}
// }
```
### Email Templates
The [/api/v2/email-templates](https://auth0.com/docs/api/management/v2#!/Email_Templates) endpoint class is accessible from the `emailTemplates()` method on the Management API class.
| Method | Endpoint | SDK Method |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| `POST` | […/email-templates](https://auth0.com/docs/api/management/v2#!/Email_Templates/post_email_templates) | `create(template: '...', body: '...', from: '...', subject: '...', syntax: '...', enabled: true)` |
| `GET` | […/email-templates/{templateName}](https://auth0.com/docs/api/management/v2#!/Email_Templates/get_email_templates_by_templateName) | `get(templateName: '...')` |
| `PATCH` | […/email-templates/{templateName}](https://auth0.com/docs/api/management/v2#!/Email_Templates/patch_email_templates_by_templateName) | `update(templateName: '...', body: [])` |
| `PUT` | […/email-templates/{templateName}](https://auth0.com/docs/api/management/v2#!/Email_Templates/put_email_templates_by_templateName) | `update(templateName: '...', body: [])` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\EmailTemplates class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/EmailTemplates.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$templates = $management->emailTemplates();
// Create a new email template.
$templates->create(
template: 'verify_email',
body: '...',
from: 'sender@auth0.com',
subject: '...',
syntax: 'liquid',
enabled: true,
);
// Retrieves the configuration of the email template.
$template = $templates->get(templateName: 'verify_email');
// Print the configuration.
dd(HttpResponse::decode($template));
// {
// "template": "verify_email",
// "body": "",
// "from": "sender@auth0.com",
// "resultUrl": "",
// "subject": "",
// "syntax": "liquid",
// "urlLifetimeInSeconds": 0,
// "includeEmailInRedirect": false,
// "enabled": false
// }
```
### Grants
The [/api/v2/grants](https://auth0.com/docs/api/management/v2#!/Grants) endpoint class is accessible from the `grants()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | -------------------------------------------------------------------------------------- | ----------------------------------- |
| `GET` | […/grants](https://auth0.com/docs/api/management/v2#!/Grants/get_grants) | `getAll()` |
| `GET` | […/grants](https://auth0.com/docs/api/management/v2#!/Grants/get_grants) | `getAllByAudience(audience: '...')` |
| `GET` | […/grants](https://auth0.com/docs/api/management/v2#!/Grants/get_grants) | `getAllByClientId(clientId: '...')` |
| `GET` | […/grants](https://auth0.com/docs/api/management/v2#!/Grants/get_grants) | `getAllByUserId(userId: '...')` |
| `DELETE` | […/grants/{id}](https://auth0.com/docs/api/management/v2#!/Grants/delete_grants_by_id) | `delete(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Grants class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Grants.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$grants = $management->grants();
// Retrieves the first batch of grant results.
$results = $grants->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($grants->getResponsePaginator() as $grant) {
// Do something with the device credential.
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "...",
// "clientID": "...",
// "user_id": "...",
// "audience": "...",
// "scope": [
// "..."
// ],
// }
// ]
```
### Guardian
The [/api/v2/guardian](https://auth0.com/docs/api/management/v2#!/Guardian) endpoint class is accessible from the `guardian()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | ----------------------------------------------------------------------------------------------------------- | ----------------------------- |
| `GET` | […/guardian/enrollments/{id}](https://auth0.com/docs/api/management/v2#!/Guardian/get_enrollments_by_id) | `getEnrollment(id: '...')` |
| `DELETE` | […/guardian/enrollments/{id}](https://auth0.com/docs/api/management/v2#!/Guardian/delete_enrollments_by_id) | `deleteEnrollment(id: '...')` |
| `GET` | […/guardian/factors](https://auth0.com/docs/api/management/v2#!/Guardian/get_factors) | `getFactors()` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Guardian class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Guardian.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$guardian = $management->guardian();
// Retrieves the first batch of factor results.
$results = $guardian->getFactors();
// You can then iterate (and auto-paginate) through all available results.
foreach ($guardian->getResponsePaginator() as $factor) {
// Do something with the device credential.
dump($factor);
// {
// "enabled": true,
// "trial_expired": true,
// "name": "..."
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "enabled": true,
// "trial_expired": true,
// "name": "..."
// }
// ]
```
### Jobs
The [/api/v2/jobs](https://auth0.com/docs/api/management/v2#!/Jobs) endpoint class is accessible from the `jobs()` method on the Management API class.
| Method | Endpoint | SDK Method |
| ------ | ---------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `GET` | […/jobs/{id}](https://auth0.com/docs/api/management/v2#!/Jobs/get_jobs_by_id) | `get(id: '...')` |
| `GET` | […/jobs/{id}/errors](https://auth0.com/docs/api/management/v2#!/Jobs/get_errors) | `getErrors(id: '...')` |
| `POST` | […/jobs/users-exports](https://auth0.com/docs/api/management/v2#!/Jobs/post_users_exports) | `createExportUsersJob(body: [])` |
| `POST` | […/jobs/users-imports](https://auth0.com/docs/api/management/v2#!/Jobs/post_users_imports) | `createImportUsers(filePath: '...', connectionId: '...')` |
| `POST` | […/jobs/verification-email](https://auth0.com/docs/api/management/v2#!/Jobs/post_verification_email) | `createSendVerificationEmail(userId: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Jobs class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Jobs.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$connections = $management->connections();
$jobs = $management->jobs();
// Create a connection.
$connection = $connections->create([
'name' => 'Test Connection',
'strategy' => 'auth0',
]);
if (! HttpResponse::wasSuccessful($job)) {
throw new \Exception('Connection creation failed.');
}
$connection = HttpResponse::decode($connection);
// Create a new user export job.
$response = $jobs->createExportUsersJob([
'format' => 'json',
'fields' => [
['name' => 'user_id'],
['name' => 'name'],
['name' => 'email'],
['name' => 'identities[0].connection', "export_as": "provider"],
['name' => 'user_metadata.some_field'],
],
'connection_id' => $connection['id'],
]);
if ($response->getStatusCode() === 201) {
// The job was created successfully. Retrieve it's ID.
$jobId = HttpResponse::decode($response)['id'];
$job = null;
while (true) {
// Get the job status.
$job = $jobs->get($jobId);
if (! HttpResponse::wasSuccessful($job)) {
$job = null;
break;
}
$job = HttpResponse::decode($job);
// If the job is complete, break out of the loop.
if ($job['status'] === 'completed') {
break;
}
// If the job has failed, break out of the loop.
if ($job['status'] === 'failed') {
$job = null
break;
}
// Wait 1 second before checking the job status again.
sleep(1);
}
if ($job === null) {
// The job failed.
$errors = $jobs->getErrors($jobId);
dd($errors);
}
// The job completed successfully. Do something with the job.
dd($job);
// Delete the connection.
$connections->delete($connection['id']);
}
```
### Logs
The [/api/v2/logs](https://auth0.com/docs/api/management/v2#!/Logs) endpoint class is accessible from the `logs()` method on the Management API class.
| Method | Endpoint | SDK Method |
| ------ | ----------------------------------------------------------------------------- | ---------------- |
| `GET` | […/logs](https://auth0.com/docs/api/management/v2#!/Logs/get_logs) | `getAll()` |
| `GET` | […/logs/{id}](https://auth0.com/docs/api/management/v2#!/Logs/get_logs_by_id) | `get(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Logs class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Logs.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$logs = $management->logs();
// Retrieves the first batch of log results.
$results = $logs->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($logs->getResponsePaginator() as $log) {
// Do something with the log.
dump($log);
// {
// "date": "...",
// "type": "...",
// "description": "..."
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "date": "...",
// "type": "...",
// "description": "..."
// }
// ]
```
### Log Streams
The [/api/v2/log-streams](https://auth0.com/docs/api/management/v2#!/Log_Streams) endpoint class is accessible from the `logStreams()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | ----------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `GET` | […/log-streams](https://auth0.com/docs/api/management/v2#!/Log_Streams/get_log_streams) | `getAll()` |
| `POST` | […/log-streams](https://auth0.com/docs/api/management/v2#!/Log_Streams/post_log_streams) | `create(type: '...', sink: '...')` |
| `GET` | […/log-streams/{id}](https://auth0.com/docs/api/management/v2#!/Log_Streams/get_log_streams_by_id) | `get(id: '...')` |
| `PATCH` | […/log-streams/{id}](https://auth0.com/docs/api/management/v2#!/Log_Streams/patch_log_streams_by_id) | `update(id: '...', body: [])` |
| `DELETE` | […/log-streams/{id}](https://auth0.com/docs/api/management/v2#!/Log_Streams/delete_log_streams_by_id) | `delete(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\LogStreams class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/LogStreams.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$logStreams = $management->logStreams();
// Create a new log stream.
$logStreams->create(
type: '...',
sink: [
'name' => '...',
]
);
// Get the first batch of log streams.
$results = $logStreams->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($logStreams->getResponsePaginator() as $logStream) {
// Do something with the log stream.
dump($logStream);
// {
// "id": "...",
// "name": "...",
// "type": "...",
// "status": "..."
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "...",
// "name": "...",
// "type": "...",
// "status": "..."
// }
// ]
```
### Organizations
The [/api/v2/organizations](https://auth0.com/docs/api/management/v2#!/Organizations) endpoint class is accessible from the `organizations()` method on the Management API class.
| Method | Endpoint | SDK Method |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `GET` | […/organizations](https://auth0.com/docs/api/management/v2#!/Organizations/get_organizations) | `getAll()` |
| `POST` | […/organizations](https://auth0.com/docs/api/management/v2#!/Organizations/post_organizations) | `create(name: '...', displayName: '...')` |
| `GET` | […/organizations/{id}](https://auth0.com/docs/api/management/v2#!/Organizations/get_organizations_by_id) | `get(id: '...')` |
| `PATCH` | […/organizations/{id}](https://auth0.com/docs/api/management/v2#!/Organizations/patch_organizations_by_id) | `update(id: '...', name: '...', displayName: '...')` |
| `DELETE` | […/organizations/{id}](https://auth0.com/docs/api/management/v2#!/Organizations/delete_organizations_by_id) | `delete(id: '...')` |
| `GET` | […/organizations/name/{name}](https://auth0.com/docs/api/management/v2#!/Organizations/get_name_by_name) | `getByName(name: '...')` |
| `GET` | […/organizations/{id}/members](https://auth0.com/docs/api/management/v2#!/Organizations/get_members) | `getMembers(id: '...')` |
| `POST` | […/organizations/{id}/members](https://auth0.com/docs/api/management/v2#!/Organizations/post_members) | `addMembers(id: '...', members: [])` |
| `DELETE` | […/organizations/{id}/members](https://auth0.com/docs/api/management/v2#!/Organizations/delete_members) | `removeMembers(id: '...', members: [])` |
| `GET` | […/organizations/{id}/invitations](https://auth0.com/docs/api/management/v2#!/Organizations/get_invitations) | `getInvitations(id: '...')` |
| `POST` | […/organizations/{id}/invitations](https://auth0.com/docs/api/management/v2#!/Organizations/post_invitations) | `createInvitation(id: '...', clientId: '...', inviter: '...', invitee: '...')` |
| `GET` | […/organizations/{id}/invitations/{invitationId}](https://auth0.com/docs/api/management/v2#!/Organizations/get_invitations_by_invitation_id) | `getInvitation(id: '...', invitationId: '...')` |
| `DELETE` | […/organizations/{id}/invitations/{invitationId}](https://auth0.com/docs/api/management/v2#!/Organizations/delete_invitations_by_invitation_id) | `deleteInvitation(id: '...', invitationId: '...')` |
| `GET` | […/organizations/{id}/enabled_connections](https://auth0.com/docs/api/management/v2#!/Organizations/get_enabled_connections) | `getEnabledConnections(id: '...')` |
| `POST` | […/organizations/{id}/enabled_connections](https://auth0.com/docs/api/management/v2#!/Organizations/post_enabled_connections) | `addEnabledConnection(id: '...', connectionId: '...', body: [])` |
| `GET` | […/organizations/{id}/enabled_connections/{connectionId}](https://auth0.com/docs/api/management/v2#!/Organizations/get_enabled_connections_by_connectionId) | `getEnabledConnection(id: '...', connectionId: '...')` |
| `PATCH` | […/organizations/{id}/enabled_connections/{connectionId}](https://auth0.com/docs/api/management/v2#!/Organizations/patch_enabled_connections_by_connectionId) | `updateEnabledConnection(id: '...' connectionId: '...', body: [])` |
| `DELETE` | […/organizations/{id}/enabled_connections/{connectionId}](https://auth0.com/docs/api/management/v2#!/Organizations/delete_enabled_connections_by_connectionId) | `removeEnabledConnection(id: '...', connectionId: '...')` |
| `GET` | […/organizations/{id}/members/{userId}/roles](https://auth0.com/docs/api/management/v2#!/Organizations/get_organization_member_roles) | `getMemberRoles(id: '...'. userId: '...')` |
| `POST` | […/organizations/{id}/members/{userId}/roles](https://auth0.com/docs/api/management/v2#!/Organizations/post_organization_member_roles) | `addMemberRoles(id: '...'. userId: '...', roles: [])` |
| `DELETE` | […/organizations/{id}/members/{userId}/roles](https://auth0.com/docs/api/management/v2#!/Organizations/delete_organization_member_roles) | `removeMemberRoles(id: '...'. userId: '...', roles: [])` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Organizations class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Organizations.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$organizations = $management->organizations();
// Get all organizations.
$results = $organizations->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($logStreams->getResponsePaginator() as $logStream) {
// Do something with the log stream.
dump($logStream);
// {
// "id": "",
// "name": "...",
// "display_name": "...",
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "",
// "name": "...",
// "display_name": "...",
// ]
// Get a single organization.
$results = $organizations->get('org_id');
// Create a new organization.
$results = $organizations->create('name', 'display_name');
// Update an existing organization.
$results = $organizations->update('org_id', 'name', 'display_name');
// Delete an organization.
$results = $organizations->delete('org_id');
// Get all members of an organization.
$results = $organizations->getMembers('org_id');
// Add members to an organization.
$results = $organizations->addMembers('org_id', ['user_id_1', 'user_id_2']);
// Remove members from an organization.
$results = $organizations->removeMembers('org_id', ['user_id_1', 'user_id_2']);
// Get all invitations for an organization.
$results = $organizations->getInvitations('org_id');
// Create a new invitation for an organization.
$results = $organizations->createInvitation('org_id', 'client_id', 'inviter_user_id', 'invitee_email');
// Get a single invitation for an organization.
$results = $organizations->getInvitation('org_id', 'invitation_id');
// Delete an invitation for an organization.
$results = $organizations->deleteInvitation('org_id', 'invitation_id');
// Get all enabled connections for an organization.
$results = $organizations->getEnabledConnections('org_id');
// Add a connection to an organization.
$results = $organizations->addEnabledConnection('org_id', 'connection_id', ['assign_membership_on_login' => true]);
// Get a single enabled connection for an organization.
$results = $organizations->getEnabledConnection('org_id', 'connection_id');
// Update an enabled connection for an organization.
$results = $organizations->updateEnabledConnection('org_id', 'connection_id', ['assign_membership_on_login' => false]);
// Remove a connection from an organization.
$results = $organizations->removeEnabledConnection('org_id', 'connection_id');
// Get all roles for a member of an organization.
$results = $organizations->getMemberRoles('org_id', 'user_id');
// Add roles to a member of an organization.
$results = $organizations->addMemberRoles('org_id', 'user_id', ['role_id_1', 'role_id_2']);
// Remove roles from a member of an organization.
$results = $organizations->removeMemberRoles('org_id', 'user_id', ['role_id_1', 'role_id_2']);
```
### Resource Servers
The [/api/v2/resource-servers](https://auth0.com/docs/api/management/v2#!/Resource_Servers) endpoint class is accessible from the `resourceServers()` method on the Management API class.
| Method | Endpoint | PHP Method |
| -------- | -------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `GET` | […/resource-servers](https://auth0.com/docs/api/management/v2#!/Resource_Servers/get_resource_servers) | `getAll()` |
| `POST` | […/resource-servers](https://auth0.com/docs/api/management/v2#!/Resource_Servers/post_resource_servers) | `create(identifier: '...', body: [])` |
| `GET` | […/resource-servers/{id}](https://auth0.com/docs/api/management/v2#!/Resource_Servers/get_resource_servers_by_id) | `get(id: '...')` |
| `PATCH` | […/resource-servers/{id}](https://auth0.com/docs/api/management/v2#!/Resource_Servers/patch_resource_servers_by_id) | `update(id: '...', body: '...')` |
| `DELETE` | […/resource-servers/{id}](https://auth0.com/docs/api/management/v2#!/Resource_Servers/delete_resource_servers_by_id) | `delete(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\ResourceServers class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/ResourceServers.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$resourceServers = $management->resourceServers();
// Create a new resource server.
$resourceServers->create(
identifier: 'https://my-resource-server.auth0.com',
body: [
'name' => 'My Example API',
'scopes' => [
[
'value' => 'read:messages',
'description' => 'Read messages',
],
[
'value' => 'write:messages',
'description' => 'Write messages',
],
],
]
);
// Get all resource servers.
$results = $resourceServers->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($logStreams->getResponsePaginator() as $logStream) {
// Do something with the log stream.
dump($logStream);
// {
// "id": "",
// "name": "",
// "is_system": false,
// "identifier": "",
// "scopes": [
// "object"
// ],
// "signing_alg": "",
// "signing_secret": "",
// "allow_offline_access": false,
// "skip_consent_for_verifiable_first_party_clients": false,
// "token_lifetime": 0,
// "token_lifetime_for_web": 0,
// "enforce_policies": false,
// "token_dialect": "",
// "client": {}
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "",
// "name": "",
// "is_system": false,
// "identifier": "",
// "scopes": [
// "object"
// ],
// "signing_alg": "",
// "signing_secret": "",
// "allow_offline_access": false,
// "skip_consent_for_verifiable_first_party_clients": false,
// "token_lifetime": 0,
// "token_lifetime_for_web": 0,
// "enforce_policies": false,
// "token_dialect": "",
// "client": {}
// }
// ]
```
### Roles
The [/api/v2/roles](https://auth0.com/docs/api/management/v2#!/Roles) endpoint class is accessible from the `roles()` method on the Management API class.
| Method | Endpoint | PHP Method |
| -------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| `GET` | […/roles](https://auth0.com/docs/api/management/v2#!/Roles/get_roles) | `getAll()` |
| `POST` | […/roles](https://auth0.com/docs/api/management/v2#!/Roles/post_roles) | `create(name: '...', body: [])` |
| `GET` | […/roles/{id}](https://auth0.com/docs/api/management/v2#!/Roles/get_roles_by_id) | `get(id: '...')` |
| `PATCH` | […/roles/{id}](https://auth0.com/docs/api/management/v2#!/Roles/patch_roles_by_id) | `update(id: '...', body: [])` |
| `DELETE` | […/roles/{id}](https://auth0.com/docs/api/management/v2#!/Roles/delete_roles_by_id) | `delete(id: '...')` |
| `GET` | […/roles/{id}/users](https://auth0.com/docs/api/management/v2#!/Roles/get_role_user) | `getUsers(id: '...')` |
| `POST` | […/roles/{id}/users](https://auth0.com/docs/api/management/v2#!/Roles/post_role_users) | `addUsers(id: '...', users: [])` |
| `GET` | […/roles/{id}/permissions](https://auth0.com/docs/api/management/v2#!/Roles/get_role_permission) | `getPermissions(id: '...')` |
| `POST` | […/roles/{id}/permissions](https://auth0.com/docs/api/management/v2#!/Roles/post_role_permission_assignment) | `addPermissions(id: '...', permissions: [])` |
| `DELETE` | […/roles/{id}/permissions](https://auth0.com/docs/api/management/v2#!/Roles/delete_role_permission_assignment) | `removePermissions(id: '...', permissions: [])` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Roles class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Roles.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$roles = $management->roles();
// Create a new role.
$roles->create(
name: 'My Example Role',
body: [
'description' => 'This is an example role.',
]
);
// Get all roles.
$results = $roles->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($logStreams->getResponsePaginator() as $logStream) {
// Do something with the log stream.
dump($logStream);
// {
// "id": "",
// "name": "",
// "description": "",
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "",
// "name": "",
// "description": "",
// }
// ]
```
### Rules
The [/api/v2/rules](https://auth0.com/docs/api/management/v2#!/Rules) endpoint class is accessible from the `rules()` method on the Management API class.
| Method | Endpoint | PHP Method |
| -------- | ----------------------------------------------------------------------------------- | ------------------------------------ |
| `GET` | […/rules](https://auth0.com/docs/api/management/v2#!/Rules/get_rules) | `getAll()` |
| `POST` | […/rules](https://auth0.com/docs/api/management/v2#!/Rules/post_rules) | `create(name: '...', script: '...')` |
| `GET` | […/rules/{id}](https://auth0.com/docs/api/management/v2#!/Rules/get_rules_by_id) | `get(id: '...')` |
| `PATCH` | […/rules/{id}](https://auth0.com/docs/api/management/v2#!/Rules/patch_rules_by_id) | `update(id: '...', body: [])` |
| `DELETE` | […/rules/{id}](https://auth0.com/docs/api/management/v2#!/Rules/delete_rules_by_id) | `delete(id: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Rules class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Rules.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$rules = $management->rules();
// Create a new rule.
$rules->create(
name: 'My Example Rule',
script: 'function (user, context, callback) { callback(null, user, context); }'
);
// Get all rules.
$results = $rules->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($logStreams->getResponsePaginator() as $logStream) {
// Do something with the log stream.
dump($logStream);
// {
// "id": "",
// "name": "",
// "script": "",
// "enabled": true,
// "order": 0,
// "stage": "login_success",
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "id": "",
// "name": "",
// "script": "",
// "enabled": true,
// "order": 0,
// "stage": "login_success",
// }
// ]
```
### Stats
The [/api/v2/stats](https://auth0.com/docs/api/management/v2#!/Stats) endpoint class is accessible from the `stats()` method on the Management API class.
| Method | Endpoint | PHP Method |
| ------ | ----------------------------------------------------------------------------------------- | --------------------------------- |
| `GET` | […/stats/active-users](https://auth0.com/docs/api/management/v2#!/Stats/get_active_users) | `getActiveUsers()` |
| `GET` | […/stats/daily](https://auth0.com/docs/api/management/v2#!/Stats/get_active_users) | `getDaily(from: '...', to: '...)` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Stats class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Stats.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$stats = $management->stats();
// Retrieve the number of logins, signups and breached-password detections (subscription required) that occurred each day within a specified date range.
$results = $stats->getDaily();
// You can then iterate (and auto-paginate) through all available results.
foreach ($stats->getResponsePaginator() as $metrics) {
// Do something with the log stream.
dump($logStream);
// {
// "date": "...",
// "logins": 0,
// "signups": 0,
// "leaked_passwords": 0,
// "updated_at": "...",
// "created_at": "..."
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "date": "...",
// "logins": 0,
// "signups": 0,
// "leaked_passwords": 0,
// "updated_at": "...",
// "created_at": "..."
// }
// ]
```
### Tenants
The [/api/v2/tenants](https://auth0.com/docs/api/management/v2#!/Tenants) endpoint class is accessible from the `tenants()` method on the Management API class.
| Method | Endpoint | PHP Method |
| ------- | --------------------------------------------------------------------------------------- | -------------------------- |
| `GET` | […/tenants/settings](https://auth0.com/docs/api/management/v2#!/Tenants/get_settings) | `getSettings()` |
| `PATCH` | […/tenants/settings](https://auth0.com/docs/api/management/v2#!/Tenants/patch_settings) | `updateSettings(body: [])` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Tenants class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Tenants.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$tenants = $management->tenants();
// Retrieve the current tenant settings.
$results = $tenants->getSettings();
dd(HttpResponse::decode($results));
// {
// "change_password": {
// "enabled": false,
// "html": ""
// },
// ...
// ...
// ...
// }
```
### Tickets
The [/api/v2/tickets](https://auth0.com/docs/api/management/v2#!/Tickets) endpoint class is accessible from the `tickets()` method on the Management API class.
| Method | Endpoint | PHP Method |
| ------ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| `POST` | […/tickets/password-change](https://auth0.com/docs/api/management/v2#!/Tickets/post_password_change) | `createPasswordChange(body: [])` |
| `POST` | […/tickets/email-verification](https://auth0.com/docs/api/management/v2#!/Tickets/post_email_verification) | `createEmailVerification(userId: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\Tickets class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/Tickets.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$tickets = $management->tickets();
// Create a password change ticket.
$results = $tickets->createPasswordChange([
'result_url' => 'https://example.com',
'user_id' => '...',
'client_id' => '...',
'organization_id' => '...',
'connection_id' => '...',
'email' => '...',
'ttl_sec' => 3600,
'mark_email_as_verified' => true,
'includeEmailInRedirect' => true,
]);
dd(HttpResponse::decode($results));
// {
// "ticket": "https://login.auth0.com/lo/reset?..."
// }
```
### User Blocks
The [/api/v2/user-blocks](https://auth0.com/docs/api/management/v2#!/User_Blocks) endpoint class is accessible from the `userBlocks()` method on the Management API class.
| Method | Endpoint | PHP Method |
| -------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------- |
| `GET` | […/user-blocks](https://auth0.com/docs/api/management/v2#!/User_Blocks/get_user_blocks) | `get(id: '...')` |
| `DELETE` | […/user-blocks](https://auth0.com/docs/api/management/v2#!/User_Blocks/delete_user_blocks) | `delete(id: '...')` |
| `GET` | […/user-blocks/{id}](https://auth0.com/docs/api/management/v2#!/User_Blocks/get_user_blocks_by_id) | `getByIdentifier(identifier: '...')` |
| `DELETE` | […/user-blocks/{id}](https://auth0.com/docs/api/management/v2#!/User_Blocks/delete_user_blocks_by_id) | `deleteByIdentifier(identifier: '...')` |
For full usage reference of the available API methods please [review the Auth0\SDK\API\Management\UserBlocks class.](https://github.com/auth0/auth0-PHP/blob/main/src/API/Management/UserBlocks.php)
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$userBlocks = $management->userBlocks();
// Retrieve a list of all user blocks.
$results = $userBlocks->get('...');
dd(HttpResponse::decode($results));
// {
// "blocked_for": [
// {
// "identifier": "...",
// "ip": "..."
// }
// ]
// }
```
### Users
The [/api/v2/users](https://auth0.com/docs/api/management/v2#!/Users) endpoint class is accessible from the `users()` method on the Management API class.
| Method | Endpoint | PHP Method |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `GET` | […/users](https://auth0.com/docs/api/management/v2#!/Users/get_users) | `getAll()` |
| `POST` | […/users](https://auth0.com/docs/api/management/v2#!/Users/post_users) | `create(connection: '...', body: [])` |
| `GET` | […/users/{id}](https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id) | `get(id: '...')` |
| `PATCH` | […/users/{id}](https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id) | `update(id: '...', body: [])` |
| `DELETE` | […/users/{id}](https://auth0.com/docs/api/management/v2#!/Users/delete_users_by_id) | `delete(id: '...')` |
| `GET` | […/users/{id}/enrollments](https://auth0.com/docs/api/management/v2#!/Users/get_enrollments) | `getEnrollments(id: '...')` |
| `GET` | […/users/{user}/authentication-methods](https://auth0.com/docs/api/management/v2#!/Users/get_authentication_methods) | `getAuthenticationMethods(user: '...')` |
| `DELETE` | […/users/{user}/authentication-methods](https://auth0.com/docs/api/management/v2#!/Users/delete_authentication_methods) | `deleteAuthenticationMethods(user: '...')` |
| `POST` | […/users/{user}/authentication-methods](https://auth0.com/docs/api/management/v2#!/Users/post_authentication_methods) | `createAuthenticationMethod(user: '...', body: [])` |
| `GET` | […/users/{id}/authentication-methods/{method}](https://auth0.com/docs/api/management/v2#!/Users/get_authentication_methods_by_authentication_method_id) | `getAuthenticationMethod(id: '...', method: '...')` |
| `PATCH` | […/users/{id}/authentication-methods/{method}](https://auth0.com/docs/api/management/v2#!/Users/patch_authentication_methods_by_authentication_method_id) | `updateAuthenticationMethod(id: '...', method: '...', body: [])` |
| `DELETE` | […/users/{id}/authentication-methods/{method}](https://auth0.com/docs/api/management/v2#!/Users/delete_authentication_methods_by_authentication_method_id) | `deleteAuthenticationMethod(id: '...', method: '...')` |
| `GET` | […/users/{id}/organizations](https://auth0.com/docs/api/management/v2#!/Users/get_user_organizations) | `getOrganizations(id: '...')` |
| `GET` | […/users/{id}/logs](https://auth0.com/docs/api/management/v2#!/Users/get_logs_by_user) | `getLogs(id: '...')` |
| `GET` | […/users/{id}/roles](https://auth0.com/docs/api/management/v2#!/Users/get_user_roles) | `getRoles(id: '...')` |
| `POST` | […/users/{id}/roles](https://auth0.com/docs/api/management/v2#!/Users/post_user_roles) | `addRoles(id: '...', roles: [])` |
| `DELETE` | […/users/{id}/roles](https://auth0.com/docs/api/management/v2#!/Users/delete_user_roles) | `removeRoles(id: '...', roles: [])` |
| `GET` | […/users/{id}/permissions](https://auth0.com/docs/api/management/v2#!/Users/get_permissions) | `getPermissions(id: '...')` |
| `POST` | […/users/{id}/permissions](https://auth0.com/docs/api/management/v2#!/Users/post_permissions) | `addPermissions(id: '...', permissions: [])` |
| `DELETE` | […/users/{id}/permissions](https://auth0.com/docs/api/management/v2#!/Users/delete_permissions) | `removePermissions(id: '...', permissions: [])` |
| `DELETE` | […/users/{id}/multifactor/{provider}](https://auth0.com/docs/api/management/v2#!/Users/delete_multifactor_by_provider) | `deleteMultifactorProvider(id: '...', provider: '...')` |
| `POST` | […/users/{id}/identities](https://auth0.com/docs/api/management/v2#!/Users/post_identities) | `linkAccount(id: '...', body: [])` |
| `DELETE` | […/users/{id}/identities/{provider}/{identityId}](https://auth0.com/docs/api/management/v2#!/Users/delete_provider_by_user_id) | `unlinkAccount(id: '...', provider: '...', identityId: '...')` |
| `POST` | […/users/{id}/recovery-code-regeneration](https://auth0.com/docs/api/management/v2#!/Users/post_recovery_code_regeneration) | `createRecoveryCode(id: '...')` |
| `POST` | […/users/{id}/multifactor/actions/invalidate-remember-browser](https://auth0.com/docs/api/management/v2#!/Users/post_invalidate_remember_browser) | `invalidateBrowsers(id: '...')` |
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$users = $management->users();
// Create a new user.
$users->create(
connection: 'Username-Password-Authentication',
body: [
'email' => '...',
'password' => '...',
'email_verified' => true,
]
);
// Get a single user.
$result = $users->get('auth0|...');
dump(HttpResponse::decodedBody($result));
// Get all users.
$results = $users->getAll();
// You can then iterate (and auto-paginate) through all available results.
foreach ($users->getResponsePaginator() as $user) {
dump($user);
// {
// "user_id": "...",
// "email": "...",
// "email_verified": true,
// ...
// }
}
// Or, just work with the initial batch from the response.
dd(HttpResponse::decode($results));
// [
// {
// "user_id": "...",
// "email": "...",
// "email_verified": true,
// ...
// }
// ]
```
# Users by Email
The `Auth0\SDK\API\Management\UsersByEmail` class provides methods to access the [Users by Email endpoint](https://auth0.com/docs/api/management/v2#!/Users_By_Email) of the v2 Management API.
| Method | Endpoint | PHP Method |
| ------ | ------------------------------------------------------------------------------------------------ | ---------- |
| `GET` | […/users-by-email](https://auth0.com/docs/api/management/v2#!/Users_By_Email/get_users_by_email) | `get()` |
```php
use Auth0\SDK\Utility\HttpResponse;
$management = app('auth0')->management();
$usersByEmail = $management->usersByEmail();
// Get a single user by email.
$result = $usersByEmail->get('...');
dump(HttpResponse::decodedBody($result));
// {
// "user_id": "...",
// "email": "...",
// "email_verified": true,
// ...
// }
```
================================================
FILE: docs/Octane.md
================================================
# Octane Support
Octane compatibility with the SDK is currently considered experimental and is not supported.
Although we are working toward ensuring the SDK has full compatibility in the future, we do not recommend using this with our SDK in production until we have full confidence and announced support. There is an opportunity for problems we have not fully identified or addressed yet.
Feedback and bug-fix contributions are greatly appreciated as we work toward full support.
================================================
FILE: docs/Sessions.md
================================================
# Sessions
In order to persist users' authentication states between HTTP requests, the Auth0 Laravel SDK uses Laravel's Session API to store and retrieve necessary data about the user. Applications can configure Laravel's Session API by modifying their `config/session.php` file. By default, sessions use the `file` driver, which stores the serialized user information in a file on the application server. However, you can configure the session store to use any of the other session drivers, such as `cookie`, `database`, `apc`, `memcached` and `redis`.
It's important to note that all session drivers, except for `cookie`, require server-side storage of the session data. If you are using a load balancer or other server cluster, you must use a session driver that is shared across all of the servers.
We strongly recommend using the `database` or `redis` session drivers for applications that use Auth0. These drivers are the most reliable and scalable options for storing session data.
## Files
The default session driver is `file`, which stores the session data in files on the server. It works well for simple applications, but it does not scale reliably beyond a single server.
## Cookies
The `cookie` session driver stores the session data in secure, encrypted cookies on the client device. Although convenient, this approach is not a reliable option for production applications as it suffers from a number of notable drawbacks:
- Browsers impose a size limit of 4 KB on individual cookies, which can quickly be exceeded by storing session data.
- Laravel's cookie driver unfortunately does not "chunk" (split up) larger cookies into multiple cookies, so it is impossible to store more than the noted 4 KB of total session data.
- Most web servers and load balancers require additional configuration to accept and deliver larger cookie headers.
If your application requires the use of cookies, please use the Auth0 PHP SDK's custom cookie session handler instead. This approach supports chunking of larger cookies, but is notably incompatible with [Octane](./Octane.md). Please refer to [Cookies.md](./Cookies.md) for more information.
## Database
The `database` session driver stores the session data in a database table. This is a very reliable option for applications of any size, but it does require a database connection to be configured for your application.
## Redis
The `redis` session driver stores the session data in a Redis database. This is an equally reliable option to the `database` driver.
## APC
The `apc` session driver stores the session data in the APC cache. This is a very fast and reliable option for applications of any size, but it does require the APC PHP extension to be installed on your server.
## Memcached
The `memcached` session driver stores the session data in a Memcached database. This is an equally reliable option to the `apc` driver, but it does require the Memcached PHP extension to be installed on your server.
## Array (Testing)
The `array` session driver stores the session data in a PHP array. This option is generally used for running tests on your application as it does not persist session data between requests.
================================================
FILE: docs/Support.md
================================================
# Support
Your application must use a [supported Laravel version](#supported-laravel-releases), and your host environment must be running a [maintained PHP version](https://www.php.net/supported-versions.php).
You will also need [Composer](https://getcomposer.org/) and an [Auth0 account](https://auth0.com/signup).
### Supported Laravel Releases
| Laravel | SDK | PHP | Supported Until |
| ---------------------------------------------- | ------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [13.x](https://laravel.com/docs/13.x/releases) | 7.21+ | [8.4](https://www.php.net/releases/8.4/en.php) | Approx. [Dec 2028](https://www.php.net/supported-versions.php) (EOL for PHP 8.4) |
| | | [8.3](https://www.php.net/releases/8.3/en.php) | Approx. [March 2028](https://laravel.com/docs/13.x/releases#support-policy) (EOL for Laravel 13) |
| [12.x](https://laravel.com/docs/12.x/releases) | 7.16+ | [8.4](https://www.php.net/releases/8.4/en.php) | Approx. [Dec 2028](https://www.php.net/supported-versions.php) (EOL for PHP 8.4) |
| | | [8.3](https://www.php.net/releases/8.3/en.php) | Approx. [Feb 2027](https://laravel.com/docs/12.x/releases#support-policy) (EOL for Laravel 12) |
| | | [8.2](https://www.php.net/releases/8.2/en.php) | Approx. [Dec 2026](https://www.php.net/supported-versions.php) (EOL for PHP 8.2) |
| [11.x](https://laravel.com/docs/11.x/releases) | 7.13+ | [8.3](https://www.php.net/releases/8.3/en.php) | Approx. [March 2026](https://laravel.com/docs/11.x/releases#support-policy) (EOL for Laravel 11) |
| | | [8.2](https://www.php.net/releases/8.2/en.php) | Approx. [Dec 2026](https://www.php.net/supported-versions.php) (EOL for PHP 8.2) |
We strive to support all actively maintained Laravel releases, prioritizing support for the latest major version with our SDK. If a new Laravel major introduces breaking changes, we may have to end support for past Laravel versions earlier than planned.
Affected Laravel versions will still receive security fixes until their end-of-life date, as announced in our release notes.
### Maintenance Releases
The following releases are no longer being updated with new features by Auth0, but will continue to receive security updates through their end-of-life date.
| Laravel | SDK | PHP | Security Fixes Until |
| ---------------------------------------------- | ---------- | ---------------------------------------------- | -------------------------------------------------------------------------------------- |
| [10.x](https://laravel.com/docs/10.x/releases) | 7.5 - 7.12 | [8.3](https://www.php.net/releases/8.3/en.php) | [Feb 2025](https://laravel.com/docs/10.x/releases#support-policy) (EOL for Laravel 10) |
| | | [8.2](https://www.php.net/releases/8.2/en.php) | [Feb 2025](https://laravel.com/docs/10.x/releases#support-policy) (EOL for Laravel 10) |
| | | [8.1](https://www.php.net/releases/8.2/en.php) | [Nov 2024](https://www.php.net/supported-versions.php) (EOL for PHP 8.1) |
### Unsupported Releases
The following releases are unsupported by Auth0. While they may be suitable for some legacy applications, your mileage may vary. We recommend upgrading to a supported version as soon as possible.
| Laravel | SDK |
| -------------------------------------------- | ---------- |
| [9.x](https://laravel.com/docs/9.x/releases) | 7.0 - 7.12 |
| [8.x](https://laravel.com/docs/8.x/releases) | 7.0 - 7.4 |
| [7.x](https://laravel.com/docs/7.x/releases) | 5.4 - 6.5 |
| [6.x](https://laravel.com/docs/6.x/releases) | 5.3 - 6.5 |
| [5.x](https://laravel.com/docs/5.x/releases) | 2.0 - 6.1 |
| [4.x](https://laravel.com/docs/4.x/releases) | 1.x |
## Support Policy
The SDK follows the [Laravel support policy](https://laravel.com/docs/master/releases#support-policy) and will be supported until the Laravel version it supports reaches end-of-life, or it is no longer technically feasible to support.
## Getting Support
- If you believe you've found a bug, please [create an issue on GitHub](https://github.com/auth0/laravel-auth0).
- For questions and community support, please [join the Auth0 Community](https://community.auth0.com/).
- For paid support plans, please [contact us directly](https://auth0.com/contact-us).
- For more information about Auth0 Support, please visit our [Support Center](https://support.auth0.com/).
================================================
FILE: docs/Telescope.md
================================================
# Laravel Telescope
As of 7.11.0, the Auth0 Laravel SDK is compatible with Laravel's Telescope debugging package. However, there are some caveats to be aware of when using the two together.
## Cause of Potential Issues
Issues stem from the fact that Telescope attempts to attribute events it's recording to the authenticated user. While this is useful information to log, it presents a problem. Because Telescope hooks into a number number of events (including the cache, queries, and events system) that the SDK raises during its authentication resolution process, this can cause an infinite loop.
When a request to your application occurs, the SDK works to determine if the end user is authenticated. It executes a number of authenticated related events that Telescope happens to record by default. When these events are recorded by Telescope it asks the authentication API to determine if the end user is authenticated, which in turn calls the SDK to determine if the end user is authenticated, and thus the loop begins.
7.11.0 introduced special checks for when Telescope is installed to prevent this from occurring, but it may not cover all cases.
If you are encountering Telescope causing infinite loops, you may need to disable the offending watchers in your `config/telescope.php` file. Alternatively, you can try wrapping any problematic code in Telescope's `withoutRecording()` method to prevent it from being recorded by Telescope. For example:
```php
\Laravel\Telescope\Telescope::withoutRecording(function () {
// Your code here...
});
```
## Missing Authentication Information from Telescope
A side effect of the workarounds introduced in 7.11.0 that prevent Telescope from causing infinite loops is that Telescope may be unable to attribute recorded events triggered by the SDK to the authenticated user. This is intentional and necessary, and not a bug.
## SDK <7.11.0 Workarounds
In versions prior to 7.11.0, you may encounter a compatibility issue with the SDK and Telescope when installed and enabled together. You may need to disable offending watchers in your `config/telescope.php` file to resolve this.
For example, if you are encountering issues with Telescope's `EventWatcher`, you can disable it in your `config/telescope.php` file, or ignore specific SDK events that are causing the issue. For example:
```php
[
Watchers\EventWatcher::class => [
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
'ignore' => [
\Auth0\Laravel\Events\Configuration\BuiltConfigurationEvent::class,
\Auth0\Laravel\Events\Configuration\BuildingConfigurationEvent::class,
],
],
],
// Other configuration options left out for brevity...
];
```
================================================
FILE: docs/Users.md
================================================
# Users
- [User Persistenece](#user-persistenece)
- [Best Practices](#best-practices)
- [Retrieving User Information](#retrieving-user-information)
- [Updating User Information](#updating-user-information)
- [Extending the SDK](#extending-the-sdk)
- [User Repositories](#user-repositories)
- [Eloquent User Models](#eloquent-user-models)
## User Persistence
By default the SDK does not persist user information to a database.
- When a user authenticates with your application, the SDK retrieves their profile data from Auth0 and stores it within their session.
- During each subsequent request, the SDK retrieves the stored profile data from the session and constructs a model representing the authenticated user from it.
- This user model is available to your application via the `Auth` facade or `auth()` helper for the duration of the current request.
Later in this guide we'll demonstrate how you can extend this default behavior to persist that profile data to your application's database, if desired.
## Best Practices
Auth0 provides a number of features that can simplify your application's authentication and authorization workflows. It may be helpful to keep the following best practices in mind as you integrate the SDK into your application:
- Treat Auth0 as the single source of truth about your users.
- If you must store user information in a database, store as little as possible. Treat any stored data as a cache, and sync it regularly using [the Management API](./Management.md).
- Always use the [the Management API](./Management.md) to update user information. If you're storing user information in a database, sync those changes to your database as needed, not the other way around.
## Retrieving User Information
To retrieve information about the currently authenticated user, use the `user()` method on the `Auth` facade or `auth()` helper.
```php
auth()->user();
```
You can also retrieve information on any user using [the Management API](./Management.md). This also returns extended information not usually contained in the session state, such as user metadata.
```php
use Auth0\Laravel\Facade\Auth0;
Route::get('/profile', function () {
$profile = Auth0::management()->users()->get(auth()->id());
$profile = Auth0::json($profile);
$name = $profile['name'] ?? 'Unknown';
$email = $profile['email'] ?? 'Unknown';
return response("Hello {$name}! Your email address is {$email}.");
})->middleware('auth');
```
## Updating User Information
To update a user's information, use [the Management API](./Management.md).
```php
use Auth0\Laravel\Facade\Auth0;
Route::get('/update', function () {
Auth0::management()
->users()
->update(
id: auth()->id(),
body: [
'user_metadata' => [
'last_visited' => time()
]
]
);
})->middleware('auth');
```
## Extending the SDK
### User Repositories
By default the SDK does not store user information in your application's database. Instead, it uses the session to store the user's ID token, and retrieves user information from the token when needed. This is a good default behavior, but it may not be suitable for all applications.
The SDK uses a repository pattern to allow you to customize how user information is stored and retrieved. This allows you to use your own database to cache user information between authentication requests, or to use a different storage mechanism entirely.
#### Creating a User Repository
You can create your own user repository by extending the SDK's `Auth0\Laravel\UserRepositoryAbstract` class implementing the `Auth0\Laravel\UserRepositoryContract` interface. Your repository class need only implement two public methods, both of which should accept a `user` array parameter.
- `fromSession()` to construct a model for an authenticated user. When called, the `user` array will contain the decoded ID token for the authenticated user.
- `fromAccessToken` to construct a model representing an access token request. When called, the `user` array will contain the decoded access token provided with the request.
When these methods are called by the SDK, the `user` array will include all the information your application needs to construct an `Authenticatable` user model.
The default `UserRepository` implementation looks like this:
```php
"https://example.auth0.com/",
"aud" => "https://api.example.com/calendar/v1/",
"sub" => "auth0|123456",
"exp" => 1458872196,
"iat" => 1458785796,
"scope" => "read write",
];
*/
return User::where('auth0', $user['sub'])->first();
}
public function fromSession(array $user): ?Authenticatable
{
/*
$user = [ // Example of a decoded ID token
"iss" => "http://example.auth0.com",
"aud" => "client_id",
"sub" => "auth0|123456",
"exp" => 1458872196,
"iat" => 1458785796,
"name" => "Jane Doe",
"email" => "janedoe@example.com",
];
*/
$user = User::updateOrCreate(
attributes: [
'auth0' => $user['sub'],
],
values: [
'name' => $user['name'] ?? '',
'email' => $user['email'] ?? '',
'email_verified' => $user['email_verified'] ?? false,
]
);
return $user;
}
}
```
Note that this example returns a custom user model, `App\Models\User`. You can find an example of this model in the [User Models](#user-models) section below.
#### Registering a Repository
You can override the SDK's default user repository by updating your application's `config/auth.php` file. Simply point the value of the `repository` key to your repository class.
```php
'providers' => [
'auth0-provider' => [
'driver' => 'auth0.provider',
'repository' => \App\Repositories\UserRepository::class,
],
],
```
### Eloquent User Models
Please see [Eloquent.md](./Eloquent.md) for guidance on using Eloquent models with the SDK.
================================================
FILE: opslevel.yml
================================================
---
version: 1
repository:
owner: dx_sdks
tier:
tags:
================================================
FILE: phpstan.neon.dist
================================================
includes:
- ./vendor/phpstan/phpstan-strict-rules/rules.neon
- ./vendor/larastan/larastan/extension.neon
parameters:
level: max
paths:
- src
- deprecated
ignoreErrors:
- '#Constructor of class (.*) has an unused parameter (.*).#'
- '#Method (.*) has parameter (.*) with no value type specified in iterable type array.#'
- '#no value type specified in iterable type array.#'
- '#Dynamic call to static method (.*).#'
reportUnmatchedIgnoredErrors: false
treatPhpDocTypesAsCertain: false
checkGenericClassInNonGenericObjectType: false
================================================
FILE: phpunit.xml.dist
================================================
tests/Unit./src/./src/Events/./src/Exceptions/
================================================
FILE: psalm.xml.dist
================================================
================================================
FILE: rector.php
================================================
paths([
__DIR__ . '/config',
__DIR__ . '/src',
]);
// Laravel AuthManager::extend() resolves callbacks in a way that breaks with
// static arrow functions on Laravel 13; keep non-static `fn` here.
$rectorConfig->skip([
StaticArrowFunctionRector::class => [
__DIR__ . '/src/ServiceProviderAbstract.php',
],
StaticClosureRector::class => [
__DIR__ . '/src/ServiceProviderAbstract.php',
],
]);
$rectorConfig->ruleWithConfiguration(
RenameFunctionRector::class,
[
'chop' => 'rtrim',
'doubleval' => 'floatval',
'fputs' => 'fwrite',
'gzputs' => 'gzwrites',
'ini_alter' => 'ini_set',
'is_double' => 'is_float',
'is_integer' => 'is_int',
'is_long' => 'is_int',
'is_real' => 'is_float',
'is_writeable' => 'is_writable',
'join' => 'implode',
'key_exists' => 'array_key_exists',
'mbstrcut' => 'mb_strcut',
'mbstrlen' => 'mb_strlen',
'mbstrpos' => 'mb_strpos',
'mbstrrpos' => 'mb_strrpos',
'mbsubstr' => 'mb_substr',
'pos' => 'current',
'sizeof' => 'count',
'split' => 'explode',
'strchr' => 'strstr',
],
);
$rectorConfig->ruleWithConfiguration(
StaticCallToFuncCallRector::class,
[
new StaticCallToFuncCall('Nette\\Utils\\Strings', 'contains', 'str_contains'),
new StaticCallToFuncCall('Nette\\Utils\\Strings', 'endsWith', 'str_ends_with'),
new StaticCallToFuncCall('Nette\\Utils\\Strings', 'startsWith', 'str_starts_with'),
],
);
$rectorConfig->ruleWithConfiguration(
ArgumentAdderRector::class,
[new ArgumentAdder('Nette\\Utils\\Strings', 'replace', 2, 'replacement', '')],
);
$rectorConfig->ruleWithConfiguration(
RenameFunctionRector::class,
[
'pg_clientencoding' => 'pg_client_encoding',
'pg_cmdtuples' => 'pg_affected_rows',
'pg_errormessage' => 'pg_last_error',
'pg_fieldisnull' => 'pg_field_is_null',
'pg_fieldname' => 'pg_field_name',
'pg_fieldnum' => 'pg_field_num',
'pg_fieldprtlen' => 'pg_field_prtlen',
'pg_fieldsize' => 'pg_field_size',
'pg_fieldtype' => 'pg_field_type',
'pg_freeresult' => 'pg_free_result',
'pg_getlastoid' => 'pg_last_oid',
'pg_loclose' => 'pg_lo_close',
'pg_locreate' => 'pg_lo_create',
'pg_loexport' => 'pg_lo_export',
'pg_loimport' => 'pg_lo_import',
'pg_loopen' => 'pg_lo_open',
'pg_loread' => 'pg_lo_read',
'pg_loreadall' => 'pg_lo_read_all',
'pg_lounlink' => 'pg_lo_unlink',
'pg_lowrite' => 'pg_lo_write',
'pg_numfields' => 'pg_num_fields',
'pg_numrows' => 'pg_num_rows',
'pg_result' => 'pg_fetch_result',
'pg_setclientencoding' => 'pg_set_client_encoding',
],
);
$rectorConfig->ruleWithConfiguration(
FunctionArgumentDefaultValueReplacerRector::class,
[
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '', '!='),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, '!', '!='),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'g', 'gt'),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'l', 'lt'),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'gte', 'ge'),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'lte', 'le'),
new ReplaceFuncCallArgumentDefaultValue('version_compare', 2, 'n', 'ne'),
],
);
$rectorConfig->ruleWithConfiguration(
FuncCallToConstFetchRector::class,
[
'php_sapi_name' => 'PHP_SAPI',
'pi' => 'M_PI',
],
);
$rectorConfig->rules([
AbsolutizeRequireAndIncludePathRector::class,
// ActionInjectionToConstructorInjectionRector::class,
// AddArrayDefaultToArrayPropertyRector::class,
AddArrowFunctionReturnTypeRector::class,
// AddClosureReturnTypeRector::class,
// AddFalseDefaultToBoolPropertyRector::class,
AddMethodCallBasedStrictParamTypeRector::class,
AddParamBasedOnParentClassMethodRector::class,
AddParamTypeBasedOnPHPUnitDataProviderRector::class,
AddParamTypeSplFixedArrayRector::class,
// AddPregQuoteDelimiterRector::class,
AddReturnTypeDeclarationBasedOnParentClassMethodRector::class,
AddReturnTypeDeclarationFromYieldsRector::class,
AddVoidReturnTypeWhereNoReturnRector::class,
AndAssignsToSeparateLinesRector::class,
ArrayKeyExistsTernaryThenValueToCoalescingRector::class,
// ArrayKeysAndInArrayToArrayKeyExistsRector::class,
ArrayMergeOfNonArraysToSimpleArrayRector::class,
// ArrayShapeFromConstantArrayReturnRector::class,
// BinarySwitchToIfElseRector::class,
BooleanNotIdenticalToNotIdenticalRector::class,
//BoolvalToTypeCastRector::class,
//CallableThisArrayToAnonymousFunctionRector::class,
CallUserFuncArrayToVariadicRector::class,
CallUserFuncToMethodCallRector::class,
CallUserFuncWithArrowFunctionToInlineRector::class,
CatchExceptionNameMatchingTypeRector::class,
ChangeArrayPushToArrayAssignRector::class,
// ChangeGlobalVariablesToPropertiesRector::class,
ChangeIfElseValueAssignToEarlyReturnRector::class,
ChangeNestedForeachIfsToEarlyContinueRector::class,
ChangeNestedIfsToEarlyReturnRector::class,
ChangeOrIfContinueToMultiContinueRector::class,
// ChangeReadOnlyPropertyWithDefaultValueToConstantRector::class,
// ChangeReadOnlyVariableWithDefaultValueToConstantRector::class,
ChangeSwitchToMatchRector::class,
ClassOnObjectRector::class,
ClassOnThisVariableObjectRector::class,
ClassPropertyAssignToConstructorPromotionRector::class,
CombinedAssignRector::class,
CombineIfRector::class,
CommonNotEqualRector::class,
CompactToVariablesRector::class,
CompleteDynamicPropertiesRector::class,
ConsecutiveNullCompareReturnsToNullCoalesceQueueRector::class,
ConsistentImplodeRector::class,
// ConsistentPregDelimiterRector::class,
CountArrayToEmptyArrayComparisonRector::class,
EmptyOnNullableObjectToInstanceOfRector::class,
EncapsedStringsToSprintfRector::class,
ExplicitBoolCompareRector::class,
// ExplicitMethodCallOverMagicGetSetRector::class,
// FinalizeClassesWithoutChildrenRector::class,
FinalPrivateToPrivateVisibilityRector::class,
FlipTypeControlToUseExclusiveTypeRector::class,
//FloatvalToTypeCastRector::class,
ForeachItemsAssignToEmptyArrayToAssignRector::class,
ForeachToInArrayRector::class,
ForRepeatedCountToOwnVariableRector::class,
// ForToForeachRector::class,
FuncGetArgsToVariadicParamRector::class,
//GetClassToInstanceOfRector::class,
GetDebugTypeRector::class,
InlineArrayReturnAssignRector::class,
InlineConstructorDefaultToPropertyRector::class,
InlineIfToExplicitIfRector::class,
InlineIsAInstanceOfRector::class,
//IntvalToTypeCastRector::class,
IsAWithStringWithThirdArgumentRector::class,
IssetOnPropertyObjectToPropertyExistsRector::class,
JoinStringConcatRector::class,
LogicalToBooleanRector::class,
MakeInheritedMethodVisibilitySameAsParentRector::class,
// MultipleClassFileToPsr4ClassesRector::class,
// NarrowUnionTypeDocRector::class,
NewlineBeforeNewAssignSetRector::class,
NewStaticToNewSelfRector::class,
// NormalizeNamespaceByPSR4ComposerAutoloadRector::class,
NullableCompareToNullRector::class,
OptionalParametersAfterRequiredRector::class,
// ParamAnnotationIncorrectNullableRector::class,
ParamTypeByMethodCallTypeRector::class,
ParamTypeByParentCallTypeRector::class,
// ParamTypeFromStrictTypedPropertyRector::class,
// Php8ResourceReturnToObjectRector::class,
PostIncDecToPreIncDecRector::class,
PrivatizeFinalClassMethodRector::class,
PrivatizeFinalClassPropertyRector::class,
PropertyTypeFromStrictSetterGetterRector::class,
RemoveAlwaysElseRector::class,
// RemoveAlwaysTrueConditionSetInConstructorRector::class,
RemoveAndTrueRector::class,
RemoveDeadConditionAboveReturnRector::class,
RemoveDeadContinueRector::class,
RemoveDeadIfForeachForRector::class,
RemoveDeadLoopRector::class,
RemoveDeadReturnRector::class,
RemoveDeadStmtRector::class,
RemoveDeadTryCatchRector::class,
RemoveDeadZeroAndOneOperationRector::class,
// RemoveDelegatingParentCallRector::class,
RemoveDoubleAssignRector::class,
// RemoveDoubleUnderscoreInMethodNameRector::class,
RemoveDuplicatedArrayKeyRector::class,
RemoveDuplicatedCaseInSwitchRector::class,
// RemoveDuplicatedIfReturnRector::class,
// RemoveDuplicatedInstanceOfRector::class,
RemoveEmptyClassMethodRector::class,
// RemoveEmptyMethodCallRector::class,
// RemoveEmptyTestMethodRector::class,
RemoveExtraParametersRector::class,
RemoveFinalFromConstRector::class,
// RemoveJustPropertyFetchForAssignRector::class,
// RemoveJustVariableAssignRector::class,
// RemoveLastReturnRector::class,
// RemoveNonExistingVarAnnotationRector::class,
RemoveNullPropertyInitializationRector::class,
RemoveParentCallWithoutParentRector::class,
RemoveSoleValueSprintfRector::class,
RemoveUnreachableStatementRector::class,
RemoveUnusedConstructorParamRector::class,
RemoveUnusedForeachKeyRector::class,
RemoveUnusedNonEmptyArrayBeforeForeachRector::class,
RemoveUnusedPrivateClassConstantRector::class,
RemoveUnusedPrivateMethodParameterRector::class,
RemoveUnusedPrivatePropertyRector::class,
RemoveUnusedPromotedPropertyRector::class,
RemoveUnusedVariableAssignRector::class,
RemoveUnusedVariableInCatchRector::class,
RemoveUselessReturnTagRector::class,
RemoveUselessVarTagRector::class,
RenameForeachValueVariableToMatchExprVariableRector::class,
RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class,
ReplaceMultipleBooleanNotRector::class,
// ReturnAnnotationIncorrectNullableRector::class,
// ReturnBinaryAndToEarlyReturnRector::class,
ReturnBinaryOrToEarlyReturnRector::class,
ReturnEarlyIfVariableRector::class,
ReturnNeverTypeRector::class,
ReturnTypeFromReturnDirectArrayRector::class,
ReturnTypeFromReturnNewRector::class,
//ReturnTypeFromStrictBoolReturnExprRector::class,
ReturnTypeFromStrictConstantReturnRector::class,
ReturnTypeFromStrictNativeCallRector::class,
ReturnTypeFromStrictNewArrayRector::class,
// ReturnTypeFromStrictScalarReturnExprRector::class,
ReturnTypeFromStrictTernaryRector::class,
ReturnTypeFromStrictTypedCallRector::class,
ReturnTypeFromStrictTypedPropertyRector::class,
SeparateMultiUseImportsRector::class,
SetStateToStaticRector::class,
SetTypeToCastRector::class,
ShortenElseIfRector::class,
SimplifyArraySearchRector::class,
SimplifyBoolIdenticalTrueRector::class,
SimplifyConditionsRector::class,
SimplifyDeMorganBinaryRector::class,
SimplifyEmptyArrayCheckRector::class,
SimplifyEmptyCheckOnEmptyArrayRector::class,
// SimplifyForeachToArrayFilterRector::class,
SimplifyForeachToCoalescingRector::class,
SimplifyFuncGetArgsCountRector::class,
SimplifyIfElseToTernaryRector::class,
SimplifyIfElseWithSameContentRector::class,
// SimplifyIfExactValueReturnValueRector::class,
SimplifyIfNotNullReturnRector::class,
SimplifyIfNullableReturnRector::class,
SimplifyIfReturnBoolRector::class,
SimplifyInArrayValuesRector::class,
SimplifyMirrorAssignRector::class,
SimplifyRegexPatternRector::class,
SimplifyStrposLowerRector::class,
SimplifyTautologyTernaryRector::class,
// SimplifyUselessLastVariableAssignRector::class,
SimplifyUselessVariableRector::class,
SingleInArrayToCompareRector::class,
SingularSwitchToIfRector::class,
SplitDoubleAssignRector::class,
SplitGroupedClassConstantsRector::class,
SplitGroupedPropertiesRector::class,
// SplitListAssignToSeparateLineRector::class,
StaticArrowFunctionRector::class,
StaticClosureRector::class,
StrContainsRector::class,
StrEndsWithRector::class,
StrictArraySearchRector::class,
StringableForToStringRector::class,
StrlenZeroToIdenticalEmptyStringRector::class,
StrStartsWithRector::class,
//StrvalToTypeCastRector::class,
SwitchNegatedTernaryRector::class,
SymplifyQuoteEscapeRector::class,
TernaryConditionVariableAssignmentRector::class,
TernaryEmptyArrayArrayDimFetchToCoalesceRector::class,
TernaryFalseExpressionToIfRector::class,
TernaryToBooleanOrFalseToBooleanAndRector::class,
ThrowWithPreviousExceptionRector::class,
// TokenGetAllToObjectRector::class,
TypedPropertyFromAssignsRector::class,
TypedPropertyFromStrictConstructorRector::class,
// TypedPropertyFromStrictGetterMethodReturnTypeRector::class,
TypedPropertyFromStrictSetUpRector::class,
UnnecessaryTernaryExpressionRector::class,
// UnSpreadOperatorRector::class,
UnusedForeachValueToArrayKeysRector::class,
UnwrapFutureCompatibleIfPhpVersionRector::class,
UnwrapSprintfOneArgumentRector::class,
UseClassKeywordForClassNameResolutionRector::class,
UseIdenticalOverEqualWithSameTypeRector::class,
//UseIncrementAssignRector::class,
// VarAnnotationIncorrectNullableRector::class,
// VarConstantCommentRector::class,
VarToPublicPropertyRector::class,
VersionCompareFuncCallToConstantRector::class,
WrapEncapsedVariableInCurlyBracesRector::class,
]);
};
================================================
FILE: src/Auth/Guard.php
================================================
isImpersonating()) {
return $this->getImposter();
}
if (null === $source || self::SOURCE_TOKEN === $source) {
$token = $this->getAuthorizationGuard()->findToken();
}
if (null === $source && ! $token instanceof CredentialEntityContract || self::SOURCE_SESSION === $source) {
$session = $this->getAuthenticationGuard()->findSession();
}
return $token ?? $session ?? null;
}
public function forgetUser(): self
{
$this->setCredential();
return $this;
}
public function getCredential(): ?CredentialEntityContract
{
if ($this->isImpersonating()) {
return $this->getImposter();
}
$token = null;
$session = null;
$source = $this->getCredentialSource();
if (null === $source || self::SOURCE_TOKEN === $source) {
$token = $this->getAuthorizationGuard()->getCredential();
}
if (null === $source && ! $token instanceof CredentialEntityContract || self::SOURCE_SESSION === $source) {
$session = $this->getAuthenticationGuard()->getCredential();
}
return $token ?? $session ?? null;
}
/**
* Sets the currently authenticated user for the guard.
*
* @param null|CredentialEntityContract $credential Optional. The credential to use.
* @param null|int $source Optional. The source context in which to assign the user. Defaults to all sources.
*/
public function login(
?CredentialEntityContract $credential,
?int $source = null,
): GuardContract {
$this->stopImpersonating();
if (null === $source || self::SOURCE_TOKEN === $source) {
$this->getAuthorizationGuard()->login($credential);
}
if (null === $source || self::SOURCE_SESSION === $source) {
$this->getAuthenticationGuard()->login($credential);
}
$this->credentialSource = $source;
return $this;
}
public function logout(): GuardContract
{
if ($this->isImpersonating()) {
$this->stopImpersonating();
return $this;
}
$source = $this->getCredentialSource();
if (null === $source || self::SOURCE_TOKEN === $source) {
$this->getAuthorizationGuard()->logout();
}
if (null === $source || self::SOURCE_SESSION === $source) {
$this->getAuthenticationGuard()->logout();
}
return $this;
}
public function refreshUser(): void
{
if ($this->isImpersonating()) {
return;
}
$source = $this->getCredentialSource();
if (null === $source || self::SOURCE_TOKEN === $source) {
$this->getAuthorizationGuard()->refreshUser();
}
if (null === $source || self::SOURCE_SESSION === $source) {
$this->getAuthenticationGuard()->refreshUser();
}
}
public function setCredential(
?CredentialEntityContract $credential = null,
?int $source = null,
): GuardContract {
$this->stopImpersonating();
if (null === $source || self::SOURCE_TOKEN === $source) {
$this->getAuthorizationGuard()->setCredential($credential);
}
if (null === $source || self::SOURCE_SESSION === $source) {
$this->getAuthenticationGuard()->setCredential($credential);
}
$this->credentialSource = $source;
return $this;
}
/**
* @param CredentialEntityContract $credential
* @param ?int $source
*/
public function setImpersonating(
CredentialEntityContract $credential,
?int $source = null,
): self {
$this->impersonationSource = $source;
$this->impersonating = $credential;
return $this;
}
public function setUser(
Authenticatable $user,
): self {
if ($this->isImpersonating()) {
if ($this->getImposter()?->getUser() === $user) {
return $this;
}
$this->stopImpersonating();
}
$source = $this->getCredentialSource();
if (null === $source || self::SOURCE_TOKEN === $source) {
$this->getAuthorizationGuard()->setUser($user);
}
if (null === $source || self::SOURCE_SESSION === $source) {
$this->getAuthenticationGuard()->setUser($user);
}
return $this;
}
public function user(): ?Authenticatable
{
if ($this->isImpersonating()) {
return $this->getImposter()?->getUser();
}
$credential = $this->getCredential();
if ($credential instanceof CredentialEntityContract) {
return $credential->getUser();
}
// $source = $this->getCredentialSource();
// $token = null;
// $session = null;
// if (null === $source || self::SOURCE_TOKEN === $source) {
// $token = $this->getAuthorizationGuard()->user();
// }
// if (null === $source || self::SOURCE_SESSION === $source) {
// $session = $this->getAuthenticationGuard()->user();
// }
// return $token ?? $session ?? null;
return null;
}
private function getAuthenticationGuard(): AuthenticationGuardContract
{
$this->sdk();
return $this->authenticator ??= new AuthenticationGuard(name: $this->name, config: $this->config, sdk: $this->sdk);
}
private function getAuthorizationGuard(): AuthorizationGuardContract
{
$this->sdk();
return $this->authorizer ??= new AuthorizationGuard(name: $this->name, config: $this->config, sdk: $this->sdk);
}
private function getCredentialSource(): ?int
{
return $this->credentialSource;
}
}
================================================
FILE: src/Auth0.php
================================================
*/
protected array $deferred = [];
final public function clear(): bool
{
$this->deferred = [];
return $this->getCache()->flush();
}
final public function commit(): bool
{
$success = true;
foreach (array_keys($this->deferred) as $singleDeferred) {
$item = $this->getDeferred((string) $singleDeferred);
// @codeCoverageIgnoreStart
if ($item instanceof CacheItemInterface && ! $this->save($item)) {
$success = false;
}
// @codeCoverageIgnoreEnd
}
$this->deferred = [];
return $success;
}
/**
* @param string $key the key for which to return the corresponding Cache Item
*/
final public function deleteItem(string $key): bool
{
return $this->getCache()->forget($key);
}
final public function deleteItems(array $keys): bool
{
$deleted = true;
foreach ($keys as $key) {
if (! $this->deleteItem($key)) {
$deleted = false;
}
}
return $deleted;
}
final public function getItem(string $key): CacheItemInterface
{
$value = $this->getCache()->get($key);
if (false === $value) {
return CacheItemBridge::miss($key);
}
return $this->createItem($key, $value);
}
/**
* @param string[] $keys
*
* @return CacheItemInterface[]
*/
final public function getItems(array $keys = []): iterable
{
if ([] === $keys) {
return [];
}
$results = $this->getCache()->many($keys);
$items = [];
foreach ($results as $key => $value) {
$key = (string) $key;
$items[$key] = $this->createItem($key, $value);
}
return $items;
}
/**
* @param string $key the key for which to return the corresponding Cache Item
*/
final public function hasItem(string $key): bool
{
return $this->getItem($key)
->isHit();
}
final public function save(CacheItemInterface $item): bool
{
if (! $item instanceof CacheItemBridge) {
return false;
}
$value = serialize($item->getRawValue());
$key = $item->getKey();
$expires = $item->getExpiration();
if ($expires->getTimestamp() <= time()) {
return $this->deleteItem($key);
}
$ttl = $expires->getTimestamp() - time();
return $this->getCache()->put($key, $value, $ttl);
}
final public function saveDeferred(CacheItemInterface $item): bool
{
if (! $item instanceof CacheItemBridge) {
return false;
}
$this->deferred[$item->getKey()] = [
'item' => $item,
'expiration' => $item->getExpiration(),
];
return true;
}
protected function createItem(string $key, mixed $value): CacheItemInterface
{
if (! is_string($value)) {
return CacheItemBridge::miss($key);
}
$value = unserialize($value);
if (false === $value) {
return CacheItemBridge::miss($key);
}
return new CacheItemBridge($key, $value, true);
}
protected function getCache(): Store
{
$cache = cache();
// @codeCoverageIgnoreStart
if (! $cache instanceof CacheManager) {
throw new RuntimeException('Cache store is not an instance of Illuminate\Contracts\Cache\CacheManager');
}
// @codeCoverageIgnoreEnd
return $cache->getStore();
}
/**
* @param string $key the key for which to return the corresponding Cache Item
*
* @codeCoverageIgnore
*/
protected function getDeferred(string $key): ?CacheItemInterface
{
if (! isset($this->deferred[$key])) {
return null;
}
$deferred = $this->deferred[$key];
$item = clone $deferred['item'];
$expires = $deferred['expiration'];
if ($expires instanceof DateTimeInterface) {
$expires = $expires->getTimestamp();
}
if (null !== $expires && $expires <= time()) {
unset($this->deferred[$key]);
return null;
}
return $item;
}
}
================================================
FILE: src/Bridges/CacheBridgeContract.php
================================================
expiration = match (true) {
null === $time => new DateTimeImmutable('now +1 year'),
is_int($time) => new DateTimeImmutable('now +' . (string) $time . ' seconds'),
$time instanceof DateInterval => (new DateTimeImmutable())->add($time),
};
return $this;
}
public function expiresAt(?DateTimeInterface $expiration): static
{
$this->expiration = $expiration ?? new DateTimeImmutable('now +1 year');
return $this;
}
public function set(mixed $value): static
{
$this->value = $value;
return $this;
}
public static function miss(string $key): self
{
return new self(
key: $key,
value: null,
hit: false,
);
}
}
================================================
FILE: src/Bridges/CacheItemBridgeAbstract.php
================================================
isHit() ? $this->value : null;
}
/**
* Returns the expiration timestamp.
*/
final public function getExpiration(): DateTimeInterface
{
return $this->expiration ?? new DateTimeImmutable('now +1 year');
}
final public function getKey(): string
{
return $this->key;
}
/**
* Returns the raw value, regardless of hit status.
*/
final public function getRawValue(): mixed
{
return $this->value;
}
final public function isHit(): bool
{
return $this->hit;
}
abstract public function expiresAfter(int | DateInterval | null $time): static;
abstract public function expiresAt(?DateTimeInterface $expiration): static;
abstract public function set(mixed $value): static;
}
================================================
FILE: src/Bridges/CacheItemBridgeContract.php
================================================
setPrefix($prefix);
}
/**
* This method is required by the interface but is not used by this SDK.
*
* @param bool $deferring whether to defer persisting the storage state
*/
final public function defer(bool $deferring): void
{
}
/**
* Delete a value from the Laravel session by key. (Key will be automatically prefixed with the SDK's configured namespace.).
*
* @param string $key session key to delete
*
* @throws SessionException If a Laravel session store is not available.
*/
final public function delete(string $key): void
{
$payload = $this->getPayload() ?? [];
if (array_key_exists($key, $payload)) {
unset($payload[$key]);
$this->getStore()->put($this->getPrefix(), json_encode(array_filter($payload), JSON_THROW_ON_ERROR));
}
}
/**
* Retrieve a value from the Laravel session by key. (Key will be automatically prefixed with the SDK's configured namespace.).
*
* @param string $key session key to query
* @param mixed $default default to return if nothing was found
*
* @throws SessionException If a Laravel session store is not available.
*/
final public function get(string $key, $default = null): mixed
{
$payload = $this->getPayload() ?? [];
return $payload[$key] ?? $default;
}
/**
* Get all values from the Laravel session that are prefixed with the SDK's configured namespace.
*
* @throws SessionException If a Laravel session store is not available.
*/
final public function getAll(): array
{
return $this->getPayload() ?? [];
}
/**
* Get the prefix used for all session keys.
*
* @return string Prefix used for all session keys.
*/
final public function getPrefix(): string
{
return $this->prefix;
}
/**
* Delete all values from the Laravel session that are prefixed with the SDK's configured namespace.
*
* @throws SessionException If a Laravel session store is not available.
*/
final public function purge(): void
{
$this->getStore()->forget($this->getPrefix());
}
/**
* Store a value in the Laravel session. (Key will be automatically prefixed with the SDK's configured namespace.).
*
* @param string $key session key to set
* @param mixed $value value to use
*
* @throws SessionException If a Laravel session store is not available.
*/
final public function set(string $key, $value): void
{
$payload = $this->getPayload() ?? [];
$payload[$key] = $value;
$this->getStore()->put($this->getPrefix(), json_encode($payload, JSON_THROW_ON_ERROR));
}
/**
* Set the prefix used for all session keys.
*
* @param string $prefix Prefix to use for all session keys.
*
* @return $this
*/
final public function setPrefix(
string $prefix = 'auth0',
): self {
$prefix = trim($prefix);
if ('' === $prefix) {
throw new InvalidArgumentException('Prefix cannot be empty.');
}
$this->prefix = $prefix;
return $this;
}
protected function getPayload(): ?array
{
$encoded = $this->getStore()->get($this->getPrefix());
if (is_string($encoded)) {
$decoded = json_decode($encoded, true, 512);
if (is_array($decoded)) {
return $decoded;
}
}
return null;
}
/**
* Retrieves the Laravel session store.
*
* @throws SessionException If a Laravel session store is not available.
*
* @psalm-suppress RedundantConditionGivenDocblockType
*/
protected function getStore(): Store
{
/**
* @var Store $store
*/
$store = app('session.store');
/**
* @var Request $request
*/
$request = app('request');
if (! $request->hasSession(true)) {
$request->setLaravelSession($store);
}
if (! $store->isStarted()) {
$store->start();
}
return $store;
}
}
================================================
FILE: src/Bridges/SessionBridgeContract.php
================================================
*/
public const VERSION_2 = ['AUTH0_CONFIG_VERSION' => 2];
private static ?array $environment = null;
private static ?array $json = null;
private static ?string $path = null;
public static function get(
string $setting,
array | string | int | bool | null $default = null,
): array | string | int | bool | null {
if (in_array($setting, self::USES_ARRAYS, true)) {
$value = self::getValue($setting, $default);
if (! is_string($value)) {
return $default;
}
return self::stringToArrayOrNull($value, ',') ?? $default;
}
if (in_array($setting, self::USES_BOOLEANS, true)) {
$value = self::getValue($setting, $default);
if (! is_bool($value) && ! is_string($value)) {
return $default;
}
return self::stringToBoolOrNull($value) ?? $default;
}
if (in_array($setting, self::USES_INTEGERS, true)) {
$value = self::getValue($setting, $default);
if (! is_int($value) && ! is_string($value)) {
return $default;
}
return self::stringOrIntToIntOrNull($value) ?? $default;
}
$result = null;
$value = self::getValue($setting) ?? $default;
if (is_string($value) || is_int($value) || null === $value) {
$result = self::stringOrNull($value) ?? $default;
}
if (self::CONFIG_DOMAIN === $setting && null === $result) {
// Fallback to extracting the tenant domain from the signing key subject.
$temp = self::getJson()['signing_keys.0.subject'] ?? '';
$temp = explode('=', $temp);
if (isset($temp[1]) && str_ends_with($temp[1], '.auth0.com')) {
$result = $temp[1]; // @codeCoverageIgnore
}
}
return $result ?? $default;
}
/**
* @codeCoverageIgnore
*
* @psalm-suppress DocblockTypeContradiction
*/
public static function getEnvironment(): array
{
if (null === self::$environment) {
$path = self::getPath();
$laravelEnvironment = env('APP_ENV');
$laravelEnvironment = is_string($laravelEnvironment) && '' !== trim($laravelEnvironment) ? trim($laravelEnvironment) : 'local';
$env = [];
$files = ['.env', '.env.auth0'];
$files[] = '.env.' . $laravelEnvironment;
$files[] = '.env.auth0.' . $laravelEnvironment;
foreach ($files as $file) {
if (! file_exists($path . $file)) {
continue;
}
$contents = file($path . $file, FILE_SKIP_EMPTY_LINES | FILE_IGNORE_NEW_LINES);
if (! is_array($contents)) {
continue;
}
if ([] === $contents) {
continue;
}
foreach ($contents as $content) {
if (1 !== substr_count($content, '=')) {
continue;
}
[$k,$v] = explode('=', $content);
// @phpstan-ignore-next-line
if (! is_string($k)) {
continue;
}
// @phpstan-ignore-next-line
if (! is_string($v)) {
continue;
}
$v = trim($v);
if ('' === $v) {
$v = null;
} elseif ('empty' === $v) {
$v = null;
} elseif ('(empty)' === $v) {
$v = null;
} elseif ('null' === $v) {
$v = null;
} elseif ('(null)' === $v) {
$v = null;
} elseif ('true' === $v) {
$v = true;
} elseif ('(true)' === $v) {
$v = true;
} elseif ('false' === $v) {
$v = false;
} elseif ('(false)' === $v) {
$v = false;
}
$env[trim($k)] = $v;
}
}
self::$environment = $env;
}
return self::$environment;
}
/**
* @codeCoverageIgnore
*/
public static function getJson(): array
{
if (null === self::$json) {
$path = self::getPath();
$laravelEnvironment = env('APP_ENV');
$laravelEnvironment = is_string($laravelEnvironment) && '' !== trim($laravelEnvironment) ? trim($laravelEnvironment) : 'local';
$configuration = [];
$files = ['.auth0.json', '.auth0.api.json', '.auth0.app.json'];
$files[] = '.auth0.' . $laravelEnvironment . '.json';
$files[] = '.auth0.' . $laravelEnvironment . '.api.json';
$files[] = '.auth0.' . $laravelEnvironment . '.app.json';
foreach ($files as $file) {
if (file_exists($path . $file)) {
$content = file_get_contents($path . $file);
if (is_string($content)) {
$json = json_decode($content, true, 512);
if (is_array($json)) {
$configuration = array_merge($configuration, $json);
}
}
}
}
self::$json = Arr::dot($configuration);
}
return self::$json;
}
public static function getPath(): string
{
if (null === self::$path) {
$path = config('auth0.configurationPath');
if (! is_string($path)) {
$path = base_path() . DIRECTORY_SEPARATOR;
}
self::$path = $path;
}
return self::$path;
}
public static function string(string $key, ?string $default = null): ?string
{
$value = config($key, $default);
if (is_string($value)) {
return $value;
}
return null;
}
public static function stringOrIntToIntOrNull(
int | string $value,
int | null $default = null,
): int | null {
if (is_int($value)) {
return $value;
}
$value = trim($value);
if ('' === $value) {
return $default;
}
if (ctype_digit($value)) {
return (int) $value;
}
return $default;
}
public static function stringOrNull(
string | int | null $value,
string | int | null $default = null,
): string | int | null {
if (! is_string($value)) {
return $default;
}
$value = trim($value);
if ('' === $value) {
return $default;
}
if ('empty' === $value) {
return $default;
}
if ('(empty)' === $value) {
return $default;
}
if ('null' === $value) {
return $default;
}
if ('(null)' === $value) {
return $default;
}
return $value;
}
public static function stringToArray(array | string | null $config, string $delimiter = ' '): array
{
if (is_array($config)) {
return $config;
}
if (is_string($config) && '' !== $config && '' !== $delimiter) {
$response = explode($delimiter, $config);
// @phpstan-ignore-next-line
if (count($response) >= 1 && '' !== trim($response[0])) {
return $response;
}
}
return [];
}
public static function stringToArrayOrNull(array | string | null $config, string $delimiter = ' '): ?array
{
if (is_array($config) && [] !== $config) {
return $config;
}
if (is_string($config) && '' !== $config && '' !== $delimiter) {
$response = explode($delimiter, $config);
// @phpstan-ignore-next-line
if (count($response) >= 1 && '' !== trim($response[0])) {
return $response;
}
}
return null;
}
public static function stringToBoolOrNull(bool | string | null $config, ?bool $default = null): ?bool
{
if (is_bool($config)) {
return $config;
}
if (is_string($config) && '' !== $config) {
$config = strtolower(trim($config));
if ('true' === $config) {
return true;
}
if ('false' === $config) {
return false;
}
}
return $default;
}
public static function version(): int
{
$version = config('auth0.AUTH0_CONFIG_VERSION', 1);
return is_int($version) ? $version : 1;
}
private static function getValue(
string $setting,
array | bool | string | int | null $default = null,
): array | bool | string | int | null {
$value = null;
if (defined('AUTH0_OVERRIDE_CONFIGURATION')) {
$override = constant('AUTH0_OVERRIDE_CONFIGURATION');
if (is_string($override)) {
$value = config($override . '.' . $setting);
}
} else {
$env = 'AUTH0_' . strtoupper(Str::snake($setting));
$json = self::CONFIG_AUDIENCE === $setting ? 'identifier' : Str::snake($setting);
$value = getenv($env);
if (! is_string($value)) {
$value = null;
}
$value ??= self::getEnvironment()[$env] ?? null;
$value = match ($setting) {
self::CONFIG_DOMAIN => '{DOMAIN}' === $value ? null : $value,
self::CONFIG_CLIENT_ID => '{CLIENT_ID}' === $value ? null : $value,
self::CONFIG_CLIENT_SECRET => '{CLIENT_SECRET}' === $value ? null : $value,
self::CONFIG_AUDIENCE => '{API_IDENTIFIER}' === $value ? null : $value,
default => $value,
};
$value ??= self::getJson()[$json] ?? $default;
}
if (! is_string($value) && ! is_array($value) && ! is_bool($value) && ! is_int($value)) {
$value = null;
}
if (is_string($value)) {
$value = trim($value, '\'"');
}
return $value ?? $default;
}
}
================================================
FILE: src/ConfigurationContract.php
================================================
|string $config
* @param string $delimiter
*/
public static function stringToArrayOrNull(array | string | null $config, string $delimiter = ' '): ?array;
/**
* Converts a truthy string representation into a boolean.
*
* @param null|bool|string $config
* @param ?bool $default
*/
public static function stringToBoolOrNull(string | bool | null $config, ?bool $default = null): ?bool;
}
================================================
FILE: src/Controllers/CallbackController.php
================================================
guard();
if (! $guard instanceof GuardAbstract) {
logger()->error(sprintf('A request implementing the `%s` controller was not routed through a Guard configured with an Auth0 driver. The incorrectly assigned Guard was: %s', self::class, $guard::class), $request->toArray());
throw new ControllerException(ControllerException::ROUTED_USING_INCOMPATIBLE_GUARD);
}
$code = $request->query('code');
$state = $request->query('state');
$code = is_string($code) ? trim($code) : '';
$state = is_string($state) ? trim($state) : '';
$success = false;
if ('' === $code) {
$code = null;
}
if ('' === $state) {
$state = null;
}
/**
* @var null|string $code
* @var null|string $state
*/
try {
if (null !== $code && null !== $state) {
Events::framework(new Attempting($guard::class, ['code' => $code, 'state' => $state], true));
$success = $guard->sdk()->exchange(
code: $code,
state: $state,
);
}
} catch (Throwable $throwable) {
$credentials = $guard->sdk()->getUser() ?? [];
$credentials['code'] = $code;
$credentials['state'] = $state;
$credentials['error'] = ['description' => $throwable->getMessage()];
Events::framework(new Failed($guard::class, $guard->user(), $credentials));
session()->invalidate();
Events::dispatch($event = new AuthenticationFailed($throwable, true));
if ($event->throwException) {
throw $throwable;
}
}
if (null !== $request->query('error') && null !== $request->query('error_description')) {
// Workaround to aid static analysis, due to the mixed formatting of the query() response:
$error = $request->query('error', '');
$errorDescription = $request->query('error_description', '');
$error = is_string($error) ? $error : '';
$errorDescription = is_string($errorDescription) ? $errorDescription : '';
Events::framework(new Attempting($guard::class, ['code' => $code, 'state' => $state], true));
Events::framework(new Failed($guard::class, $guard->user(), [
'code' => $code,
'state' => $state,
'error' => ['error' => $error, 'description' => $errorDescription],
]));
session()->invalidate();
// Create a dynamic exception to report the API error response
$exception = new CallbackControllerException(sprintf(CallbackControllerException::MSG_API_RESPONSE, $error, $errorDescription));
// Store the API exception in the session as a flash variable, in case the application wants to access it.
session()->flash('auth0.callback.error', sprintf(CallbackControllerException::MSG_API_RESPONSE, $error, $errorDescription));
Events::dispatch($event = new AuthenticationFailed($exception, true));
if ($event->throwException) {
throw $exception;
}
}
if (! $success) {
return redirect()->intended(config(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_LOGIN, '/login'));
}
$credential = ($guard instanceof Guard) ? $guard->find(Guard::SOURCE_SESSION) : $guard->find();
$user = $credential?->getUser();
if ($credential instanceof CredentialEntityContract && $user instanceof Authenticatable) {
Events::framework(new Validated($guard::class, $user));
session()->regenerate(true);
/**
* @var Guard $guard
*/
$guard->login($credential, Guard::SOURCE_SESSION);
Events::dispatch(new AuthenticationSucceeded($user));
// @phpstan-ignore-next-line
if ($user instanceof Authenticatable) {
Events::framework(new Authenticated($guard::class, $user));
}
}
return redirect()->intended(config(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_AFTER_LOGIN, config(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_INDEX, '/')));
}
}
================================================
FILE: src/Controllers/CallbackControllerContract.php
================================================
guard();
if (! $guard instanceof GuardAbstract) {
logger()->error(sprintf('A request implementing the `%s` controller was not routed through a Guard configured with an Auth0 driver. The incorrectly assigned Guard was: %s', self::class, $guard::class), $request->toArray());
throw new ControllerException(ControllerException::ROUTED_USING_INCOMPATIBLE_GUARD);
}
$loggedIn = $guard->check() ? true : null;
$loggedIn ??= (($guard instanceof Guard) ? $guard->find(Guard::SOURCE_SESSION) : $guard->find()) instanceof CredentialEntityContract;
if ($loggedIn) {
return redirect()->intended(
config(
Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_AFTER_LOGIN,
config(
Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_INDEX,
'/',
),
),
);
}
session()->regenerate(true);
Events::dispatch($event = new LoginAttempting());
$url = $guard->sdk()->login(params: $event->parameters);
return redirect()->away($url);
}
}
================================================
FILE: src/Controllers/LoginControllerContract.php
================================================
guard();
if (! $guard instanceof GuardAbstract) {
logger()->error(sprintf('A request implementing the `%s` controller was not routed through a Guard configured with an Auth0 driver. The incorrectly assigned Guard was: %s', self::class, $guard::class), $request->toArray());
throw new ControllerException(ControllerException::ROUTED_USING_INCOMPATIBLE_GUARD);
}
$loggedIn = $guard->check() ? true : null;
$loggedIn ??= (($guard instanceof Guard) ? $guard->find(Guard::SOURCE_SESSION) : $guard->find()) instanceof CredentialEntityContract;
$landing = Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_AFTER_LOGOUT);
$landing ??= Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_INDEX);
$landing ??= '/';
if ($loggedIn) {
session()->invalidate();
$guard->logout(); /** @phpstan-ignore-line */
$route = (string) url($landing); /** @phpstan-ignore-line */
$url = $guard->sdk()->authentication()->getLogoutLink($route);
return redirect()->away($url);
}
return redirect()->intended($landing);
}
}
================================================
FILE: src/Controllers/LogoutControllerContract.php
================================================
user = null;
$this->idToken = null;
$this->accessToken = null;
$this->accessTokenDecoded = null;
$this->accessTokenScope = null;
$this->accessTokenExpiration = null;
$this->refreshToken = null;
return $this;
}
public function setAccessToken(
?string $accessToken = null,
): self {
$this->accessToken = $accessToken;
return $this;
}
public function setAccessTokenDecoded(
?array $accessTokenDecoded = null,
): self {
$this->accessTokenDecoded = $accessTokenDecoded;
return $this;
}
public function setAccessTokenExpiration(
?int $accessTokenExpiration = null,
): self {
$this->accessTokenExpiration = $accessTokenExpiration;
return $this;
}
public function setAccessTokenScope(
?array $accessTokenScope = null,
): self {
$this->accessTokenScope = $accessTokenScope;
return $this;
}
public function setIdToken(
?string $idToken = null,
): self {
$this->idToken = $idToken;
return $this;
}
public function setRefreshToken(
?string $refreshToken = null,
): self {
$this->refreshToken = $refreshToken;
return $this;
}
public function setUser(
?Authenticatable $user = null,
): self {
$this->user = $user;
return $this;
}
/**
* Create a new Credential instance.
*
* @param null|Authenticatable $user The user entity this credential represents.
* @param null|string $idToken The ID token for this credential.
* @param null|string $accessToken The access token for this credential.
* @param null|array $accessTokenScope The access token scope for this credential.
* @param null|int $accessTokenExpiration The access token expiration for this credential.
* @param null|string $refreshToken The refresh token for this credential.
* @param null|array $accessTokenDecoded The decoded access token for this credential.
*/
public static function create(
?Authenticatable $user = null,
?string $idToken = null,
?string $accessToken = null,
?array $accessTokenScope = null,
?int $accessTokenExpiration = null,
?string $refreshToken = null,
?array $accessTokenDecoded = null,
): self {
return new self(
$user,
$idToken,
$accessToken,
$accessTokenScope,
$accessTokenExpiration,
$refreshToken,
$accessTokenDecoded,
);
}
}
================================================
FILE: src/Entities/CredentialEntityAbstract.php
================================================
$accessTokenScope The access token scope for this credential.
* @param null|int $accessTokenExpiration The access token expiration for this credential.
* @param null|string $refreshToken The refresh token for this credential.
* @param null|array $accessTokenDecoded The decoded access token for this credential.
*/
public function __construct(
protected ?Authenticatable $user = null,
protected ?string $idToken = null,
protected ?string $accessToken = null,
protected ?array $accessTokenScope = null,
protected ?int $accessTokenExpiration = null,
protected ?string $refreshToken = null,
protected ?array $accessTokenDecoded = null,
) {
}
final public function getAccessToken(): ?string
{
return $this->accessToken;
}
/**
* @psalm-suppress MixedReturnTypeCoercion
*/
final public function getAccessTokenDecoded(): ?array
{
return $this->accessTokenDecoded;
}
final public function getAccessTokenExpiration(): ?int
{
return $this->accessTokenExpiration;
}
final public function getAccessTokenExpired(): ?bool
{
$expires = $this->getAccessTokenExpiration();
if (null === $expires || $expires <= 0) {
return null;
}
return time() >= $expires;
}
/**
* @psalm-suppress MixedReturnTypeCoercion
*/
final public function getAccessTokenScope(): ?array
{
return $this->accessTokenScope;
}
final public function getIdToken(): ?string
{
return $this->idToken;
}
final public function getRefreshToken(): ?string
{
return $this->refreshToken;
}
final public function getUser(): ?Authenticatable
{
return $this->user;
}
/**
* @return array{user: false|string, idToken: null|string, accessToken: null|string, accessTokenDecoded: null|array, accessTokenScope: null|array, accessTokenExpiration: null|int, accessTokenExpired: null|bool, refreshToken: null|string}
*/
final public function jsonSerialize(): mixed
{
return [
'user' => json_encode($this->getUser(), JSON_FORCE_OBJECT),
'idToken' => $this->getIdToken(),
'accessToken' => $this->getAccessToken(),
'accessTokenDecoded' => $this->getAccessTokenDecoded(),
'accessTokenScope' => $this->getAccessTokenScope(),
'accessTokenExpiration' => $this->getAccessTokenExpiration(),
'accessTokenExpired' => $this->getAccessTokenExpired(),
'refreshToken' => $this->getRefreshToken(),
];
}
abstract public function clear(): self;
abstract public function setAccessToken(
?string $accessToken = null,
): self;
abstract public function setAccessTokenDecoded(
?array $accessTokenDecoded = null,
): self;
abstract public function setAccessTokenExpiration(
?int $accessTokenExpiration = null,
): self;
abstract public function setAccessTokenScope(
?array $accessTokenScope = null,
): self;
abstract public function setIdToken(
?string $idToken = null,
): self;
abstract public function setRefreshToken(
?string $refreshToken = null,
): self;
abstract public function setUser(
?Authenticatable $user = null,
): self;
}
================================================
FILE: src/Entities/CredentialEntityContract.php
================================================
*/
public function getAccessTokenDecoded(): ?array;
/**
* Get the access token expiration for this credential.
*/
public function getAccessTokenExpiration(): ?int;
/**
* Check if the access token for this credential has expired.
*/
public function getAccessTokenExpired(): ?bool;
/**
* Get the access token scope for this credential.
*
* @return null|array
*/
public function getAccessTokenScope(): ?array;
/**
* Get the ID token for this credential.
*/
public function getIdToken(): ?string;
/**
* Get the refresh token for this credential.
*/
public function getRefreshToken(): ?string;
/**
* Get the user entity this credential represents.
*/
public function getUser(): ?Authenticatable;
/**
* Set the access token for this credential.
*
* @param null|string $accessToken The access token for this credential.
*/
public function setAccessToken(
?string $accessToken = null,
): self;
/**
* Set the decoded access token content for this credential.
*
* @param null|array $accessTokenDecoded The decoded access token content for this credential.
*/
public function setAccessTokenDecoded(
?array $accessTokenDecoded = null,
): self;
/**
* Set the access token expiration for this credential.
*
* @param null|int $accessTokenExpiration The access token expiration for this credential.
*/
public function setAccessTokenExpiration(
?int $accessTokenExpiration = null,
): self;
/**
* Set the access token scope for this credential.
*
* @param null|array $accessTokenScope The access token scope for this credential.
*/
public function setAccessTokenScope(
?array $accessTokenScope = null,
): self;
/**
* Set the ID token for this credential.
*
* @param null|string $idToken The ID token for this credential.
*/
public function setIdToken(
?string $idToken = null,
): self;
/**
* Set the refresh token for this credential.
*
* @param null|string $refreshToken The refresh token for this credential.
*/
public function setRefreshToken(
?string $refreshToken = null,
): self;
/**
* Set the user entity this credential represents.
*
* @param null|Authenticatable $user The user entity this credential represents.
*/
public function setUser(
?Authenticatable $user = null,
): self;
}
================================================
FILE: src/Entities/EntityAbstract.php
================================================
configuration instanceof SdkConfiguration) {
$configuration = [];
if (2 === Configuration::version()) {
$defaultConfiguration = config('auth0.guards.default');
$guardConfiguration = [];
if (null !== $this->guardConfigurationKey && '' !== $this->guardConfigurationKey && 'default' !== $this->guardConfigurationKey) {
$guardConfiguration = config('auth0.guards.' . $this->guardConfigurationKey) ?? [];
}
if (is_array($defaultConfiguration) && [] !== $defaultConfiguration) {
$configuration = array_merge($configuration, array_filter($defaultConfiguration));
}
if (is_array($guardConfiguration) && [] !== $guardConfiguration) {
$configuration = array_merge($configuration, array_filter($guardConfiguration));
}
}
if (2 !== Configuration::version()) {
$configuration = config('auth0');
if (! is_array($configuration)) {
$configuration = [];
}
}
$this->configuration = $this->createConfiguration($configuration);
}
return $this->configuration;
}
final public function getCredentials(): ?object
{
return $this->getSdk()->getCredentials();
}
final public function getGuardConfigurationKey(): ?string
{
return $this->guardConfigurationKey;
}
final public function getSdk(): Auth0Interface
{
if (! $this->sdk instanceof Auth0Interface) {
return $this->setSdk(new Auth0($this->getConfiguration()));
}
return $this->sdk;
}
final public function management(): ManagementInterface
{
return $this->getSdk()->management();
}
final public function setGuardConfigurationKey(
?string $guardConfigurationKey = null,
): self {
$this->guardConfigurationKey = $guardConfigurationKey;
return $this;
}
final public function setSdk(Auth0Interface $sdk): Auth0Interface
{
$this->configuration = $sdk->configuration();
$this->sdk = $sdk;
$this->setSdkTelemetry();
return $this->sdk;
}
abstract public function reset(): self;
/**
* @param null|array|SdkConfiguration $configuration
*/
abstract public function setConfiguration(
SdkConfiguration | array | null $configuration = null,
): self;
protected function bootBackchannelLogoutCache(array $config): array
{
$backchannelLogoutCache = $config['backchannelLogoutCache'] ?? null;
if (false === $backchannelLogoutCache) {
unset($config['backchannelLogoutCache']);
return $config;
}
if (null === $backchannelLogoutCache) {
$backchannelLogoutCache = $this->getBackchannelLogoutCachePool();
}
if (is_string($backchannelLogoutCache)) {
$backchannelLogoutCache = app(trim($backchannelLogoutCache));
}
$config['backchannelLogoutCache'] = $backchannelLogoutCache instanceof CacheItemPoolInterface ? $backchannelLogoutCache : null;
return $config;
}
protected function bootManagementTokenCache(array $config): array
{
$managementTokenCache = $config['managementTokenCache'] ?? null;
$this->getManagementTokenCachePool();
// if (false === $managementTokenCache) {
// unset($config['managementTokenCache']);
// return $config;
// }
// if (null === $managementTokenCache) {
// $managementTokenCache = $this->getManagementTokenCachePool();
// }
// if (is_string($managementTokenCache)) {
// $managementTokenCache = app(trim($managementTokenCache));
// }
$config['managementTokenCache'] = $managementTokenCache instanceof CacheItemPoolInterface ? $managementTokenCache : null;
return $config;
}
protected function bootSessionStorage(array $config): array
{
$sessionStorage = $config['sessionStorage'] ?? null;
$sessionStorageId = $config['sessionStorageId'] ?? 'auth0_session';
if (false === $sessionStorage || 'cookie' === $sessionStorage) {
unset($config['sessionStorage']);
return $config;
}
if (null === $sessionStorage) {
$sessionStorage = app(SessionBridge::class, [
'prefix' => $sessionStorageId,
]);
}
if (is_string($sessionStorage)) {
$sessionStorage = app(trim($sessionStorage), [
'prefix' => $sessionStorageId,
]);
}
$config['sessionStorage'] = $sessionStorage instanceof StoreInterface ? $sessionStorage : null;
return $config;
}
protected function bootStrategy(array $config): array
{
$strategy = $config['strategy'] ?? SdkConfiguration::STRATEGY_REGULAR;
if (! is_string($strategy)) {
$strategy = SdkConfiguration::STRATEGY_REGULAR;
}
$config['strategy'] = $strategy;
return $config;
}
protected function bootTokenCache(array $config): array
{
$tokenCache = $config['tokenCache'] ?? null;
if (false === $tokenCache) {
unset($config['tokenCache']);
return $config;
}
if (null === $tokenCache) {
$tokenCache = $this->getTokenCachePool();
}
if (is_string($tokenCache)) {
$tokenCache = app(trim($tokenCache));
}
$config['tokenCache'] = $tokenCache instanceof CacheItemPoolInterface ? $tokenCache : null;
return $config;
}
protected function bootTransientStorage(array $config): array
{
$transientStorage = $config['transientStorage'] ?? null;
$transientStorageId = $config['transientStorageId'] ?? 'auth0_transient';
if (false === $transientStorage || 'cookie' === $transientStorage) {
unset($config['transientStorage']);
return $config;
}
if (null === $transientStorage) {
$transientStorage = app(SessionBridge::class, [
'prefix' => $transientStorageId,
]);
}
if (is_string($transientStorage)) {
$transientStorage = app(trim($transientStorage), [
'prefix' => $transientStorageId,
]);
}
$config['transientStorage'] = $transientStorage instanceof StoreInterface ? $transientStorage : null;
return $config;
}
protected function createConfiguration(
array $configuration,
): SdkConfiguration {
Events::dispatch(new BuildingConfigurationEvent($configuration));
$configuration = $this->bootStrategy($configuration);
$configuration = $this->bootTokenCache($configuration);
$configuration = $this->bootManagementTokenCache($configuration);
if (in_array($configuration['strategy'], SdkConfiguration::STRATEGIES_USING_SESSIONS, true)) {
$configuration = $this->bootSessionStorage($configuration);
$configuration = $this->bootTransientStorage($configuration);
}
$sdkConfiguration = new SdkConfiguration($configuration);
Events::dispatch(new BuiltConfigurationEvent($sdkConfiguration));
return $sdkConfiguration;
}
protected function getBackchannelLogoutCachePool(): CacheItemPoolInterface
{
if (! $this->backchannelLogoutCachePool instanceof CacheItemPoolInterface) {
$this->backchannelLogoutCachePool = app(CacheBridge::class);
}
return $this->backchannelLogoutCachePool;
}
protected function getManagementTokenCachePool(): CacheItemPoolInterface
{
if (! $this->managementTokenCachePool instanceof CacheItemPoolInterface) {
$this->managementTokenCachePool = app(CacheBridge::class);
}
return $this->managementTokenCachePool;
}
protected function getTokenCachePool(): CacheItemPoolInterface
{
if (! $this->tokenCachePool instanceof CacheItemPoolInterface) {
$this->tokenCachePool = app(CacheBridge::class);
}
return $this->tokenCachePool;
}
/**
* Updates the Auth0 PHP SDK's telemetry to include the correct Laravel markers.
*/
protected function setSdkTelemetry(): self
{
HttpTelemetry::setEnvProperty('Laravel', app()->version());
HttpTelemetry::setPackage('laravel', Service::VERSION);
return $this;
}
}
================================================
FILE: src/Entities/InstanceEntityContract.php
================================================
|SdkConfiguration $configuration
*/
public function setConfiguration(SdkConfiguration | array | null $configuration): self;
/**
* Create/return instance of the Auth0-PHP SDK.
*
* @param Auth0Interface $sdk
*/
public function setSdk(Auth0Interface $sdk): Auth0Interface;
}
================================================
FILE: src/Entities/InstanceEntityTrait.php
================================================
sdk, $this->configuration);
$this->sdk = null;
$this->configuration = null;
return $this;
}
/**
* @param null|array|SdkConfiguration $configuration
*/
public function setConfiguration(
SdkConfiguration | array | null $configuration = null,
): self {
if (is_array($configuration)) {
$configuration = $this->createConfiguration($configuration);
}
$this->configuration = $configuration;
if ($this->configuration instanceof SdkConfiguration && $this->sdk instanceof Auth0Interface) {
$this->sdk->setConfiguration($this->configuration);
}
return $this;
}
public static function create(
SdkConfiguration | array | null $configuration = null,
?string $guardConfigurationName = null,
): self {
$instance = new self();
if (null !== $guardConfigurationName) {
$instance->setGuardConfigurationKey($guardConfigurationName);
}
if (null !== $configuration) {
$instance->setConfiguration($configuration);
}
return $instance;
}
}
================================================
FILE: src/Events/Auth0EventContract.php
================================================
json_decode(json_encode($this->exception, JSON_THROW_ON_ERROR), true),
'throwException' => $this->throwException,
];
}
}
================================================
FILE: src/Events/AuthenticationFailedContract.php
================================================
, throwException: bool}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/AuthenticationSucceeded.php
================================================
json_decode(json_encode($this->user, JSON_THROW_ON_ERROR), true),
];
}
}
================================================
FILE: src/Events/AuthenticationSucceededContract.php
================================================
}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/Configuration/BuildingConfigurationEvent.php
================================================
$configuration a configuration array for use with the Auth0-PHP SDK
*/
public function __construct(
public array &$configuration,
) {
}
/**
* @psalm-suppress LessSpecificImplementedReturnType
*
* @return array{configuration: mixed}
*/
final public function jsonSerialize(): array
{
return [
'configuration' => json_decode(json_encode($this->configuration, JSON_THROW_ON_ERROR), true),
];
}
}
================================================
FILE: src/Events/Configuration/BuildingConfigurationEventContract.php
================================================
}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/Configuration/BuiltConfigurationEvent.php
================================================
json_decode(json_encode($this->configuration, JSON_THROW_ON_ERROR), true),
];
}
}
================================================
FILE: src/Events/Configuration/BuiltConfigurationEventContract.php
================================================
}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/EventAbstract.php
================================================
$parameters Additional API parameters to be sent with the authentication request.
*/
public function __construct(
public array $parameters = [],
) {
}
/**
* @psalm-suppress LessSpecificImplementedReturnType
*
* @return array{parameters: mixed}
*/
final public function jsonSerialize(): array
{
return [
'parameters' => json_decode(json_encode($this->parameters, JSON_THROW_ON_ERROR), true),
];
}
}
================================================
FILE: src/Events/LoginAttemptingContract.php
================================================
}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/Middleware/StatefulMiddlewareRequest.php
================================================
json_decode(json_encode($this->request, JSON_THROW_ON_ERROR), true),
'guard' => json_decode(json_encode($this->guard, JSON_THROW_ON_ERROR), true),
];
}
}
================================================
FILE: src/Events/Middleware/StatefulMiddlewareRequestContract.php
================================================
, guard: array}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/Middleware/StatelessMiddlewareRequest.php
================================================
json_decode(json_encode($this->request, JSON_THROW_ON_ERROR), true),
'guard' => json_decode(json_encode($this->guard, JSON_THROW_ON_ERROR), true),
];
}
}
================================================
FILE: src/Events/Middleware/StatelessMiddlewareRequestContract.php
================================================
, guard: array}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/TokenExpired.php
================================================
$this->token,
];
}
}
================================================
FILE: src/Events/TokenVerificationAttemptingContract.php
================================================
$this->token,
'exception' => json_decode(json_encode($this->exception, JSON_THROW_ON_ERROR), true),
'throwException' => $this->throwException,
];
}
}
================================================
FILE: src/Events/TokenVerificationFailedContract.php
================================================
, throwException: bool}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events/TokenVerificationSucceeded.php
================================================
$this->token,
'payload' => $this->payload,
];
}
}
================================================
FILE: src/Events/TokenVerificationSucceededContract.php
================================================
}
*/
public function jsonSerialize(): array;
}
================================================
FILE: src/Events.php
================================================
event(self::getName($event), $event));
return;
}
event(self::getName($event), $event);
}
}
public static function framework(object $event): void
{
if (self::$enabled) {
if (self::withoutTelescopeRecording($event::class)) {
self::TELESCOPE::withoutRecording(static fn (): mixed => event($event));
return;
}
event($event);
}
}
private static function getName(EventContract $event): string
{
return match ($event::class) {
AuthenticationFailed::class => self::AUTHENTICATION_FAILED,
AuthenticationSucceeded::class => self::AUTHENTICATION_SUCCEEDED,
BuildingConfigurationEvent::class => self::CONFIGURATION_BUILDING,
BuiltConfigurationEvent::class => self::CONFIGURATION_BUILT,
LoginAttempting::class => self::LOGIN_ATTEMPTING,
StatefulMiddlewareRequest::class => self::MIDDLEWARE_STATEFUL_REQUEST,
StatelessMiddlewareRequest::class => self::MIDDLEWARE_STATELESS_REQUEST,
TokenExpired::class => self::TOKEN_EXPIRED,
TokenRefreshFailed::class => self::TOKEN_REFRESH_FAILED,
TokenRefreshSucceeded::class => self::TOKEN_REFRESH_SUCCEEDED,
TokenVerificationAttempting::class => self::TOKEN_VERIFICATION_ATTEMPTING,
TokenVerificationFailed::class => self::TOKEN_VERIFICATION_FAILED,
TokenVerificationSucceeded::class => self::TOKEN_VERIFICATION_SUCCEEDED,
default => $event::class,
};
}
private static function withoutTelescopeRecording(string $event): bool
{
if (! class_exists(self::TELESCOPE)) {
return false;
}
return match ($event) {
self::CONFIGURATION_BUILDING => true,
self::CONFIGURATION_BUILT => true,
default => false,
};
}
}
================================================
FILE: src/EventsContract.php
================================================
*/
public const AUTHENTICATION_FAILED = AuthenticationFailed::class;
/**
* @var class-string
*/
public const AUTHENTICATION_SUCCEEDED = AuthenticationSucceeded::class;
/**
* @var class-string
*/
public const CONFIGURATION_BUILDING = BuildingConfigurationEvent::class;
/**
* @var class-string
*/
public const CONFIGURATION_BUILT = BuiltConfigurationEvent::class;
/**
* @var class-string
*/
public const LOGIN_ATTEMPTING = LoginAttempting::class;
/**
* @var class-string
*/
public const MIDDLEWARE_STATEFUL_REQUEST = StatefulMiddlewareRequest::class;
/**
* @var class-string
*/
public const MIDDLEWARE_STATELESS_REQUEST = StatelessMiddlewareRequest::class;
/**
* @var class-string
*/
public const TOKEN_EXPIRED = TokenExpired::class;
/**
* @var class-string
*/
public const TOKEN_REFRESH_FAILED = TokenRefreshFailed::class;
/**
* @var class-string
*/
public const TOKEN_REFRESH_SUCCEEDED = TokenRefreshSucceeded::class;
/**
* @var class-string
*/
public const TOKEN_VERIFICATION_ATTEMPTING = TokenVerificationAttempting::class;
/**
* @var class-string
*/
public const TOKEN_VERIFICATION_FAILED = TokenVerificationFailed::class;
/**
* @var class-string
*/
public const TOKEN_VERIFICATION_SUCCEEDED = TokenVerificationSucceeded::class;
/**
* Dispatch an SDK event.
*
* @param EventContract $event The event to dispatch.
*/
public static function dispatch(
EventContract $event,
): void;
/**
* Dispatch a Laravel framework event.
*
* @param object $event The event to dispatch.
*/
public static function framework(
object $event,
): void;
}
================================================
FILE: src/Exceptions/AuthenticationException.php
================================================
isImpersonating()) {
return $this->getImposter();
}
return $this->findSession();
}
public function findSession(): ?CredentialEntityContract
{
if ($this->isImpersonating()) {
return $this->getImposter();
}
$this->getSession();
$session = $this->pullState();
$user = $session?->getUser();
if ($session instanceof CredentialEntityContract && $user instanceof Authenticatable) {
$user = $this->getProvider()->retrieveByCredentials($this->normalizeUserArray($user));
if ($user instanceof Authenticatable) {
$scope = $session->getAccessTokenScope();
$decoded = $session->getAccessTokenDecoded();
/**
* @var array $scope
* @var array $decoded
*/
$credential = CredentialEntity::create(
user: $user,
idToken: $session->getIdToken(),
accessToken: $session->getAccessToken(),
accessTokenDecoded: $decoded,
accessTokenScope: $scope,
accessTokenExpiration: $session->getAccessTokenExpiration(),
refreshToken: $session->getRefreshToken(),
);
return $this->refreshSession($credential);
}
}
return null;
}
public function forgetUser(): self
{
$this->setCredential();
return $this;
}
public function getCredential(): ?CredentialEntityContract
{
if ($this->isImpersonating()) {
return $this->getImposter();
}
if ($this->credential instanceof CredentialEntityContract) {
$currThumbprint = $this->getCredThumbprint($this->sdk()->getCredentials());
if ($currThumbprint !== $this->credThumbprint) {
$updated = $this->findSession();
$this->setCredential($updated);
$this->pushState($updated);
$this->credThumbprint = $currThumbprint;
}
}
return $this->credential;
}
public function hashPasswordForCookie(string $passwordHash): string
{
/** @var string $key */
$key = config('app.key') ?? 'base-key-for-password-hash-mac';
return hash_hmac(
'sha256',
$passwordHash,
$key,
);
}
public function login(
?CredentialEntityContract $credential,
): self {
$this->stopImpersonating();
$this->setCredential($credential);
$this->pushState($credential);
if ($credential instanceof CredentialEntityContract) {
$user = $credential->getUser();
if ($user instanceof Authenticatable) {
Events::framework(new Login(self::class, $user, true));
}
}
return $this;
}
public function logout(): self
{
$user = $this->user();
if ($user instanceof Authenticatable) {
Events::framework(new Logout(self::class, $user));
}
$this->stopImpersonating();
$this->setCredential();
$this->pushState();
$this->forgetUser();
return $this;
}
public function pushState(
?CredentialEntityContract $credential = null,
): self {
$sdk = $this->sdk();
$credential ??= $this->getCredential();
if (! $credential instanceof CredentialEntityContract) {
$sdk->clear(true);
return $this;
}
$user = $credential->getUser();
$idToken = $credential->getIdToken();
$accessToken = $credential->getAccessToken();
$accessTokenScope = $credential->getAccessTokenScope();
$accessTokenExpiration = $credential->getAccessTokenExpiration();
$refreshToken = $credential->getRefreshToken();
if ($user instanceof Authenticatable) {
$update = $this->normalizeUserArray($user);
$current = $sdk->getUser() ?? [];
if (count($update) !== count($current) || [] !== array_diff(array_map('serialize', $update), array_map('serialize', $current))) {
$sdk->setUser($update);
}
}
if (null !== $idToken && $idToken !== $sdk->getIdToken()) {
$sdk->setIdToken($idToken);
}
if (null !== $accessToken && $accessToken !== $sdk->getAccessToken()) {
$sdk->setAccessToken($accessToken);
}
if (null !== $accessTokenScope && $accessTokenScope !== $sdk->getAccessTokenScope()) {
/**
* @var array $accessTokenScope
*/
$sdk->setAccessTokenScope($accessTokenScope);
}
if (null !== $accessTokenExpiration && $accessTokenExpiration !== $sdk->getAccessTokenExpiration()) {
$sdk->setAccessTokenExpiration($accessTokenExpiration);
}
if (null !== $refreshToken && $refreshToken !== $sdk->getRefreshToken()) {
$sdk->setRefreshToken($refreshToken);
}
return $this;
}
public function refreshUser(): void
{
if ($this->isImpersonating()) {
return;
}
if ($this->check()) {
$credential = $this->getCredential();
$accessToken = $credential?->getAccessToken();
if (! $credential instanceof CredentialEntityContract || null === $accessToken) {
return;
}
$response = $this->sdk()->authentication()->userInfo($accessToken);
if (HttpResponse::wasSuccessful($response)) {
$response = HttpResponse::decodeContent($response);
if (! is_array($response)) {
return;
}
$user = $this->getProvider()->retrieveByCredentials($response);
$scope = $credential->getAccessTokenScope();
$decoded = $credential->getAccessTokenDecoded();
/**
* @var array $scope
* @var array $decoded
*/
$this->pushState(CredentialEntity::create(
user: $user,
idToken: $credential->getIdToken(),
accessToken: $credential->getAccessToken(),
accessTokenScope: $scope,
accessTokenDecoded: $decoded,
accessTokenExpiration: $credential->getAccessTokenExpiration(),
refreshToken: $credential->getRefreshToken(),
));
}
}
}
public function setCredential(
?CredentialEntityContract $credential = null,
): self {
$this->stopImpersonating();
$this->credential = $credential;
return $this;
}
/**
* @param CredentialEntityContract $credential
*/
public function setImpersonating(
CredentialEntityContract $credential,
): self {
$this->impersonationSource = self::SOURCE_SESSION;
$this->impersonating = $credential;
return $this;
}
public function setUser(
Authenticatable $user,
): self {
if ($this->isImpersonating()) {
if ($this->getImposter()?->getUser() === $user) {
return $this;
}
$this->stopImpersonating();
}
$credential = $this->getCredential() ?? CredentialEntity::create();
$credential->setUser($user);
$this->setCredential($credential);
$this->pushState($credential);
return $this;
}
public function user(): ?Authenticatable
{
if ($this->isImpersonating()) {
return $this->getImposter()?->getUser();
}
static $lastResponse = null;
/**
* @var ?Authenticatable $lastResponse
*/
// @codeCoverageIgnoreStart
if (class_exists(self::TELESCOPE) && true === config('telescope.enabled')) {
static $depth = 0;
static $lastCalled = null;
/**
* @var int $depth
* @var ?int $lastCalled
*/
if (null === $lastCalled) {
$lastCalled = time();
}
if (time() - $lastCalled > 10) {
$lastResponse = null;
$depth = 0;
}
if ($depth >= 1) {
return $lastResponse;
}
++$depth;
$lastCalled = time();
}
// @codeCoverageIgnoreEnd
$currentUser = $this->getCredential()?->getUser();
if ($currentUser instanceof Authenticatable) {
return $lastResponse = $currentUser;
}
$session = $this->find();
if ($session instanceof CredentialEntityContract) {
$this->login($session);
return $lastResponse = $this->getCredential()?->getUser();
}
return $lastResponse = null;
}
private function getCredThumbprint(?object $credential): null | string
{
if (null === $credential) {
return null;
}
return md5(serialize($credential));
}
private function pullState(): ?CredentialEntityContract
{
$sdk = $this->sdk();
$sdk->refreshState();
$credentials = $sdk->getCredentials();
/** @var mixed $credentials */
if (is_object($credentials) && property_exists($credentials, 'user') && property_exists($credentials, 'idToken') && property_exists($credentials, 'accessToken') && property_exists($credentials, 'accessTokenScope') && property_exists($credentials, 'accessTokenExpiration') && property_exists($credentials, 'refreshToken')) {
$decoded = null;
if (null !== $credentials->accessToken) {
$decoded = (new Parser(new SdkConfiguration(strategy: SdkConfiguration::STRATEGY_NONE), $credentials->accessToken))->export();
}
/**
* @var null|array $decoded
*/
return CredentialEntity::create(
user: new StatefulUser($credentials->user),
idToken: $credentials->idToken,
accessToken: $credentials->accessToken,
accessTokenDecoded: $decoded,
accessTokenScope: $credentials->accessTokenScope,
accessTokenExpiration: $credentials->accessTokenExpiration,
refreshToken: $credentials->refreshToken,
);
}
return null;
}
private function refreshSession(
?CredentialEntityContract $credential,
): ?CredentialEntityContract {
if (! $credential instanceof CredentialEntityContract || true !== $credential->getAccessTokenExpired()) {
return $credential;
}
if (null === $credential->getRefreshToken()) {
return null;
}
try {
$this->sdk()->renew();
$session = $this->pullState();
} catch (Throwable) {
Events::dispatch(new TokenRefreshFailed());
$session = null;
}
if ($session instanceof CredentialEntityContract) {
Events::dispatch(new TokenRefreshSucceeded());
$user = $session->getUser();
// @codeCoverageIgnoreStart
if (! $user instanceof Authenticatable) {
return null;
}
// @codeCoverageIgnoreEnd
$user = $this->getProvider()->retrieveByCredentials($this->normalizeUserArray($user));
if ($user instanceof Authenticatable) {
$decoded = null;
$accessToken = $session->getAccessToken();
if (null !== $accessToken) {
$decoded = (new Parser(new SdkConfiguration(strategy: SdkConfiguration::STRATEGY_NONE), $accessToken))->export();
}
$scope = $session->getAccessTokenScope();
/**
* @var array $scope
* @var null|array $decoded
*/
return CredentialEntity::create(
user: $user,
idToken: $session->getIdToken(),
accessToken: $session->getAccessToken(),
accessTokenDecoded: $decoded,
accessTokenScope: $scope,
accessTokenExpiration: $session->getAccessTokenExpiration(),
refreshToken: $session->getRefreshToken(),
);
}
}
$this->setCredential(null);
$this->pushState();
return null;
}
}
================================================
FILE: src/Guards/AuthenticationGuardContract.php
================================================
isImpersonating()) {
return $this->getImposter();
}
return $this->findToken();
}
public function findToken(): ?CredentialEntityContract
{
if ($this->isImpersonating()) {
return $this->getImposter();
}
/**
* @var \Illuminate\Http\Request $request
*/
$request = app('request');
$token = trim($request->bearerToken() ?? '');
if ('' === $token) {
return null;
}
$decoded = $this->processToken(
token: $token,
);
/**
* @var null|array $decoded
*/
if (null === $decoded) {
return null;
}
$provider = $this->getProvider();
// @codeCoverageIgnoreStart
if (! $provider instanceof UserProviderContract) {
return null;
}
// @codeCoverageIgnoreEnd
$user = $provider->getRepository()->fromAccessToken(
user: $decoded,
);
// @codeCoverageIgnoreStart
if (! $user instanceof Authenticatable) {
return null;
}
// @codeCoverageIgnoreEnd
$data = $this->normalizeUserArray($user);
// @codeCoverageIgnoreStart
if ([] === $data) {
return null;
}
// @codeCoverageIgnoreEnd
$scope = isset($data['scope']) && is_string($data['scope']) ? explode(' ', $data['scope']) : [];
$exp = isset($data['exp']) && is_numeric($data['exp']) ? (int) $data['exp'] : null;
return CredentialEntity::create(
user: $user,
accessToken: $token,
accessTokenScope: $scope,
accessTokenExpiration: $exp,
accessTokenDecoded: $decoded,
);
}
public function forgetUser(): self
{
$this->setCredential();
return $this;
}
public function getCredential(): ?CredentialEntityContract
{
if ($this->isImpersonating()) {
return $this->getImposter();
}
return $this->credential;
}
public function login(
?CredentialEntityContract $credential,
): self {
$this->stopImpersonating();
$this->setCredential($credential);
return $this;
}
public function logout(): self
{
$this->stopImpersonating();
$this->setCredential();
$this->forgetUser();
return $this;
}
public function refreshUser(): void
{
if ($this->isImpersonating()) {
return;
}
if ($this->check()) {
$credential = $this->getCredential();
$accessToken = $credential?->getAccessToken();
if (! $credential instanceof CredentialEntityContract || null === $accessToken) {
return;
}
$response = $this->sdk()->authentication()->userInfo($accessToken);
if (HttpResponse::wasSuccessful($response)) {
$response = HttpResponse::decodeContent($response);
if (! is_array($response)) {
return;
}
$user = $this->getProvider()->retrieveByCredentials($response);
$scope = $credential->getAccessTokenScope();
/**
* @var array $scope
*/
$this->setCredential(CredentialEntity::create(
user: $user,
idToken: $credential->getIdToken(),
accessToken: $credential->getAccessToken(),
accessTokenScope: $scope,
accessTokenExpiration: $credential->getAccessTokenExpiration(),
refreshToken: $credential->getRefreshToken(),
));
}
}
}
public function setCredential(
?CredentialEntityContract $credential = null,
): self {
$this->stopImpersonating();
$this->credential = $credential;
return $this;
}
/**
* @param CredentialEntityContract $credential
*/
public function setImpersonating(
CredentialEntityContract $credential,
): self {
$this->impersonationSource = self::SOURCE_TOKEN;
$this->impersonating = $credential;
return $this;
}
public function setUser(
Authenticatable $user,
): self {
if ($this->isImpersonating()) {
if ($this->getImposter()?->getUser() === $user) {
return $this;
}
$this->stopImpersonating();
}
$credential = $this->getCredential() ?? CredentialEntity::create();
$credential->setUser($user);
$this->setCredential($credential);
return $this;
}
public function user(): ?Authenticatable
{
if ($this->isImpersonating()) {
return $this->getImposter()?->getUser();
}
$currentUser = $this->getCredential()?->getUser();
if ($currentUser instanceof Authenticatable) {
return $currentUser;
}
$token = $this->find();
if ($token instanceof CredentialEntityContract) {
$this->login($token);
return $this->getCredential()?->getUser();
}
return null;
}
}
================================================
FILE: src/Guards/AuthorizationGuardContract.php
================================================
user()) instanceof Authenticatable) {
return $user;
}
throw new AuthenticationException(AuthenticationException::UNAUTHENTICATED);
}
final public function check(): bool
{
return $this->hasUser();
}
final public function getImposter(): ?CredentialEntityContract
{
return $this->impersonating;
}
final public function getImposterSource(): ?int
{
return $this->impersonationSource;
}
final public function getName(): string
{
return $this->name;
}
final public function getProvider(): UserProvider
{
if ($this->provider instanceof UserProvider) {
return $this->provider;
}
$providerName = $this->config['provider'] ?? '';
if (! is_string($providerName) || '' === $providerName) {
// @codeCoverageIgnoreStart
throw new GuardException(GuardExceptionContract::USER_PROVIDER_UNCONFIGURED);
// @codeCoverageIgnoreEnd
}
$providerName = trim($providerName);
/**
* @var \Illuminate\Auth\AuthManager $auth
*/
$auth = app('auth');
$provider = $auth->createUserProvider($providerName);
if ($provider instanceof UserProvider) {
$this->provider = $provider;
return $provider;
}
// @codeCoverageIgnoreStart
throw new GuardException(sprintf(GuardExceptionContract::USER_PROVIDER_UNAVAILABLE, $providerName));
// @codeCoverageIgnoreEnd
}
final public function getRefreshedUser(): ?Authenticatable
{
$this->refreshUser();
return $this->user();
}
final public function getSession(): Session
{
if (! $this->session instanceof Session) {
/**
* @var \Illuminate\Session\Store $store
*/
$store = app('session.store');
/**
* @var \Illuminate\Http\Request $request
*/
$request = app('request');
if (! $request->hasSession(true)) {
$request->setLaravelSession($store);
}
if (! $store->isStarted()) {
$store->start();
}
$this->session = $store;
}
return $this->session;
}
final public function guest(): bool
{
return ! $this->check();
}
final public function hasPermission(
string $permission,
?CredentialEntityContract $credential = null,
): bool {
$permission = trim($permission);
if ('*' === $permission) {
return true;
}
$available = $credential?->getAccessTokenDecoded() ?? $this->getCredential()?->getAccessTokenDecoded() ?? [];
$available = $available['permissions'] ?? [];
/**
* @var mixed $available
*/
if (! is_array($available) || [] === $available) {
return false;
}
return in_array($permission, $available, true);
}
final public function hasScope(
string $scope,
?CredentialEntityContract $credential = null,
): bool {
$scope = trim($scope);
if ('*' === $scope) {
return true;
}
$available = $credential?->getAccessTokenScope() ?? $this->getCredential()?->getAccessTokenScope() ?? [];
if ([] !== $available) {
return in_array($scope, $available, true);
}
return false;
}
final public function hasUser(): bool
{
return $this->user() instanceof Authenticatable;
}
final public function id(): string | null
{
$user = $this->user()?->getAuthIdentifier();
if (is_string($user) || is_int($user)) {
return (string) $user;
}
return null;
}
final public function isImpersonating(): bool
{
return $this->impersonating instanceof CredentialEntityContract;
}
final public function management(): ManagementInterface
{
return $this->sdk()->management();
}
final public function processToken(
string $token,
): ?array {
Events::dispatch($event = new TokenVerificationAttempting($token));
$token = $event->token;
$decoded = null;
try {
$decoded = $this->sdk()->decode(token: $token, tokenType: Token::TYPE_ACCESS_TOKEN)->toArray();
} catch (InvalidTokenException $invalidTokenException) {
Events::dispatch($event = new TokenVerificationFailed($token, $invalidTokenException));
if ($event->throwException) {
// @codeCoverageIgnoreStart
throw $invalidTokenException;
// @codeCoverageIgnoreEnd
}
return null;
}
Events::dispatch(new TokenVerificationSucceeded($token, $decoded));
return $decoded;
}
final public function sdk(
bool $reset = false,
): Auth0Interface {
if (! $this->sdk instanceof InstanceEntityContract || $reset) {
$configurationName = $this->config['configuration'] ?? $this->name;
$this->sdk = InstanceEntity::create(
guardConfigurationName: $configurationName,
);
}
return $this->sdk->getSdk();
}
/**
* @codeCoverageIgnore
*/
final public function service(): ?InstanceEntityContract
{
return $this->sdk;
}
final public function stopImpersonating(): void
{
$this->impersonating = null;
$this->impersonationSource = null;
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @param array $credentials
*/
final public function validate(
array $credentials = [],
): bool {
return false;
}
final public function viaRemember(): bool
{
return false;
}
abstract public function find(): ?CredentialEntityContract;
abstract public function forgetUser(): self;
abstract public function getCredential(): ?CredentialEntityContract;
abstract public function login(?CredentialEntityContract $credential): GuardContract;
abstract public function logout(): GuardContract;
abstract public function refreshUser(): void;
abstract public function setCredential(?CredentialEntityContract $credential = null): GuardContract;
/**
* Toggle the Guard's impersonation state. This should only be used by the Impersonate trait, and is not intended for use by end-users. It is public to allow for testing.
*
* @param CredentialEntityContract $credential
*/
abstract public function setImpersonating(
CredentialEntityContract $credential,
): self;
abstract public function setUser(
Authenticatable $user,
): self;
abstract public function user(): ?Authenticatable;
/**
* Normalize a user model object for easier storage or comparison.
*
* @param Authenticatable $user User model object.
*
* @throws Exception If the user model object cannot be normalized.
*
* @return array Normalized user model object.
*
* @psalm-suppress TypeDoesNotContainType, UndefinedDocblockClass, UndefinedInterfaceMethod
*
* @codeCoverageIgnore
*/
protected function normalizeUserArray(
Authenticatable $user,
): array {
$response = null;
$implements = class_implements($user, true);
$implements = is_array($implements) ? $implements : [];
if (isset($implements[JsonSerializable::class])) {
/**
* @var JsonSerializable $user
*/
$response = json_encode($user->jsonSerialize(), JSON_THROW_ON_ERROR);
}
if (null === $response && isset($implements[Jsonable::class])) {
/**
* @var Jsonable $user
*/
$response = $user->toJson();
}
if (null === $response && isset($implements[Arrayable::class])) {
/**
* @var Arrayable $user
*/
try {
$response = json_encode($user->toArray(), JSON_THROW_ON_ERROR);
} catch (Exception) {
}
}
// if (null === $response && (new ReflectionClass($user))->hasMethod('attributesToArray')) {
// try {
// // @phpstan-ignore-next-line
// $response = json_encode($user->attributesToArray(), JSON_THROW_ON_ERROR);
// } catch (\Exception) {
// }
// }
if (is_string($response)) {
try {
$response = json_decode($response, true, 512, JSON_THROW_ON_ERROR);
if (is_array($response) && [] !== $response) {
/**
* @var array $response
*/
return $response;
}
} catch (Exception) {
}
}
throw new GuardException(GuardExceptionContract::USER_MODEL_NORMALIZATION_FAILURE);
}
}
================================================
FILE: src/Guards/GuardContract.php
================================================
*/
public function processToken(
string $token,
): ?array;
/**
* Query the /userinfo endpoint and update the currently authenticated user for the guard.
*/
public function refreshUser(): void;
/**
* Get an Auth0 PHP SDK instance.
*
* @param bool $reset Optional. Whether to reset the SDK instance.
*
* @throws BindingResolutionException If the Auth0 class cannot be resolved.
* @throws NotFoundExceptionInterface If the Auth0 service cannot be found.
* @throws ContainerExceptionInterface If the Auth0 service cannot be resolved.
*
* @return Auth0Interface Auth0 PHP SDK instance.
*/
public function sdk(
bool $reset = false,
): Auth0Interface;
/**
* Sets the guard's currently configured credential.
*
* @param null|CredentialEntityContract $credential Optional. The credential to assign.
*/
public function setCredential(?CredentialEntityContract $credential = null): self;
/**
* Toggle the Guard's impersonation state. This should only be used by the Impersonate trait, and is not intended for use by end-users. It is public to allow for testing.
*
* @param CredentialEntityContract $credential
*/
public function setImpersonating(
CredentialEntityContract $credential,
): self;
/**
* Sets the currently authenticated user for the guard. This method will replace the current user of an existing credential, if one is set, or establish a new one. If an existing credential uses a session source, the session will be updated.
*
* @param Authenticatable $user The user to set as authenticated.
*/
public function setUser(
Authenticatable $user,
): self;
/**
* Stop impersonating a user.
*/
public function stopImpersonating(): void;
/**
* Returns the currently authenticated user for the guard, if available.
*/
public function user(): ?Authenticatable;
/**
* This method is not currently implemented, but is required by Laravel's Guard contract.
*
* @param array $credentials
*/
public function validate(
array $credentials = [],
): bool;
/**
* This method is not currently implemented, but is required by Laravel's Guard contract.
*/
public function viaRemember(): bool;
}
================================================
FILE: src/Middleware/AuthenticateMiddleware.php
================================================
guard();
$scope = trim($scope);
if (! $guard instanceof GuardContract) {
abort(Response::HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error');
}
Events::dispatch(new StatefulMiddlewareRequest($request, $guard));
$credential = $guard->find(GuardContract::SOURCE_SESSION);
if ($credential instanceof CredentialEntityContract) {
if ('' === $scope || $guard->hasScope($scope, $credential)) {
$guard->login($credential);
return $next($request);
}
abort(Response::HTTP_FORBIDDEN, 'Forbidden');
}
return redirect()
->setIntendedUrl($request->fullUrl())
->to('/login'); // @phpstan-ignore-line
}
}
================================================
FILE: src/Middleware/AuthenticateMiddlewareContract.php
================================================
guard();
if (! $guard instanceof GuardContract) {
abort(Response::HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error');
}
Events::dispatch(new StatefulMiddlewareRequest($request, $guard));
$credential = $guard->find(GuardContract::SOURCE_SESSION);
if ($credential instanceof CredentialEntityContract && ('' === $scope || $guard->hasScope($scope, $credential))) {
$guard->login($credential);
}
return $next($request);
}
}
================================================
FILE: src/Middleware/AuthenticateOptionalMiddlewareContract.php
================================================
shouldUse('auth0-session');
return $next($request);
}
}
================================================
FILE: src/Middleware/AuthenticatorMiddlewareContract.php
================================================
guard();
if (! $guard instanceof GuardContract) {
return $next($request);
}
Events::dispatch(new StatelessMiddlewareRequest($request, $guard));
$credential = $guard->find(GuardContract::SOURCE_TOKEN);
if ($credential instanceof CredentialEntityContract) {
if ('' === $scope || $guard->hasScope($scope, $credential)) {
$guard->login($credential);
return $next($request);
}
abort(Response::HTTP_FORBIDDEN, 'Forbidden');
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
}
}
================================================
FILE: src/Middleware/AuthorizeMiddlewareContract.php
================================================
guard();
if (! $guard instanceof GuardContract) {
return $next($request);
}
Events::dispatch(new StatelessMiddlewareRequest($request, $guard));
$credential = $guard->find(GuardContract::SOURCE_TOKEN);
if ($credential instanceof CredentialEntityContract && ('' === $scope || $guard->hasScope($scope, $credential))) {
$guard->login($credential);
return $next($request);
}
return $next($request);
}
}
================================================
FILE: src/Middleware/AuthorizeOptionalMiddlewareContract.php
================================================
shouldUse('auth0-api');
return $next($request);
}
}
================================================
FILE: src/Middleware/AuthorizerMiddlewareContract.php
================================================
defaultGuard = $guard;
}
}
final public function handle(
Request $request,
Closure $next,
?string $guard = null,
): Response {
$guard = trim($guard ?? '');
if ('' === $guard) {
$guard = $this->defaultGuard;
}
auth()->shouldUse($guard);
return $next($request);
}
}
================================================
FILE: src/Middleware/GuardMiddlewareContract.php
================================================
*/
final public static function json(ResponseInterface $response): ?array
{
if (! in_array($response->getStatusCode(), [200, 201], true)) {
return null;
}
$json = json_decode((string) $response->getBody(), true);
if (! is_array($json)) {
return null;
}
return $json;
}
/**
* Register the SDK's authentication routes and controllers.
*
* @param string $authenticationGuard The name of the authentication guard to use.
*/
final public static function routes(
string $authenticationGuard = 'auth0-session',
): void {
Route::group(['middleware' => ['web', 'guard:' . $authenticationGuard]], static function (): void {
Route::get(Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_LOGIN) ?? '/login', LoginController::class)->name('login');
Route::get(Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_LOGOUT) ?? '/logout', LogoutController::class)->name('logout');
Route::get(Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_CALLBACK) ?? '/callback', CallbackController::class)->name('callback');
});
}
}
================================================
FILE: src/ServiceContract.php
================================================
mergeConfigFrom(implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'config', 'auth0.php']), 'auth0');
$this->publishes([implode(DIRECTORY_SEPARATOR, [__DIR__, '..', 'config', 'auth0.php']) => config_path('auth0.php')], 'auth0');
$auth->extend('auth0.authenticator', fn (Application $app, string $name, array $config): AuthenticationGuard => new AuthenticationGuard($name, $config));
$auth->extend('auth0.authorizer', fn (Application $app, string $name, array $config): AuthorizationGuard => new AuthorizationGuard($name, $config));
$auth->provider('auth0.provider', static fn (Application $app, array $config): UserProvider => new UserProvider($config));
$router->aliasMiddleware('guard', GuardMiddleware::class);
$gate->define('scope', static function (Authenticatable $user, string $scope, ?GuardContract $guard = null): bool {
$guard ??= auth()->guard();
if (! $guard instanceof GuardContract) {
return false;
}
return $guard->hasScope($scope);
});
$gate->define('permission', static function (Authenticatable $user, string $permission, ?GuardContract $guard = null): bool {
$guard ??= auth()->guard();
if (! $guard instanceof GuardContract) {
return false;
}
return $guard->hasPermission($permission);
});
$gate->before(static function (?Authenticatable $user, ?string $ability) {
$guard = auth()->guard();
if (! $guard instanceof GuardContract || ! $user instanceof Authenticatable || ! is_string($ability)) {
return;
}
if (str_starts_with($ability, 'scope:')) {
if ($guard->hasScope(substr($ability, 6))) {
return Response::allow();
}
return Response::deny();
}
if (str_contains($ability, ':')) {
if ($guard->hasPermission($ability)) {
return Response::allow();
}
return Response::deny();
}
});
$this->registerDeprecated($router, $auth);
$this->registerMiddleware($router);
$this->registerRoutes();
return $this;
}
final public function provides()
{
return [
Auth0::class,
AuthenticateMiddleware::class,
AuthenticateOptionalMiddleware::class,
AuthenticationGuard::class,
AuthenticatorMiddleware::class,
AuthorizationGuard::class,
AuthorizeMiddleware::class,
AuthorizeOptionalMiddleware::class,
AuthorizerMiddleware::class,
CacheBridge::class,
CacheItemBridge::class,
CallbackController::class,
Configuration::class,
Guard::class,
GuardMiddleware::class,
LoginController::class,
LogoutController::class,
Service::class,
SessionBridge::class,
UserProvider::class,
UserRepository::class,
];
}
final public function register(): self
{
$this->registerGuards();
$this->app->singleton(Auth0::class, static fn (): Service => new Service());
$this->app->singleton(Service::class, static fn (): Service => new Service());
$this->app->singleton(Configuration::class, static fn (): Configuration => new Configuration());
$this->app->singleton(Service::class, static fn (): Service => new Service());
$this->app->singleton(AuthenticatorMiddleware::class, static fn (): AuthenticatorMiddleware => new AuthenticatorMiddleware());
$this->app->singleton(AuthorizerMiddleware::class, static fn (): AuthorizerMiddleware => new AuthorizerMiddleware());
$this->app->singleton(AuthenticateMiddleware::class, static fn (): AuthenticateMiddleware => new AuthenticateMiddleware());
$this->app->singleton(AuthenticateOptionalMiddleware::class, static fn (): AuthenticateOptionalMiddleware => new AuthenticateOptionalMiddleware());
$this->app->singleton(AuthorizeMiddleware::class, static fn (): AuthorizeMiddleware => new AuthorizeMiddleware());
$this->app->singleton(AuthorizeOptionalMiddleware::class, static fn (): AuthorizeOptionalMiddleware => new AuthorizeOptionalMiddleware());
$this->app->singleton(GuardMiddleware::class, static fn (): GuardMiddleware => new GuardMiddleware());
$this->app->singleton(CallbackController::class, static fn (): CallbackController => new CallbackController());
$this->app->singleton(LoginController::class, static fn (): LoginController => new LoginController());
$this->app->singleton(LogoutController::class, static fn (): LogoutController => new LogoutController());
$this->app->singleton(UserProvider::class, static fn (): UserProvider => new UserProvider());
$this->app->singleton(UserRepository::class, static fn (): UserRepository => new UserRepository());
$this->app->singleton('auth0', static fn (): Service => app(Service::class));
$this->app->singleton('auth0.repository', static fn (): UserRepository => app(UserRepository::class));
return $this;
}
final public function registerDeprecated(
Router $router,
AuthManager $auth,
): void {
$auth->extend('auth0.guard', fn (Application $app, string $name, array $config): Guard => new Guard($name, $config));
$router->aliasMiddleware('auth0.authenticate.optional', AuthenticateOptionalMiddleware::class);
$router->aliasMiddleware('auth0.authenticate', AuthenticateMiddleware::class);
$router->aliasMiddleware('auth0.authorize.optional', AuthorizeOptionalMiddleware::class);
$router->aliasMiddleware('auth0.authorize', AuthorizeMiddleware::class);
}
/**
* @codeCoverageIgnore
*/
final public function registerGuards(): void
{
if (true === config('auth0.registerGuards')) {
if (null === config('auth.guards.auth0-session')) {
config([
'auth.guards.auth0-session' => [
'driver' => 'auth0.authenticator',
'configuration' => 'web',
'provider' => 'auth0-provider',
],
]);
}
if (null === config('auth.guards.auth0-api')) {
config([
'auth.guards.auth0-api' => [
'driver' => 'auth0.authorizer',
'configuration' => 'api',
'provider' => 'auth0-provider',
],
]);
}
if (null === config('auth.providers.auth0-provider')) {
config([
'auth.providers.auth0-provider' => [
'driver' => 'auth0.provider',
'repository' => 'auth0.repository',
],
]);
}
}
}
/**
* @codeCoverageIgnore
*
* @param Router $router
*/
final public function registerMiddleware(
Router $router,
): void {
if (true === config('auth0.registerMiddleware')) {
$kernel = $this->app->make(Kernel::class);
/**
* @var \Illuminate\Foundation\Http\Kernel $kernel
*/
$kernel->appendMiddlewareToGroup('web', AuthenticatorMiddleware::class);
$kernel->prependToMiddlewarePriority(AuthenticatorMiddleware::class);
$kernel->appendMiddlewareToGroup('api', AuthorizerMiddleware::class);
$kernel->prependToMiddlewarePriority(AuthorizerMiddleware::class);
}
}
final public function registerRoutes(): void
{
if (true === config('auth0.registerAuthenticationRoutes')) {
Route::group(['middleware' => 'web'], static function (): void {
Route::get(Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_LOGIN) ?? '/login', LoginController::class)->name('login');
Route::get(Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_LOGOUT) ?? '/logout', LogoutController::class)->name('logout');
Route::get(Configuration::string(Configuration::CONFIG_NAMESPACE_ROUTES . Configuration::CONFIG_ROUTE_CALLBACK) ?? '/callback', CallbackController::class)->name('callback');
});
}
}
}
================================================
FILE: src/ServiceProviderContract.php
================================================
'some-auth0-user-id',
'azp' => 'some-auth0-application-client-id',
'scope' => '',
];
/**
* Set the currently logged in user for the application. Only intended for unit testing.
*
* @param array $attributes The attributes to use for the user.
* @param null|string $guard The guard to impersonate with.
* @param ?int $source
*/
public function actingAsAuth0User(
array $attributes = [],
?string $guard = null,
?int $source = GuardContract::SOURCE_TOKEN,
): self {
$issued = time();
$expires = $issued + 60 * 60;
$timestamps = ['iat' => $issued, 'exp' => $expires];
$attributes = array_merge($this->defaultActingAsAttributes, $timestamps, $attributes);
$scope = $attributes['scope'] ? explode(' ', $attributes['scope']) : [];
unset($attributes['scope']);
$instance = auth()->guard($guard);
if (! $instance instanceof GuardContract) {
$user = new ImposterUser($attributes);
return $this->actingAs($user, $guard);
}
$provider = new UserProvider();
if (GuardContract::SOURCE_SESSION === $source) {
$user = $provider->getRepository()->fromSession($attributes);
} else {
$user = $provider->getRepository()->fromAccessToken($attributes);
}
$credential = CredentialEntity::create(
user: $user,
accessTokenScope: $scope,
);
$instance->setImpersonating($credential, $source);
return $this->actingAs($user, $guard);
}
abstract public function actingAs(Authenticatable $user, $guard = null);
}
================================================
FILE: src/Traits/Impersonate.php
================================================
impersonateSession($credential, $guard);
}
if (GuardContract::SOURCE_TOKEN === $source || null === $source) {
$this->impersonateToken($credential, $guard);
}
return $this;
}
/**
* Pretend to be an authenticated user for the request. Only intended for unit testing.
*
* @param CredentialEntityContract $credential The Credential to impersonate.
* @param null|string $guard The guard to impersonate with.
*
* @return $this The current test case instance.
*/
public function impersonateSession(
CredentialEntityContract $credential,
?string $guard = null,
): self {
$instance = auth()->guard($guard);
$user = $credential->getUser() ?? new ImposterUser([]);
if ($instance instanceof GuardContract) {
$instance->setImpersonating($credential, GuardContract::SOURCE_SESSION);
}
return $this->actingAs($user, $guard);
}
/**
* Pretend to be a bearer token-established stateless user for the request. Only intended for unit testing.
*
* @param CredentialEntityContract $credential The Credential to impersonate.
* @param null|string $guard The guard to impersonate with.
*
* @return $this The current test case instance.
*/
public function impersonateToken(
CredentialEntityContract $credential,
?string $guard = null,
): self {
$instance = auth()->guard($guard);
$user = $credential->getUser() ?? new ImposterUser([]);
if ($instance instanceof GuardContract) {
$instance->setImpersonating($credential, GuardContract::SOURCE_TOKEN);
}
return $this->actingAs($user, $guard);
}
abstract public function actingAs(Authenticatable $user, $guard = null);
}
================================================
FILE: src/UserProvider.php
================================================
repository ?? $this->resolveRepository();
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @param Authenticatable $user
* @param array $credentials
* @param bool $force
*
* @codeCoverageIgnore
*/
final public function rehashPasswordIfRequired(Authenticatable $user, array $credentials, bool $force = false): void
{
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @param array $credentials
*/
final public function retrieveByCredentials(array $credentials): ?Authenticatable
{
if ([] === $credentials) {
return null;
}
$hash = hash('sha256', json_encode($credentials, JSON_THROW_ON_ERROR) ?: ''); /** @phpstan-ignore-line */
$cached = $this->withoutRecording(static fn (): mixed => Cache::get('auth0_sdk_credential_lookup_' . $hash));
if ($cached instanceof Authenticatable) {
return $cached;
}
static $lastResponse = null;
static $lastCredentials = null;
// @codeCoverageIgnoreStart
/**
* @var ?Authenticatable $lastResponse
* @var array $lastCredentials
*/
if ($lastCredentials === $credentials) {
return $lastResponse;
}
if (class_exists(self::TELESCOPE) && true === config('telescope.enabled')) {
static $depth = 0;
static $lastCalled = null;
/**
* @var int $depth
* @var ?int $lastCalled
*/
if (null === $lastCalled) {
$lastCalled = time();
}
if ($lastCredentials !== $credentials || time() - $lastCalled > 10) {
$lastResponse = null;
$depth = 0;
}
if ($depth >= 1) {
return $lastResponse;
}
++$depth;
$lastCalled = time();
$lastCredentials = $credentials;
}
// @codeCoverageIgnoreEnd
$lastResponse = $this->getRepository()->fromSession($credentials);
$this->withoutRecording(static fn (): bool => Cache::put('auth0_sdk_credential_lookup_' . $hash, $lastResponse, 5));
return $lastResponse;
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @codeCoverageIgnore
*
* @param mixed $identifier
*/
final public function retrieveById($identifier): ?Authenticatable
{
return null;
}
/**
* @psalm-suppress DocblockTypeContradiction
*
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @param mixed $identifier
* @param mixed $token
*/
final public function retrieveByToken($identifier, $token): ?Authenticatable
{
// @phpstan-ignore-next-line
if (! is_string($token)) {
return null;
}
$guard = auth()->guard();
if (! $guard instanceof GuardContract) {
return null;
}
$user = $guard->processToken($token);
return null !== $user ? $this->getRepository()->fromAccessToken($user) : null;
}
final public function setRepository(string $repository): void
{
$this->resolveRepository($repository);
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @codeCoverageIgnore
*
* @param Authenticatable $user
* @param mixed $token
*/
final public function updateRememberToken(Authenticatable $user, $token): void
{
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @param Authenticatable $user
* @param array $credentials
*/
final public function validateCredentials(
Authenticatable $user,
array $credentials,
): bool {
return false;
}
protected function getConfiguration(
string $key,
): array | string | null {
return $this->config[$key] ?? null;
}
protected function getRepositoryName(): string
{
return $this->repositoryName;
}
protected function resolveRepository(
?string $repositoryName = null,
): UserRepositoryContract {
$model = $repositoryName;
$model ??= $this->getConfiguration('model');
$model ??= $this->getConfiguration('repository');
$model ??= UserRepository::class;
if ($model === $this->getRepositoryName()) {
return $this->getRepository();
}
if (! is_string($model)) {
throw new BindingResolutionException('The configured Repository could not be loaded.');
}
if (! app()->bound($model)) {
try {
app()->make($model);
} catch (BindingResolutionException) {
throw new BindingResolutionException(sprintf('The configured Repository %s could not be loaded.', $model));
}
}
$this->setRepositoryName($model);
/**
* @var UserRepositoryContract $repository
*/
$repository = app($model);
return $this->repository = $repository;
}
protected function setConfiguration(
string $key,
string $value,
): void {
$this->config[$key] = $value;
}
protected function setRepositoryName(string $repositoryName): void
{
$this->setConfiguration('model', $repositoryName);
$this->repositoryName = $repositoryName;
}
/**
* @codeCoverageIgnore
*
* @param callable $callback
*/
protected function withoutRecording(callable $callback): mixed
{
if (class_exists(self::TELESCOPE)) {
return self::TELESCOPE::withoutRecording($callback);
}
return $callback();
}
}
================================================
FILE: src/UserProviderContract.php
================================================
fill($attributes);
}
public function __get(string $key): mixed
{
return $this->getAttribute($key);
}
public function __set(string $key, mixed $value): void
{
$this->setAttribute($key, $value);
}
final public function getAttribute(string $key, mixed $default = null): mixed
{
return $this->attributes[$key] ?? $default;
}
final public function getAttributes(): mixed
{
return $this->attributes;
}
final public function getAuthIdentifier(): int | string | null
{
return $this->attributes['sub'] ?? $this->attributes['user_id'] ?? $this->attributes['email'] ?? null;
}
final public function getAuthIdentifierName(): string
{
return 'id';
}
final public function getAuthPassword(): string
{
return '';
}
final public function getAuthPasswordName(): string
{
return 'password';
}
final public function getRememberToken(): string
{
return '';
}
final public function getRememberTokenName(): string
{
return '';
}
final public function jsonSerialize(): mixed
{
return $this->attributes;
}
/**
* @phpcsSuppress SlevomatCodingStandard.Functions.UnusedParameter
*
* @param mixed $value
*/
final public function setRememberToken(mixed $value): void
{
}
abstract public function fill(array $attributes): self;
abstract public function setAttribute(string $key, mixed $value): self;
}
================================================
FILE: src/Users/UserContract.php
================================================
$value) {
$this->setAttribute($key, $value);
}
return $this;
}
final public function setAttribute(string $key, mixed $value): self
{
$this->attributes[$key] = $value;
return $this;
}
}
================================================
FILE: tests/Pest.php
================================================
in(__DIR__);
// uses()->beforeAll(function (): void {
// })->in(__DIR__);
uses()->beforeEach(function (): void {
$this->events = [];
Event::listen('*', function ($event) {
$this->events[] = $event;
});
Cache::flush();
config()->set('auth', [
'defaults' => [
'guard' => 'legacyGuard',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'legacyGuard' => [
'driver' => 'auth0.guard',
'configuration' => 'web',
'provider' => 'auth0-provider',
],
'auth0-session' => [
'driver' => 'auth0.authenticator',
'configuration' => 'web',
'provider' => 'auth0-provider',
],
'auth0-api' => [
'driver' => 'auth0.authorizer',
'configuration' => 'api',
'provider' => 'auth0-provider',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'auth0-provider' => [
'driver' => 'auth0.provider',
'repository' => 'auth0.repository',
],
],
]);
})->in(__DIR__);
// uses()->afterEach(function (): void {
// $commands = ['optimize:clear'];
// foreach ($commands as $command) {
// Artisan::call($command);
// }
// })->in(__DIR__);
uses()->compact();
/*
|--------------------------------------------------------------------------
| Expectations
|--------------------------------------------------------------------------
|
| When you're writing tests, you often need to check that values meet certain conditions. The
| "expect()" function gives you access to a set of "expectations" methods that you can use
| to assert different things. Of course, you may extend the Expectation API at any time.
|
*/
// expect()->extend('toBeOne', function () {
// return $this->toBe(1);
// });
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
| project that you don't want to repeat in every file. Here you can also expose helpers as
| global functions to help you to reduce the number of lines of code in your test files.
|
*/
// function something()
// {
// // ..
// }
function mockIdToken(
string $algorithm = Token::ALGO_RS256,
array $claims = [],
array $headers = []
): string {
$secret = createRsaKeys()->private;
$claims = array_merge([
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
'sub' => 'hello|world',
'aud' => config('auth0.guards.default.clientId'),
'exp' => time() + 60,
'iat' => time(),
'email' => 'john.doe@somewhere.test'
], $claims);
return (string) Generator::create($secret, $algorithm, $claims, $headers);
}
function mockAccessToken(
string $algorithm = Token::ALGO_RS256,
array $claims = [],
array $headers = []
): string {
$secret = createRsaKeys()->private;
$claims = array_merge([
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
'sub' => 'hello|world',
'aud' => config('auth0.guards.default.clientId'),
'iat' => time(),
'exp' => time() + 60,
'azp' => config('auth0.guards.default.clientId'),
'scope' => 'openid profile email',
], $claims);
return (string) Generator::create($secret, $algorithm, $claims, $headers);
}
function createRsaKeys(
string $digestAlg = 'sha256',
int $keyType = OPENSSL_KEYTYPE_RSA,
int $bitLength = 2048
): object
{
$config = [
'digest_alg' => $digestAlg,
'private_key_type' => $keyType,
'private_key_bits' => $bitLength,
];
$privateKeyResource = openssl_pkey_new($config);
if ($privateKeyResource === false) {
throw new RuntimeException("OpenSSL reported an error: " . getSslError());
}
$export = openssl_pkey_export($privateKeyResource, $privateKey);
if ($export === false) {
throw new RuntimeException("OpenSSL reported an error: " . getSslError());
}
$publicKey = openssl_pkey_get_details($privateKeyResource);
$resCsr = openssl_csr_new([], $privateKeyResource);
$resCert = openssl_csr_sign($resCsr, null, $privateKeyResource, 30);
openssl_x509_export($resCert, $x509);
return (object) [
'private' => $privateKey,
'public' => $publicKey['key'],
'cert' => $x509,
'resource' => $privateKeyResource,
];
}
function getSslError(): string
{
$errors = [];
while ($error = openssl_error_string()) {
$errors[] = $error;
}
return implode(', ', $errors);
}
================================================
FILE: tests/TestCase.php
================================================
set('database.default', 'testbench');
$app['config']->set('database.connections.testbench', [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]);
}
/**
* Asserts that an event was dispatched. Optionally assert the number of times it was dispatched and/or that it was dispatched after another event.
*
* @param string $expectedEvent The event to assert was dispatched.
* @param int $times The number of times the event was expected to be dispatched.
* @param string|null $followingEvent The event that was expected to be dispatched before this event.
*/
protected function assertDispatched(string $expectedEvent, int $times = 0, ?string $followingEvent = null)
{
expect($this->events)
->toBeArray()
->toContain($expectedEvent);
if ($times > 0) {
expect(array_count_values($this->events)[$expectedEvent])
->toBeInt()
->toBe($times);
}
if (null !== $followingEvent) {
expect($this->events)
->toContain($followingEvent);
$indexExpected = array_search($expectedEvent, $this->events);
$indexFollowing = array_search($followingEvent, $this->events);
if ($indexExpected !== false && $indexFollowing !== false) {
expect($indexExpected)
->toBeInt()
->toBeGreaterThan($indexFollowing);
}
}
}
/**
* Asserts that events were dispatched in the order provided. Events not in the array are ignored.
*
* @param array $events Array of events to assert were dispatched in order.
*/
protected function assertDispatchedOrdered(array $events)
{
$previousIndex = -1;
foreach ($events as $event) {
$index = array_search($event, $this->events);
expect($index)
->toBeInt()
->toBeGreaterThan($previousIndex);
$previousIndex = $index;
}
}
}
================================================
FILE: tests/Unit/Auth/GuardStatefulTest.php
================================================
group('auth', 'auth.guard', 'auth.guard.stateful');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = $guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->config = $this->sdk->configuration();
$this->session = $this->config->getSessionStorage();
$this->user = new StatefulUser(['sub' => uniqid('auth0|')]);
$this->session->set('user', ['sub' => 'hello|world']);
$this->session->set('idToken', (string) Generator::create((createRsaKeys())->private));
$this->session->set('accessToken', (string) Generator::create((createRsaKeys())->private));
$this->session->set('accessTokenScope', [uniqid()]);
$this->session->set('accessTokenExpiration', time() + 60);
$this->route = '/' . uniqid();
$guard = $this->guard;
Route::get($this->route, function () use ($guard) {
$credential = $guard->find(Guard::SOURCE_SESSION);
if (null !== $credential) {
$guard->login($credential, Guard::SOURCE_SESSION);
return response()->json(['status' => 'OK']);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
});
it('gets a user from a valid session', function (): void {
getJson($this->route)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world');
});
it('updates internal and session states as appropriate', function (): void {
// Session should be available and populated
expect($this->session)
->getAll()->not()->toBe([]);
// Guard should pick up on the session during the HTTP request
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should have it's state populated
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world');
// Empty guard state
$this->guard->logout();
// Guard should have had it's state emptied
expect($this->guard)
->user()->toBeNull();
// Session should have been emptied
expect($this->session)
->getAll()->toBe([]);
// HTTP request should fail without a session.
getJson($this->route)
->assertStatus(Response::HTTP_UNAUTHORIZED);
// Inject a new session into the store
$this->session->set('user', ['sub' => 'hello|world|two']);
// Session should be available and populated again
expect($this->session)
->getAll()->not()->toBe([]);
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should pick up on the session
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world|two');
// Directly wipe the Laravel session, circumventing the Guard APIs
$this->session->purge();
// Session should be empty
expect($this->session)
->getAll()->toBe([]);
getJson($this->route)
->assertStatus(Response::HTTP_UNAUTHORIZED);
// Guard should have it's state emptied
expect($this->guard)
->user()->toBeNull();
$this->session->set('user', ['sub' => 'hello|world|4']);
// Session should be available
expect($this->session)
->getAll()->not()->toBe([]);
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should pick up on the session
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world|4');
$identifier = uniqid('auth0|');
$user = new StatefulUser(['sub' => $identifier]);
// Overwrite state using the Guard's login()
$this->guard->login(CredentialEntity::create(
user: $user
), Guard::SOURCE_SESSION);
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should have it's state updated
expect($this->guard)
->user()->getAuthIdentifier()->toBe($identifier);
// Session should be updated
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
});
it('creates a session from login()', function (): void {
$identifier = uniqid('auth0|');
// $idToken = uniqid('id-token-');
// $accessToken = uniqid('access-token-');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
// $this->session->set('idToken', $idToken);
// $this->session->set('accessToken', $accessToken);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
expect($found)
->toBeInstanceOf(CredentialEntity::class);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier])
// ->get('idToken')->toBe($idToken)
// ->get('accessToken')->toBe($accessToken)
->get('accessTokenScope')->toBe($accessTokenScope)
->get('accessTokenExpiration')->toBe($accessTokenExpiration)
->get('refreshToken')->toBeNull();
$user = new StatefulUser(['sub' => $identifier]);
$changedIdToken = uniqid('CHANGED-id-token-');
$changedRefreshToken = uniqid('CHANGED-refresh-token-');
// Overwrite state using the Guard's login()
$this->guard->login(CredentialEntity::create(
user: $user,
idToken: $changedIdToken,
refreshToken: $changedRefreshToken
), Guard::SOURCE_SESSION);
expect($this->guard)
->user()->getAuthIdentifier()->toBe($identifier);
expect($this->session)
->get('user')->toBe(['sub' => $identifier])
->get('idToken')->toBe($changedIdToken)
// ->get('accessToken')->toBe($accessToken)
->get('accessTokenScope')->toBe($accessTokenScope)
->get('accessTokenExpiration')->toBe($accessTokenExpiration)
->get('refreshToken')->toBe($changedRefreshToken);
});
it('queries the /userinfo endpoint for refreshUser()', function (): void {
$identifier = uniqid('auth0|');
// $idToken = uniqid('id-token-');
// $accessToken = uniqid('access-token-');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
// $this->session->set('idToken', $idToken);
// $this->session->set('accessToken', $accessToken);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
$response = (new MockResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new MockStreamFactory)->createStream(
json_encode(
value: [
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
],
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
]);
});
it('does not query the /userinfo endpoint for refreshUser() if an access token is not available', function (): void {
$identifier = uniqid('auth0|');
// $idToken = uniqid('id-token-');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
// $this->session->set('idToken', $idToken);
$this->session->set('accessToken', null);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
$requestFactory = new MockRequestFactory;
$responseFactory = new MockResponseFactory;
$streamFactory = new MockStreamFactory;
$response = $responseFactory->createResponse(200);
$response->getBody()->write(json_encode(
[
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
],
JSON_PRETTY_PRINT
));
$http = new MockHttpClient(fallbackResponse: $response, requestLimit: 0);
$http->addResponseWildcard($response);
$this->config->setHttpRequestFactory($requestFactory);
$this->config->setHttpResponseFactory($responseFactory);
$this->config->setHttpStreamFactory($streamFactory);
$this->config->setHttpClient($http);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
]);
});
it('rejects bad responses from the /userinfo endpoint for refreshUser()', function (): void {
$identifier = uniqid('auth0|');
// $idToken = uniqid('id-token-');
// $accessToken = uniqid('access-token-');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
// $this->session->set('idToken', $idToken);
// $this->session->set('accessToken', $accessToken);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
$response = (new MockResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new MockStreamFactory)->createStream(
json_encode(
value: 'bad response',
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
]);
});
it('immediately invalidates an expired session when a refresh token is not available', function (): void {
$this->session->set('accessTokenExpiration', time() - 1000);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->guard)
->user()->toBeNull();
expect($this->session)
->get('user')->toBeNull();
});
it('invalidates an expired session when an access token fails to refresh', function (): void {
$this->session->set('accessTokenExpiration', time() - 1000);
$this->session->set('refreshToken', uniqid());
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->guard)
->user()->toBeNull();
expect($this->session)
->get('user')->toBeNull();
});
it('successfully continues a session when an access token is successfully refreshed', function (): void {
$this->session->set('accessTokenExpiration', time() - 1000);
$this->session->set('refreshToken', (string) Generator::create((createRsaKeys())->private));
$response = (new MockResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new MockStreamFactory)->createStream(
json_encode(
value: [
'access_token' => (string) Generator::create((createRsaKeys())->private),
'expires_in' => 60,
'scope' => 'openid profile',
'token_type' => 'Bearer',
],
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->guard)
->user()->not()->toBeNull();
expect($this->session)
->get('user')->not()->toBeNull();
});
================================================
FILE: tests/Unit/Auth/GuardStatelessTest.php
================================================
group('auth', 'auth.guard', 'auth.guard.stateless');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => ['https://example.com/health-api'],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->config = $this->sdk->configuration();
$this->token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => [
config('auth0.guards.default.audience')[0],
"https://my-domain.auth0.com/userinfo"
],
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->bearerToken = ['Authorization' => 'Bearer ' . $this->token->toString()];
$this->route = '/' . uniqid();
$guard = $this->guard;
Route::get($this->route, function () use ($guard) {
$credential = $guard->find(Guard::SOURCE_TOKEN);
if (null !== $credential) {
$guard->login($credential, Guard::SOURCE_TOKEN);
return response()->json(['status' => 'OK']);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
});
it('assigns a user from a good token', function (): void {
expect($this->guard)
->user()->toBeNull();
getJson($this->route, $this->bearerToken)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->not()->toBeNull();
});
it('does not assign a user from a empty token', function (): void {
getJson($this->route, ['Authorization' => 'Bearer '])
->assertStatus(Response::HTTP_UNAUTHORIZED);
expect($this->guard)
->user()->toBeNull();
});
it('does not get a user from a bad token', function (): void {
$this->guard
->sdk()
->configuration()
->setAudience(['BAD_AUDIENCE']);
expect($this->guard)
->user()->toBeNull();
getJson($this->route, $this->bearerToken)
->assertStatus(Response::HTTP_UNAUTHORIZED);
expect($this->guard)
->user()->toBeNull();
});
================================================
FILE: tests/Unit/Auth/GuardTest.php
================================================
group('auth', 'auth.guard', 'auth.guard.shared');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->config = $this->sdk->configuration();
$this->session = $this->config->getSessionStorage();
$this->user = new StatefulUser(['sub' => uniqid('auth0|')]);
Route::middleware('auth:auth0')->get('/test', function () {
return 'OK';
});
});
it('returns its configured name', function (): void {
expect($this->guard)
->toBeInstanceOf(Guard::class)
->getName()->toBe('legacyGuard');
});
it('assigns a user at login', function (): void {
expect($this->guard)
->toBeInstanceOf(Guard::class)
->user()->toBeNull();
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->user()->toBe($this->user);
expect($this->guard)
->id()->toBe($this->user->getAuthIdentifier());
});
it('logs out a user', function (): void {
expect($this->guard)
->toBeInstanceOf(Guard::class)
->user()->toBeNull();
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->user()->toBe($this->user);
$this->guard->logout();
expect($this->guard)
->user()->toBeNull();
expect($this->guard)
->id()->toBeNull();
});
it('forgets a user', function (): void {
expect($this->guard)
->toBeInstanceOf(Guard::class)
->user()->toBeNull();
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->user()->toBe($this->user);
$this->guard->forgetUser();
expect($this->guard)
->user()->toBeNull();
expect($this->guard)
->id()->toBeNull();
});
it('checks if a user is logged in', function (): void {
expect($this->guard)
->check()->toBeFalse();
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->check()->toBeTrue();
});
it('checks if a user is a guest', function (): void {
expect($this->guard)
->guest()->toBeTrue();
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->guest()->toBeFalse();
});
it('gets the user identifier', function (): void {
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->id()->toBe($this->user->getAuthIdentifier());
});
it('validates a user', function (): void {
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->validate(['id' => '123'])->toBeFalse()
->validate(['id' => '456'])->toBeFalse();
});
it('gets/sets a user', function (): void {
$this->guard->setUser($this->user);
expect($this->guard)
->user()->toBe($this->user);
});
it('has a user', function (): void {
$this->guard->setUser($this->user);
expect($this->guard)
->hasUser()->toBeTrue();
$this->guard->logout();
expect($this->guard)
->hasUser()->toBeFalse();
});
it('clears an imposter at logout', function (): void {
$this->guard->setImpersonating(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->hasUser()->toBeTrue()
->isImpersonating()->toBeTrue();
$this->guard->logout();
expect($this->guard)
->isImpersonating()->toBeFalse()
->hasUser()->toBeFalse();
});
it('has a scope', function (): void {
$this->user = new StatefulUser(['sub' => uniqid('auth0|'), 'scope' => 'read:users 456']);
$credential = CredentialEntity::create(
user: $this->user,
accessTokenScope: ['read:users', '456']
);
expect($this->guard)
->hasScope('read:users', $credential)->toBeTrue()
->hasScope('123', $credential)->toBeFalse()
->hasScope('456', $credential)->toBeTrue()
->hasScope('789', $credential)->toBeFalse()
->hasScope('*', $credential)->toBeTrue();
$credential = CredentialEntity::create(
user: $this->user
);
expect($this->guard)
->hasScope('read:users', $credential)->toBeFalse()
->hasScope('*', $credential)->toBeTrue();
});
it('checks if a user was authenticated via remember', function (): void {
$this->guard->login(CredentialEntity::create(
user: $this->user
));
expect($this->guard)
->viaRemember()->toBeFalse();
});
it('returns null if authenticate() is called without being authenticated', function (): void {
$response = $this->guard->authenticate();
expect($response)->toBeNull();
})->throws(AuthenticationException::class, AuthenticationException::UNAUTHENTICATED);
it('returns a user from authenticate() if called while authenticated', function (): void {
$this->guard->login(CredentialEntity::create(
user: $this->user
));
$response = $this->guard->authenticate();
expect($response)
->toBe($this->user);
});
it('gets/sets a credentials', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->guard->setCredential($credential);
expect($this->guard)
->user()->toBe($this->user);
});
it('queries the /userinfo endpoint', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->guard->setCredential($credential, Guard::SOURCE_TOKEN);
expect($this->guard)
->user()->toBe($this->user);
$identifier = 'updated|' . uniqid();
$response = (new MockResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new MockStreamFactory)->createStream(
json_encode(
value: [
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
],
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$userAttributes = $this->guard->getRefreshedUser()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
]);
});
test('hasPermission(*) returns true for wildcard', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->guard->setCredential($credential, Guard::SOURCE_TOKEN);
expect($this->guard->hasPermission('*'))
->toBeTrue();
});
test('hasPermission() returns true for matches', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenDecoded: [
'permissions' => [
'read:posts',
'read:messages',
'read:users',
],
],
accessTokenExpiration: time() + 3600
);
$this->guard->setCredential($credential, Guard::SOURCE_TOKEN);
expect($this->guard->hasPermission('read:messages'))
->toBeTrue();
expect($this->guard->hasPermission('write:posts'))
->toBeFalse();
});
test('hasPermission() returns false when there are no permissions', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenDecoded: [
'permissions' => [],
],
accessTokenExpiration: time() + 3600
);
$this->guard->setCredential($credential, Guard::SOURCE_TOKEN);
expect($this->guard->hasPermission('read:messages'))
->toBeFalse();
});
test('management() returns a Management API class', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenDecoded: [
'permissions' => [],
],
accessTokenExpiration: time() + 3600
);
$this->guard->setCredential($credential, Guard::SOURCE_TOKEN);
expect($this->guard->management())
->toBeInstanceOf(ManagementInterface::class);
});
test('sdk() uses the guard name to optionally merge configuration data', function (): void {
config([
'auth0.guards.default.domain' => 'https://default-domain.com',
'auth0.guards.web.strategy' => 'none',
'auth0.guards.web.domain' => 'https://legacy-domain.com',
]);
expect($this->guard->sdk()->configuration()->getDomain())
->toBe('legacy-domain.com');
});
test('sdk() configuration v1 is supported', function (): void {
config(['auth0' => [
'strategy' => 'none',
'domain' => 'https://v1-domain.com',
]]);
expect($this->guard->sdk()->configuration()->getDomain())
->toBe('v1-domain.com');
});
test('sdk() configuration v1 defaults to an empty array', function (): void {
config(['auth0' => 123]);
$this->guard->sdk()->configuration()->getDomain();
})->throws(ConfigurationException::class);
================================================
FILE: tests/Unit/Bridges/CacheBridgeTest.php
================================================
group('cache', 'cache.laravel', 'cache.laravel.pool');
test('getItem(), hasItem() and save() behave as expected', function (): void {
$pool = new CacheBridge();
$cache = $pool->getItem('testing');
expect($pool)
->hasItem('testing')->toBeFalse();
expect($cache)
->toBeInstanceOf(CacheItemBridge::class)
->get()->toBeNull()
->isHit()->toBeFalse();
$cache->set(42);
expect($cache)
->get()->toBeNull();
expect($pool)
->save($cache)->toBeTrue();
expect($pool)
->hasItem('testing')->toBeTrue();
$cache = $pool->getItem('testing');
expect($cache)
->toBeInstanceOf(CacheItemBridge::class)
->isHit()->toBeTrue()
->get()->toEqual(42);
$results = $pool->getItems();
expect($results)
->toBeArray()
->toHaveCount(0);
$results = $pool->getItems(['testing']);
expect($results['testing'])
->toBeInstanceOf(CacheItemBridge::class)
->isHit()->toBeTrue()
->get()->toEqual(42);
$this->app[\Illuminate\Cache\CacheManager::class]
->getStore()
->put('testing', false, 60);
$cache = $pool->getItem('testing');
expect($pool)
->hasItem('testing')->toBeFalse();
expect($cache)
->toBeInstanceOf(CacheItemBridge::class)
->get()->toBeNull()
->isHit()->toBeFalse();
$cacheMock = Mockery::mock(CacheItemInterface::class);
expect($pool)
->save($cacheMock)->toBeFalse();
});
test('save() with a negative expiration value is deleted', function (): void {
$pool = new CacheBridge();
$cache = new CacheItemBridge('testing', 42, true, new DateTime('now - 1 year'));
expect($pool)->hasItem('testing')->toBeFalse();
$pool->save($cache);
expect($pool)->hasItem('testing')->toBeFalse();
});
test('saveDeferred() behaves as expected', function (): void {
$pool = new CacheBridge();
$cache = new CacheItemBridge('testing', 42, true, new DateTime('now + 1 hour'));
expect($pool)
->hasItem('testing')->toBeFalse()
->saveDeferred($cache)->toBeTrue()
->hasItem('testing')->toBeFalse()
->commit()->toBeTrue()
->hasItem('testing')->toBeTrue();
});
test('save() with a false value is discarded', function (): void {
$pool = new CacheBridge();
$cache = new CacheItemBridge('testing', false, true, new DateTime('now + 1 hour'));
expect($pool)
->hasItem('testing')->toBeFalse()
->save($cache)->toBeTrue()
->hasItem('testing')->toBeFalse();
});
test('saveDeferred() returns false when the wrong type of interface is saved', function (): void {
$pool = new CacheBridge();
$cache = new CacheItemBridge('testing', 42, true, new DateTime('now + 1 hour'));
$cache = new class implements CacheItemInterface {
public function getKey(): string
{
return 'testing';
}
public function get(): mixed
{
return 42;
}
public function isHit(): bool
{
return true;
}
public function set(mixed $value): static
{
return $this;
}
public function expiresAt($expiration): static
{
return $this;
}
public function expiresAfter($time): static
{
return $this;
}
};
expect($pool)
->hasItem('testing')->toBeFalse()
->saveDeferred($cache)->toBeFalse()
->hasItem('testing')->toBeFalse();
});
test('deleteItem() behaves as expected', function (): void {
$pool = new CacheBridge();
$cache = new CacheItemBridge('testing', 42, true, new DateTime('now + 1 minute'));
expect($pool)->hasItem('testing')->toBeFalse();
$pool->save($cache);
expect($pool)->hasItem('testing')->toBeTrue();
$cache = $pool->getItem('testing');
expect($cache)
->isHit()->toBeTrue()
->get()->toBe(42);
$pool->deleteItem('testing');
expect($pool)
->hasItem('testing')->toBeFalse();
$cache = $pool->getItem('testing');
expect($cache)
->isHit()->toBeFalse()
->get()->toBeNull();
});
test('deleteItems() behaves as expected', function (): void {
$pool = new CacheBridge();
expect($pool)
->hasItem('testing1')->toBeFalse()
->hasItem('testing2')->toBeFalse()
->hasItem('testing3')->toBeFalse();
$cache = new CacheItemBridge('testing1', uniqid(), true, new DateTime('now + 1 minute'));
$pool->save($cache);
$cache = new CacheItemBridge('testing2', uniqid(), true, new DateTime('now + 1 minute'));
$pool->save($cache);
$cache = new CacheItemBridge('testing3', uniqid(), true, new DateTime('now + 1 minute'));
$pool->save($cache);
expect($pool)
->hasItem('testing1')->toBeTrue()
->hasItem('testing2')->toBeTrue()
->hasItem('testing3')->toBeTrue();
$results = $pool->getItems(['testing1' => 1, 'testing2' => 2, 'testing3' => 3]);
expect($results)
->toHaveKey('testing1')
->toHaveKey('testing2')
->toHaveKey('testing3')
->testing1->isHit()->toBeTrue()
->testing2->isHit()->toBeTrue()
->testing3->isHit()->toBeTrue();
expect($pool)
->deleteItems(['testing1', 'testing2', 'testing3'])->toBeTrue()
->hasItem('testing1')->toBeFalse()
->hasItem('testing2')->toBeFalse()
->hasItem('testing3')->toBeFalse()
->deleteItems(['testing4', 'testing5', 'testing6'])->toBeFalse();
});
test('clear() behaves as expected', function (): void {
$pool = new CacheBridge();
expect($pool)
->hasItem('testing1')->toBeFalse()
->hasItem('testing2')->toBeFalse()
->hasItem('testing3')->toBeFalse();
$cache = new CacheItemBridge('testing1', uniqid(), true, new DateTime('now + 1 minute'));
$pool->save($cache);
$cache = new CacheItemBridge('testing2', uniqid(), true, new DateTime('now + 1 minute'));
$pool->save($cache);
$cache = new CacheItemBridge('testing3', uniqid(), true, new DateTime('now + 1 minute'));
$pool->save($cache);
expect($pool)
->hasItem('testing1')->toBeTrue()
->hasItem('testing2')->toBeTrue()
->hasItem('testing3')->toBeTrue();
$results = $pool->getItems(['testing1' => 1, 'testing2' => 2, 'testing3' => 3]);
expect($results)
->toHaveKey('testing1')
->toHaveKey('testing2')
->toHaveKey('testing3')
->testing1->isHit()->toBeTrue()
->testing2->isHit()->toBeTrue()
->testing3->isHit()->toBeTrue();
$pool->clear();
expect($pool)
->hasItem('testing1')->toBeFalse()
->hasItem('testing2')->toBeFalse()
->hasItem('testing3')->toBeFalse();
});
================================================
FILE: tests/Unit/Bridges/CacheItemBridgeTest.php
================================================
group('cache', 'cache.laravel', 'cache.laravel.item');
test('getKey() returns an expected value', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
expect($cacheItem->getKey())
->toBe('testing');
});
test('get() returns an expected value when hit', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
expect($cacheItem->get())
->toBe(42);
});
test('get() returns null when no hit', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, false);
expect($cacheItem->get())
->toBeNull();
});
test('getRawValue() returns an expected value', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, false);
expect($cacheItem->getRawValue())
->toBe(42);
});
test('isHit() returns an expected value when hit', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
expect($cacheItem->isHit())
->toBeTrue();
$cacheItem = new CacheItemBridge('testing', 42, false);
expect($cacheItem->isHit())
->toBeFalse();
});
test('set() alters the stored value as expected', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
expect($cacheItem->get())
->toBe(42);
expect($cacheItem->set(43))
->toBe($cacheItem)
->get()->toBe(43);
});
test('expiresAt() defaults to +1 year and accepts changes to its value', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
expect($cacheItem->getExpiration()->getTimestamp())
->toBeGreaterThan((new DateTime('now +1 year -1 minute'))->getTimestamp())
->toBeLessThan((new DateTime('now +1 year +1 minute'))->getTimestamp());
$cacheItem->expiresAt(new DateTime('now +1 day'));
expect($cacheItem->getExpiration()->getTimestamp())
->toBeGreaterThan((new DateTime('now +1 day -1 minute'))->getTimestamp())
->toBeLessThan((new DateTime('now +1 day +1 minute'))->getTimestamp());
});
test('expiresAfter() defaults to +1 year and accepts changes to its value', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
expect($cacheItem->getExpiration()->getTimestamp())
->toBeGreaterThan((new DateTime('now +1 year -1 minute'))->getTimestamp())
->toBeLessThan((new DateTime('now +1 year +1 minute'))->getTimestamp());
$cacheItem->expiresAfter(300);
expect($cacheItem->getExpiration()->getTimestamp())
->toBeGreaterThan((new DateTime('now +250 seconds'))->getTimestamp())
->toBeLessThan((new DateTime('now +350 seconds'))->getTimestamp());
});
test('miss() returns a configured instance', function (): void {
$cacheItem = new CacheItemBridge('testing', 42, true);
$newCacheItem = $cacheItem->miss('testing123');
expect($cacheItem->getKey())
->toBe('testing');
expect($newCacheItem->getKey())
->toBe('testing123');
expect($newCacheItem->get())
->not()->toBe($cacheItem->get());
});
================================================
FILE: tests/Unit/Bridges/SessionBridgeTest.php
================================================
group('session-store');
it('throws an exception when an empty prefix is provided', function (): void {
expect(function () {
new SessionBridge(
prefix: '',
);
})->toThrow(InvalidArgumentException::class);
});
it('accepts and uses a specified prefix', function (): void {
$prefix = uniqid();
$store = new SessionBridge(
prefix: $prefix,
);
expect($store)
->toBeInstanceOf(StoreInterface::class)
->getPrefix()->toBe($prefix);
});
it('allows updating the prefix', function (): void {
$store = new SessionBridge();
expect($store)
->toBeInstanceOf(StoreInterface::class)
->getPrefix()->toBe('auth0');
$prefix = uniqid();
$store->setPrefix($prefix);
expect($store)
->toBeInstanceOf(StoreInterface::class)
->getPrefix()->toBe($prefix);
});
it('supports CRUD operations', function (): void {
$prefix = uniqid();
$store = new SessionBridge(
prefix: $prefix,
);
expect($store)
->toBeInstanceOf(StoreInterface::class)
->get('test')->toBeNull()
->set('test', 'value')->toBeNull()
->get('test')->toBe('value')
->getAll()->toBe(['test' => 'value'])
->set('test2', 'value2')->toBeNull()
->getAll()->toBe(['test' => 'value', 'test2' => 'value2'])
->delete('test')->toBeNull()
->getAll()->toBe(['test2' => 'value2'])
->set('test3', 'value3')->toBeNull()
->getAll()->toBe(['test2' => 'value2', 'test3' => 'value3'])
->purge()->toBeNull()
->getAll()->toBe([]);
});
================================================
FILE: tests/Unit/ConfigurationTest.php
================================================
group('Configuration');
test('stringToArrayOrNull() behaves as expected', function (): void {
expect(Configuration::stringToArrayOrNull('foo bar baz'))
->toBe(['foo', 'bar', 'baz']);
expect(Configuration::stringToArrayOrNull(['foo', 'bar', 'baz']))
->toBe(['foo', 'bar', 'baz']);
expect(Configuration::stringToArrayOrNull(' '))
->toBeNull();
});
test('stringToArray() behaves as expected', function (): void {
expect(Configuration::stringToArray('foo bar baz'))
->toBe(['foo', 'bar', 'baz']);
expect(Configuration::stringToArray(['foo', 'bar', 'baz']))
->toBe(['foo', 'bar', 'baz']);
expect(Configuration::stringToArray(' '))
->toBeArray()
->toHaveCount(0);
});
test('stringToBoolOrNull() behaves as expected', function (): void {
expect(Configuration::stringToBoolOrNull('true'))
->toBeTrue();
expect(Configuration::stringToBoolOrNull('false'))
->toBeFalse();
expect(Configuration::stringToBoolOrNull('foo'))
->toBeNull();
expect(Configuration::stringToBoolOrNull('foo', true))
->toBeTrue();
expect(Configuration::stringToBoolOrNull('foo', false))
->toBeFalse();
});
test('stringOrNull() behaves as expected', function (): void {
expect(Configuration::stringOrNull(123))
->toBeNull();
expect(Configuration::stringOrNull(' 456 '))
->toEqual('456');
expect(Configuration::stringOrNull(' '))
->toBeNull();
expect(Configuration::stringOrNull('empty'))
->toBeNull();
expect(Configuration::stringOrNull('(empty)'))
->toBeNull();
expect(Configuration::stringOrNull('null'))
->toBeNull();
expect(Configuration::stringOrNull('(null)'))
->toBeNull();
});
test('stringOrIntToIntOrNull() behaves as expected', function (): void {
expect(Configuration::stringOrIntToIntOrNull(123))
->toEqual(123);
expect(Configuration::stringOrIntToIntOrNull(' 456 '))
->toEqual(456);
expect(Configuration::stringOrIntToIntOrNull(' '))
->toBeNull();
expect(Configuration::stringOrIntToIntOrNull(' abc '))
->toBeNull();
});
test('get() ignores quickstart placeholders', function (): void {
putenv('AUTH0_DOMAIN={DOMAIN}');
putenv('AUTH0_CLIENT_ID={CLIENT_ID}');
putenv('AUTH0_CLIENT_SECRET={CLIENT_SECRET}');
putenv('AUTH0_AUDIENCE={API_IDENTIFIER}');
putenv('AUTH0_CUSTOM_DOMAIN=https://example.com');
expect(Configuration::get(Configuration::CONFIG_CUSTOM_DOMAIN))
->toBeString('https://example.com');
expect(Configuration::get(Configuration::CONFIG_DOMAIN))
->toBeNull();
expect(Configuration::get(Configuration::CONFIG_CLIENT_ID))
->toBeNull();
expect(Configuration::get(Configuration::CONFIG_CLIENT_SECRET))
->toBeNull();
expect(Configuration::get(Configuration::CONFIG_AUDIENCE))
->toBeNull();
});
test('get() behaves as expected', function (): void {
config(['test' => [
Configuration::CONFIG_AUDIENCE => implode(',', [uniqid(), uniqid()]),
Configuration::CONFIG_SCOPE => [],
Configuration::CONFIG_ORGANIZATION => '',
Configuration::CONFIG_USE_PKCE => true,
Configuration::CONFIG_HTTP_TELEMETRY => 'true',
Configuration::CONFIG_COOKIE_SECURE => 123,
Configuration::CONFIG_PUSHED_AUTHORIZATION_REQUEST => false,
'tokenLeeway' => 123,
]]);
define('AUTH0_OVERRIDE_CONFIGURATION', 'test');
expect(Configuration::get(Configuration::CONFIG_AUDIENCE))
->toBeArray()
->toHaveCount(2);
expect(Configuration::get(Configuration::CONFIG_SCOPE))
->toBeNull();
expect(Configuration::get(Configuration::CONFIG_ORGANIZATION))
->toBeNull();
expect(Configuration::get(Configuration::CONFIG_USE_PKCE))
->toBeTrue();
expect(Configuration::get(Configuration::CONFIG_HTTP_TELEMETRY))
->toBeTrue();
expect(Configuration::get(Configuration::CONFIG_COOKIE_SECURE))
->toBeNull();
expect(Configuration::get(Configuration::CONFIG_PUSHED_AUTHORIZATION_REQUEST))
->toBeFalse();
expect(Configuration::get('tokenLeeway'))
->toBeInt()
->toEqual(123);
});
test('string() behaves as expected', function (): void {
config(['test2' => [
'testInteger' => 123,
'testString' => '123',
]]);
define('AUTH0_OVERRIDE_CONFIGURATION_STRING_METHOD', 'test2');
expect(Configuration::string('test2.testInteger'))
->toBeNull();
expect(Configuration::string('test2.testString'))
->toBeString()
->toEqual('123');
});
================================================
FILE: tests/Unit/Controllers/CallbackControllerTest.php
================================================
group('stateful', 'controller', 'controller.stateful', 'controller.stateful.callback');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->guard = auth('legacyGuard');
$this->sdk = $this->guard->sdk();
$this->config = $this->guard->sdk()->configuration();
$this->user = new ImposterUser(['sub' => uniqid('auth0|')]);
Route::get('/auth0/callback', CallbackController::class)->name('callback');
});
it('redirects home if an incompatible guard is active', function (): void {
config([
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null,
]);
expect(function () {
$this->withoutExceptionHandling()
->getJson('/auth0/callback')
->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR);
})->toThrow(ControllerException::class);
});
it('accepts code and state parameters', function (): void {
expect(function () {
$this->withoutExceptionHandling()
->getJson('/auth0/callback?code=code&state=state');
})->toThrow(StateException::class);
$this->assertDispatched(Attempting::class, 1);
$this->assertDispatched(Failed::class, 1);
$this->assertDispatched(AuthenticationFailed::class, 1);
$this->assertDispatchedOrdered([
Attempting::class,
Failed::class,
AuthenticationFailed::class,
]);
});
it('accepts error and error_description parameters', function (): void {
expect(function () {
$this->withoutExceptionHandling()
->getJson('/auth0/callback?error=123&error_description=456');
})->toThrow(CallbackControllerException::class);
$this->assertDispatched(Attempting::class, 1);
$this->assertDispatched(Failed::class, 1);
$this->assertDispatched(AuthenticationFailed::class, 1);
$this->assertDispatchedOrdered([
Attempting::class,
Failed::class,
AuthenticationFailed::class,
]);
});
it('returns a user and sets up a session', function (): void {
$this->config->setTokenAlgorithm(Token::ALGO_HS256);
$state = uniqid();
$pkce = uniqid();
$nonce = uniqid();
$verifier = uniqid();
$idToken = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
'sub' => 'hello|world',
'aud' => config('auth0.guards.default.clientId'),
'exp' => time() + 60,
'iat' => time(),
'email' => 'john.doe@somewhere.test',
'nonce' => $nonce
], []);
$accessToken = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
'sub' => 'hello|world',
'aud' => config('auth0.guards.default.clientId'),
'iat' => time(),
'exp' => time() + 60,
'azp' => config('auth0.guards.default.clientId'),
'scope' => 'openid profile email'
], []);
$factory = $this->config->getHttpResponseFactory();
$response = $factory->createResponse();
$response->getBody()->write(json_encode([
'access_token' => $accessToken->toString(),
'id_token' => $idToken->toString(),
'scope' => 'openid profile email',
'expires_in' => 60,
]));
$client = $this->config->getHttpClient();
$client->addResponse('POST', 'https://' . config('auth0.guards.default.domain') . '/oauth/token', $response);
$this->withSession([
'auth0_transient' => json_encode([
'state' => $state,
'pkce' => $pkce,
'nonce' => $nonce,
'code_verifier' => $verifier
])
])->getJson('/auth0/callback?code=code&state=' . $state)
->assertFound()
->assertLocation('/');
$this->assertDispatched(Attempting::class, 1);
$this->assertDispatched(Validated::class, 1);
$this->assertDispatched(Login::class, 1);
$this->assertDispatched(AuthenticationSucceeded::class, 1);
$this->assertDispatched(Authenticated::class, 1);
$this->assertDispatchedOrdered([
Attempting::class,
Validated::class,
Login::class,
AuthenticationSucceeded::class,
Authenticated::class,
]);
});
it('redirects visitors if an expected parameter is not provided', function (): void {
$this->getJson('/auth0/callback?code=code')
->assertFound()
->assertLocation('/login');
});
================================================
FILE: tests/Unit/Controllers/LoginControllerTest.php
================================================
group('stateful', 'controller', 'controller.stateful', 'controller.stateful.login');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->validSession = [
'auth0_session' => json_encode([
'user' => ['sub' => 'hello|world'],
'idToken' => (string) Generator::create((createRsaKeys())->private),
'accessToken' => (string) Generator::create((createRsaKeys())->private),
'accessTokenScope' => [uniqid()],
'accessTokenExpiration' => time() + 60,
])
];
Route::get('/login', LoginController::class);
});
it('redirects to the home route if an incompatible guard is active', function (): void {
config($config = [
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null,
]);
expect(function () {
$this->withoutExceptionHandling()
->getJson('/login')
->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR);
})->toThrow(ControllerException::class);
});
it('redirects to the home route when a user is already logged in', function (): void {
$this->withSession($this->validSession)
->get('/login')
->assertRedirect('/');
});
it('redirects to the Universal Login Page', function (): void {
$this->get('/login')
->assertRedirectContains('/authorize');
});
================================================
FILE: tests/Unit/Controllers/LogoutControllerTest.php
================================================
group('stateful', 'controller', 'controller.stateful', 'controller.stateful.logout');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->validSession = [
'auth0_session' => json_encode([
'user' => ['sub' => 'hello|world'],
'idToken' => (string) Generator::create((createRsaKeys())->private),
'accessToken' => (string) Generator::create((createRsaKeys())->private),
'accessTokenScope' => [uniqid()],
'accessTokenExpiration' => time() + 60,
])
];
Route::get('/logout', LogoutController::class);
});
it('redirects to the home route if an incompatible guard is active', function (): void {
config($config = [
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
expect(function () {
$this->withoutExceptionHandling()
->getJson('/logout')
->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR);
})->toThrow(ControllerException::class);
});
it('redirects to the home route when a user is not already logged in', function (): void {
$this->get('/logout')
->assertRedirect('/');
});
it('redirects to the Auth0 logout endpoint', function (): void {
$this->withSession($this->validSession)
->get('/logout')
->assertRedirectContains('/v2/logout');
});
================================================
FILE: tests/Unit/Entities/CredentialEntityTest.php
================================================
group('stateful', 'model', 'model.credential');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => [uniqid()],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->user = new StatelessUser(['sub' => uniqid('auth0|')]);
$this->idToken = mockIdToken(algorithm: Token::ALGO_HS256);
$this->accessToken = mockAccessToken(algorithm: Token::ALGO_HS256);
$this->accessTokenScope = ['openid', 'profile', 'email', uniqid()];
$this->accessTokenDecoded = [uniqid(), ['hello' => 'world']];
$this->accessTokenExpiration = time() + 3600;
$this->refreshToken = uniqid();
});
test('create() returns a properly configured instance', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: $this->idToken,
accessToken: $this->accessToken,
accessTokenScope: $this->accessTokenScope,
accessTokenExpiration: $this->accessTokenExpiration,
refreshToken: $this->refreshToken
);
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getUser()->toBe($this->user)
->getIdToken()->toBe($this->idToken)
->getAccessToken()->toBe($this->accessToken)
->getAccessTokenScope()->toBe($this->accessTokenScope)
->getAccessTokenExpiration()->toBe($this->accessTokenExpiration)
->getRefreshToken()->toBe($this->refreshToken);
});
it('clear() nullifies all properties', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: $this->idToken,
accessToken: $this->accessToken,
accessTokenScope: $this->accessTokenScope,
accessTokenExpiration: $this->accessTokenExpiration,
refreshToken: $this->refreshToken,
);
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getUser()->toBe($this->user)
->getIdToken()->toBe($this->idToken)
->getAccessToken()->toBe($this->accessToken)
->getAccessTokenScope()->toBe($this->accessTokenScope)
->getAccessTokenExpiration()->toBe($this->accessTokenExpiration)
->getRefreshToken()->toBe($this->refreshToken);
expect($credential->clear())
->toBeInstanceOf(CredentialEntity::class)
->getUser()->toBeNull()
->getIdToken()->toBeNull()
->getAccessToken()->toBeNull()
->getAccessTokenScope()->toBeNull()
->getAccessTokenExpiration()->toBeNull()
->getRefreshToken()->toBeNull();
});
it('setUser() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getUser()->toBeNull();
expect($credential->setUser($this->user))
->toBeInstanceOf(CredentialEntity::class)
->getUser()->toBe($this->user);
});
it('setIdToken() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getIdToken()->toBeNull();
expect($credential->setIdToken($this->idToken))
->toBeInstanceOf(CredentialEntity::class)
->getIdToken()->toBe($this->idToken);
});
it('setAccessToken() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getAccessToken()->toBeNull();
expect($credential->setAccessToken($this->accessToken))
->toBeInstanceOf(CredentialEntity::class)
->getAccessToken()->toBe($this->accessToken);
});
it('setAccessTokenScope() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenScope()->toBeNull();
expect($credential->setAccessTokenScope($this->accessTokenScope))
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenScope()->toBe($this->accessTokenScope);
});
it('setAccessTokenDecoded() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenDecoded()->toBeNull();
expect($credential->setAccessTokenDecoded($this->accessTokenDecoded))
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenDecoded()->toBe($this->accessTokenDecoded);
});
it('setAccessTokenExpiration() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenExpiration()->toBeNull();
expect($credential->setAccessTokenExpiration($this->accessTokenExpiration))
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenExpiration()->toBe($this->accessTokenExpiration);
});
it('setRefreshToken() assigns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getRefreshToken()->toBeNull();
expect($credential->setRefreshToken($this->refreshToken))
->toBeInstanceOf(CredentialEntity::class)
->getRefreshToken()->toBe($this->refreshToken);
});
it('getAccessTokenExpired() returns a correct value', function (): void {
$credential = CredentialEntity::create();
expect($credential)
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenExpired()->toBeNull();
expect($credential->setAccessTokenExpiration($this->accessTokenExpiration))
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenExpired()->toBeFalse();
expect($credential->setAccessTokenExpiration($this->accessTokenExpiration - 3600 * 2))
->toBeInstanceOf(CredentialEntity::class)
->getAccessTokenExpired()->toBeTrue();
});
it('jsonSerialize() returns a correct structure', function (): void {
$credential = CredentialEntity::create(
user: $this->user,
idToken: $this->idToken,
accessToken: $this->accessToken,
accessTokenScope: $this->accessTokenScope,
accessTokenExpiration: $this->accessTokenExpiration,
refreshToken: $this->refreshToken,
);
expect(json_encode($credential))
->json()
->user->toBe(json_encode($this->user))
->idToken->toBe($this->idToken)
->accessToken->toBe($this->accessToken)
->accessTokenScope->toBe($this->accessTokenScope)
->accessTokenExpiration->toBe($this->accessTokenExpiration)
->refreshToken->toBe($this->refreshToken);
});
================================================
FILE: tests/Unit/Entities/InstanceEntityTest.php
================================================
group('Entities/InstanceEntity');
beforeEach(function (): void {
});
it('instantiates an empty configuration if a non-array is supplied', function (): void {
config(['auth0' => true]);
(new InstanceEntity())->getConfiguration();
})->throws(ConfigurationException::class);
test('setGuardConfigurationKey() sets the guard configuration key', function (): void {
$key = uniqid();
$instance = new InstanceEntity();
$instance->setGuardConfigurationKey($key);
expect($instance->getGuardConfigurationKey())
->toBe($key);
});
test('setConfiguration sets the configuration using an SdkConfiguration', function (): void {
$instance = new InstanceEntity();
$configuration = new SdkConfiguration(['strategy' => 'none', 'domain' => uniqid() . '.auth0.test']);
$instance->setConfiguration($configuration);
expect($instance->getConfiguration())
->toBe($configuration);
});
test('setConfiguration sets the configuration using an array', function (): void {
$instance = new InstanceEntity();
$configuration = ['strategy' => 'none', 'domain' => uniqid() . '.auth0.test'];
$instance->setConfiguration($configuration);
expect($instance->getConfiguration())
->toBeInstanceOf(SdkConfiguration::class);
});
test('::create() sets the guard configuration key and configuration', function (): void {
$key = uniqid();
$configuration = new SdkConfiguration(['strategy' => 'none', 'domain' => uniqid() . '.auth0.test']);
$instance = InstanceEntity::create(
configuration: $configuration,
guardConfigurationName: $key,
);
expect($instance->getConfiguration())
->toBe($configuration);
expect($instance->getGuardConfigurationKey())
->toBe($key);
});
================================================
FILE: tests/Unit/Guards/AuthenticationGuardTest.php
================================================
group('auth', 'auth.guard', 'auth.guard.session');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = $guard = auth('auth0-session');
$this->sdk = $this->laravel->getSdk();
$this->config = $this->sdk->configuration();
$this->session = $this->config->getSessionStorage();
$this->user = new StatefulUser(['sub' => uniqid('auth0|')]);
$this->session->set('user', ['sub' => 'hello|world']);
$this->session->set('idToken', (string) Generator::create((createRsaKeys())->private));
$this->session->set('accessToken', (string) Generator::create((createRsaKeys())->private));
$this->session->set('accessTokenScope', [uniqid()]);
$this->session->set('accessTokenExpiration', time() + 60);
$this->route = '/' . uniqid();
$this->route2 = '/' . uniqid();
$guard = $this->guard;
Route::get($this->route, function () use ($guard) {
$credential = $guard->find(Guard::SOURCE_SESSION);
if (null !== $credential) {
$guard->login($credential, Guard::SOURCE_SESSION);
return response()->json(['status' => 'OK']);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
Route::get($this->route2, function () use ($guard) {
return response()->json(['user' => $guard->user(Guard::SOURCE_SESSION)->getAuthIdentifier()]);
});
});
it('retrieves the authenticated user from a valid session using find()', function (): void {
$result = $this->guard->find();
expect($result)->toBeInstanceOf(CredentialEntity::class);
expect($result->getUser())->toBeInstanceOf(StatefulUser::class);
expect($this->guard->user()->getAuthIdentifier())->toBe('hello|world');
});
it('retrieves the authenticated user from a valid session using user()', function (): void {
getJson($this->route)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world');
});
it('updates internal and session states as appropriate', function (): void {
// Session should be available and populated
expect($this->session)
->getAll()->not()->toBe([]);
// Guard should pick up on the session during the HTTP request
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should have it's state populated
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world');
// Empty guard state
$this->guard->logout();
// Guard should have had it's state emptied
expect($this->guard)
->user()->toBeNull();
// Session should have been emptied
expect($this->session)
->getAll()->toBe([]);
// HTTP request should fail without a session.
getJson($this->route)
->assertStatus(Response::HTTP_UNAUTHORIZED);
// Inject a new session into the store
$this->session->set('user', ['sub' => 'hello|world|two']);
// Session should be available and populated again
expect($this->session)
->getAll()->not()->toBe([]);
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should pick up on the session
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world|two');
// Directly wipe the Laravel session, circumventing the Guard APIs
$this->session->purge();
// Session should be empty
expect($this->session)
->getAll()->toBe([]);
getJson($this->route)
->assertStatus(Response::HTTP_UNAUTHORIZED);
// Guard should have it's state emptied
expect($this->guard)
->user()->toBeNull();
$this->session->set('user', ['sub' => 'hello|world|4']);
// Session should be available
expect($this->session)
->getAll()->not()->toBe([]);
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should pick up on the session
expect($this->guard)
->user()->getAuthIdentifier()->toBe('hello|world|4');
$identifier = uniqid('auth0|');
$user = new StatefulUser(['sub' => $identifier]);
// Overwrite state using the Guard's login()
$this->guard->login(CredentialEntity::create(
user: $user
), Guard::SOURCE_SESSION);
getJson($this->route)
->assertStatus(Response::HTTP_OK);
// Guard should have it's state updated
expect($this->guard)
->user()->getAuthIdentifier()->toBe($identifier);
// Session should be updated
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
});
it('creates a session from login()', function (): void {
$identifier = uniqid('auth0|');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
expect($found)
->toBeInstanceOf(CredentialEntity::class);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier])
->get('accessTokenScope')->toBe($accessTokenScope)
->get('accessTokenExpiration')->toBe($accessTokenExpiration)
->get('refreshToken')->toBeNull();
$user = new StatefulUser(['sub' => $identifier]);
$changedRefreshToken = (string) Generator::create((createRsaKeys())->private);
// Overwrite state using the Guard's login()
$this->guard->login(CredentialEntity::create(
user: $user,
refreshToken: $changedRefreshToken
), Guard::SOURCE_SESSION);
expect($this->guard)
->user()->getAuthIdentifier()->toBe($identifier);
expect($this->session)
->get('user')->toBe(['sub' => $identifier])
->get('accessTokenScope')->toBe($accessTokenScope)
->get('accessTokenExpiration')->toBe($accessTokenExpiration)
->get('refreshToken')->toBe($changedRefreshToken);
});
it('queries the /userinfo endpoint for refreshUser()', function (): void {
$identifier = uniqid('auth0|');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
$response = (new ResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new StreamFactory)->createStream(
json_encode(
value: [
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
],
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
]);
});
it('does not query the /userinfo endpoint for refreshUser() if an access token is not available', function (): void {
$identifier = uniqid('auth0|');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
$this->session->set('accessToken', null);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
$response = (new ResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new StreamFactory)->createStream(
json_encode(
value: [
'sub' => $identifier,
'name' => 'John Doe',
'email' => '...',
],
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$this->guard
->sdk()
->configuration()
->getHttpClient()
->setRequestLimit(-1);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
]);
});
it('rejects bad responses from the /userinfo endpoint for refreshUser()', function (): void {
$identifier = uniqid('auth0|');
$accessTokenScope = [uniqid('access-token-scope-')];
$accessTokenExpiration = time() + 60;
$this->session->set('user', ['sub' => $identifier]);
$this->session->set('accessTokenScope', $accessTokenScope);
$this->session->set('accessTokenExpiration', $accessTokenExpiration);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->session)
->get('user')->toBe(['sub' => $identifier]);
$response = (new ResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new StreamFactory)->createStream(
json_encode(
value: 'bad response',
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $identifier,
]);
});
it('immediately invalidates an expired session when a refresh token is not available', function (): void {
$this->session->set('accessTokenExpiration', time() - 1000);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->guard)
->user()->toBeNull();
expect($this->session)
->get('user')->toBeNull();
});
it('invalidates an expired session when an access token fails to refresh', function (): void {
$this->session->set('accessTokenExpiration', time() - 1000);
$this->session->set('refreshToken', uniqid());
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->guard)
->user()->toBeNull();
expect($this->session)
->get('user')->toBeNull();
});
it('successfully continues a session when an access token succeeds is renewed', function (): void {
$this->session->set('accessTokenExpiration', time() - 1000);
$this->session->set('refreshToken', uniqid());
$response = (new ResponseFactory)->createResponse();
$token = Generator::create((createRsaKeys())->private, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => [
"https://example.com/health-api",
"https://my-domain.auth0.com/userinfo",
config('auth0.guards.default.clientId')
],
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new StreamFactory)->createStream(
json_encode(
value: [
'access_token' => $token->toString(),
'expires_in' => 60,
'scope' => 'openid profile',
'token_type' => 'Bearer',
],
flags: JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR
)
)
)
);
$found = $this->guard->find(Guard::SOURCE_SESSION);
$this->guard->login($found, Guard::SOURCE_SESSION);
expect($this->guard)
->user()->not()->toBeNull();
expect($this->session)
->get('user')->not()->toBeNull();
});
it('returns a consistent HMAC from hashPasswordForCookie()', function (): void {
config(['app.key' => 'test-app-key']);
$hash = $this->guard->hashPasswordForCookie('password-hash');
expect($hash)
->toBeString()
->toBe(hash_hmac('sha256', 'password-hash', 'test-app-key'));
});
it('returns different HMACs for different passwords from hashPasswordForCookie()', function (): void {
config(['app.key' => 'test-app-key']);
$hash1 = $this->guard->hashPasswordForCookie('password-one');
$hash2 = $this->guard->hashPasswordForCookie('password-two');
expect($hash1)->not()->toBe($hash2);
});
it('returns different HMACs for different app keys from hashPasswordForCookie()', function (): void {
config(['app.key' => 'key-one']);
$hash1 = $this->guard->hashPasswordForCookie('password-hash');
config(['app.key' => 'key-two']);
$hash2 = $this->guard->hashPasswordForCookie('password-hash');
expect($hash1)->not()->toBe($hash2);
});
it('uses fallback key when app.key is null in hashPasswordForCookie()', function (): void {
config(['app.key' => null]);
$hash = $this->guard->hashPasswordForCookie('password-hash');
expect($hash)
->toBeString()
->toBe(hash_hmac('sha256', 'password-hash', 'base-key-for-password-hash-mac'));
});
================================================
FILE: tests/Unit/Guards/AuthorizationGuardTest.php
================================================
group('auth', 'auth.guard', 'auth.guard.session');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => ['https://example.com/health-api'],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = $guard = auth('auth0-api');
$this->sdk = $this->laravel->getSdk();
$this->config = $this->sdk->configuration();
$this->identifier = 'auth0|' . uniqid();
$this->token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => $this->identifier,
"aud" => [
config('auth0.guards.default.audience')[0],
"https://my-domain.auth0.com/userinfo"
],
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->bearerToken = ['Authorization' => 'Bearer ' . $this->token->toString()];
$this->route = '/' . uniqid();
$this->route2 = '/' . uniqid();
$guard = $this->guard;
Route::get($this->route, function () use ($guard) {
$credential = $guard->find(Guard::SOURCE_TOKEN);
if (null !== $credential) {
$guard->login($credential, Guard::SOURCE_TOKEN);
return response()->json(['status' => 'OK', 'user' => $guard->user(Guard::SOURCE_TOKEN)->getAuthIdentifier()]);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
Route::get($this->route2, function () use ($guard) {
return response()->json(['user' => $guard->user(Guard::SOURCE_TOKEN)?->getAuthIdentifier()]);
});
});
it('assigns a user with login() from a good token', function (): void {
expect($this->guard)
->user()->toBeNull();
getJson($this->route, $this->bearerToken)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->not()->toBeNull();
});
it('assigns a user with user() from a good token', function (): void {
expect($this->guard)
->user()->toBeNull();
getJson($this->route2, $this->bearerToken)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->not()->toBeNull();
});
// it('does not assign a user from a empty token', function (): void {
// getJson($this->route, ['Authorization' => 'Bearer '])
// ->assertStatus(Response::HTTP_UNAUTHORIZED);
// expect($this->guard)
// ->user()->toBeNull();
// });
// it('does not get a user from a bad token', function (): void {
// $this->config->setAudience(['BAD_AUDIENCE']);
// expect($this->guard)
// ->user()->toBeNull();
// getJson($this->route, $this->bearerToken)
// ->assertStatus(Response::HTTP_UNAUTHORIZED);
// expect($this->guard)
// ->user()->toBeNull();
// });
it('does not query the /userinfo endpoint for refreshUser() without a bearer token', function (): void {
expect($this->guard)
->user()->toBeNull();
$this->guard->setCredential(new CredentialEntity(
user: new StatelessUser(['sub' => $this->identifier]),
));
expect($this->guard)
->user()->not()->toBeNull();
$client = new MockHttpClient(requestLimit: 0);
$this->config->setHttpClient($client);
$this->guard->refreshUser();
expect($this->guard)
->user()->not()->toBeNull();
});
it('aborts querying the /userinfo endpoint for refreshUser() when a bad response is received', function (): void {
expect($this->guard)
->user()->toBeNull();
getJson($this->route2, $this->bearerToken)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->getAuthIdentifier()->toBe($this->identifier);
$response = (new MockResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new MockStreamFactory)->createStream(
json_encode(
value: true,
flags: JSON_PRETTY_PRINT
)
)
)
);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toHaveKey('sub', $this->identifier);
});
it('queries the /userinfo endpoint for refreshUser()', function (): void {
expect($this->guard)
->user()->toBeNull();
getJson($this->route2, $this->bearerToken)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->getAuthIdentifier()->toBe($this->identifier);
$response = (new MockResponseFactory)->createResponse();
$this->guard
->sdk()
->configuration()
->getHttpClient()
->addResponseWildcard($response->withBody(
(new MockStreamFactory)->createStream(
json_encode(
value: [
'sub' => $this->identifier,
'name' => 'John Doe',
'email' => '...',
],
flags: JSON_PRETTY_PRINT
)
)
)
);
$this->guard->refreshUser();
$userAttributes = $this->guard->user()->getAttributes();
expect($userAttributes)
->toBeArray()
->toMatchArray([
'sub' => $this->identifier,
'name' => 'John Doe',
'email' => '...',
]);
});
================================================
FILE: tests/Unit/Middleware/AuthenticateMiddlewareTest.php
================================================
group('stateful', 'middleware', 'middleware.stateful', 'middleware.stateful.authenticate');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->validSession = [
'auth0_session' => json_encode([
'user' => ['sub' => 'hello|world'],
'idToken' => (string) Generator::create((createRsaKeys())->private),
'accessToken' => (string) Generator::create((createRsaKeys())->private),
'accessTokenScope' => [uniqid(), 'read:admin'],
'accessTokenExpiration' => time() + 60,
])
];
});
it('redirects to login route if a visitor does not have a session', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate')->get($route, function () use ($route): string {
return $route;
});
$this->withoutExceptionHandling()
->get($route)
->assertRedirect('/login');
expect(redirect()->getIntendedUrl())
->toEqual('http://localhost' . $route);
expect($this->guard)
->user()->toBeNull();
});
it('assigns a user', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate')->get($route, function () use ($route): string {
return $route;
});
$this->withSession($this->validSession)
->get($route)
->assertStatus(Response::HTTP_OK)
->assertSee($route);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('assigns a user when using a configured scope matches', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate:read:admin')->get($route, function () use ($route): string {
return $route;
});
$this->withSession($this->validSession)
->get($route)
->assertStatus(Response::HTTP_OK)
->assertSee($route);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('does not assign a user when a configured scope is not matched', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate:something:else')->get($route, function () use ($route): string {
return $route;
});
$this->withSession($this->validSession)
->get($route)
->assertStatus(Response::HTTP_FORBIDDEN);
expect($this->guard)
->user()->toBeNull();
});
it('does not assign a user when an incompatible guard is used', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate')->get($route, function () use ($route): string {
return $route;
});
config($config = [
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$this->get($route)
->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR);
expect($this->guard)
->user()->toBeNull();
});
================================================
FILE: tests/Unit/Middleware/AuthenticateOptionalMiddlewareTest.php
================================================
group('stateful', 'middleware', 'middleware.stateful', 'middleware.stateful.authenticate_optional');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->validSession = [
'auth0_session' => json_encode([
'user' => ['sub' => 'hello|world'],
'idToken' => (string) Generator::create((createRsaKeys())->private),
'accessToken' => (string) Generator::create((createRsaKeys())->private),
'accessTokenScope' => [uniqid(), 'read:admin'],
'accessTokenExpiration' => time() + 60,
]),
];
});
it('continues if a visitor does not have a session', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional')->get($route, function () use ($route): string {
return $route;
});
$this->get($route)
->assertStatus(Response::HTTP_OK)
->assertSee($route);
expect($this->guard)
->user()->toBeNull();
});
it('assigns a user', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional')->get($route, function () use ($route): string {
return $route;
});
$this->withSession($this->validSession)
->get($route)
->assertStatus(Response::HTTP_OK)
->assertSee($route);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('assigns a user when using a configured scope matches', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional:read:admin')->get($route, function () use ($route): string {
return $route;
});
$this->withSession($this->validSession)
->get($route)
->assertStatus(Response::HTTP_OK)
->assertSee($route);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('does not assign a user when a configured scope is not matched', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional:something:else')->get($route, function () use ($route): string {
return $route;
});
$this->withSession($this->validSession)
->get($route)
->assertStatus(Response::HTTP_OK)
->assertSee($route);
expect($this->guard)
->user()->toBeNull();
});
it('does not assign a user when an incompatible guard is used', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional')->get($route, function () use ($route): string {
return $route;
});
config($config = [
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$this->get($route)
->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR);
expect($this->guard)
->user()->toBeNull();
});
================================================
FILE: tests/Unit/Middleware/AuthenticatorMiddlewareTest.php
================================================
group('Middleware/AuthenticatorMiddleware');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_NONE,
]);
});
it('installs the Auth0 Authenticator', function (): void {
config(['auth.defaults.guard' => 'web']);
app(ServiceProvider::class, ['app' => app()])->registerMiddleware(app('router'));
Route::middleware('web')->get('/test', function (): JsonResponse {
if (auth()->guard()->name !== 'auth0-session') {
abort(Response::HTTP_INTERNAL_SERVER_ERROR);
}
return response()->json([
'guard' => auth()->guard()->name,
'middleware' => app('router')->getMiddlewareGroups()
]);
});
$this->get('/test')
->assertOK();
});
================================================
FILE: tests/Unit/Middleware/AuthorizeMiddlewareTest.php
================================================
group('stateful', 'middleware', 'middleware.stateless', 'middleware.stateless.authorize');
beforeEach(function (): void {
$this->secret = uniqid();
$this->domain = uniqid() . '.auth0.com';
$this->clientId = uniqid();
$this->audience = [uniqid()];
$this->cookieSecret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => $this->domain,
'auth0.guards.default.clientId' => $this->clientId,
'auth0.guards.default.audience' => $this->audience,
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => $this->cookieSecret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
// Also configure 'web' since legacyGuard uses configuration => 'web'
'auth0.guards.web.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.web.domain' => $this->domain,
'auth0.guards.web.clientId' => $this->clientId,
'auth0.guards.web.audience' => $this->audience,
'auth0.guards.web.clientSecret' => $this->secret,
'auth0.guards.web.cookieSecret' => $this->cookieSecret,
'auth0.guards.web.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
});
it('does not assign a user when an incompatible guard is used', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
config([
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . uniqid()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeNull();
});
it('returns a 401 and does not assign a user when an invalid bearer token is provided', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$this->getJson($route, ['Authorization' => 'Bearer ' . uniqid()])
->assertStatus(Response::HTTP_UNAUTHORIZED);
expect($this->guard)
->user()->toBeNull();
});
it('assigns a user', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => array_merge(
$this->audience,
[config('auth0.guards.default.clientId')]
),
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . $token->toString()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('assigns a user when using a configured scope matches', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize:read:admin')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => array_merge(
$this->audience,
[config('auth0.guards.default.clientId')]
),
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . $token->toString()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('returns a 403 and does not assign a user when a configured scope is not matched', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize:something:else')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => array_merge(
$this->audience,
[config('auth0.guards.default.clientId')]
),
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . $token->toString()])
->assertStatus(Response::HTTP_FORBIDDEN);
expect($this->guard)
->user()->toBeNull();
});
================================================
FILE: tests/Unit/Middleware/AuthorizeOptionalMiddlewareTest.php
================================================
group('stateful', 'middleware', 'middleware.stateless', 'middleware.stateless.authorize');
beforeEach(function (): void {
$this->secret = uniqid();
$this->domain = uniqid() . '.auth0.com';
$this->clientId = uniqid();
$this->audience = [uniqid()];
$this->cookieSecret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => $this->domain,
'auth0.guards.default.clientId' => $this->clientId,
'auth0.guards.default.audience' => $this->audience,
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => $this->cookieSecret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
// Also configure 'web' since legacyGuard uses configuration => 'web'
'auth0.guards.web.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.web.domain' => $this->domain,
'auth0.guards.web.clientId' => $this->clientId,
'auth0.guards.web.audience' => $this->audience,
'auth0.guards.web.clientSecret' => $this->secret,
'auth0.guards.web.cookieSecret' => $this->cookieSecret,
'auth0.guards.web.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
});
it('does not assign a user when an invalid bearer token is provided', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$this->getJson($route, ['Authorization' => 'Bearer ' . uniqid()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeNull();
});
it('assigns a user', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => array_merge(
$this->audience,
[config('auth0.guards.default.clientId')]
),
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . $token->toString()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('assigns a user when using a configured scope matches', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional:read:admin')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => array_merge(
$this->audience,
[config('auth0.guards.default.clientId')]
),
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . $token->toString()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('does not assign a user when a configured scope is not matched', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional:something:else')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
"sub" => "auth0|123456",
"aud" => array_merge(
$this->audience,
[config('auth0.guards.default.clientId')]
),
"azp" => config('auth0.guards.default.clientId'),
"exp" => time() + 60,
"iat" => time(),
"scope" => "openid profile read:patients read:admin"
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . $token->toString()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeNull();
});
it('does not assign a user when an incompatible guard is used', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional')->get($route, function () use ($route): string {
return json_encode(['status' => $route]);
});
config([
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$this->getJson($route, ['Authorization' => 'Bearer ' . uniqid()])
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route]);
expect($this->guard)
->user()->toBeNull();
});
================================================
FILE: tests/Unit/Middleware/AuthorizerMiddlewareTest.php
================================================
group('Middleware/AuthenticatorMiddleware');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_NONE,
]);
});
it('installs the Auth0 Authenticator', function (): void {
config(['auth.defaults.guard' => 'web']);
app(ServiceProvider::class, ['app' => app()])->registerMiddleware(app('router'));
Route::middleware('api')->get('/test', function (): JsonResponse {
if (auth()->guard()->name !== 'auth0-api') {
abort(Response::HTTP_INTERNAL_SERVER_ERROR);
}
return response()->json([
'guard' => auth()->guard()->name,
'middleware' => app('router')->getMiddlewareGroups()
]);
});
$this->get('/test')
->assertOK();
});
================================================
FILE: tests/Unit/Middleware/GuardMiddlewareTest.php
================================================
group('middleware', 'middleware.guard');
beforeEach(function (): void {
$this->laravel = app('auth0');
});
it('assigns the guard for route handling', function (): void {
$routeMiddlewareAssignedGuard = '/' . uniqid();
$routeMiddlewareUnassignedGuard = '/' . uniqid();
$routeUnspecifiedGuard = '/' . uniqid();
$defaultGuardClass = 'Illuminate\Auth\SessionGuard';
$sdkGuardClass = 'Auth0\Laravel\Auth\Guard';
config(['auth.defaults.guard' => 'web']);
Route::get($routeUnspecifiedGuard, function (): string {
return get_class(auth()->guard());
});
Route::middleware('guard:legacyGuard')->get($routeMiddlewareAssignedGuard, function (): string {
return get_class(auth()->guard());
});
Route::middleware('guard')->get($routeMiddlewareUnassignedGuard, function (): string {
return get_class(auth()->guard());
});
$this->get($routeUnspecifiedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($defaultGuardClass);
$this->get($routeMiddlewareAssignedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($sdkGuardClass);
$this->get($routeUnspecifiedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($sdkGuardClass);
$this->get($routeMiddlewareUnassignedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($defaultGuardClass);
$this->get($routeUnspecifiedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($defaultGuardClass);
});
it('assigns the guard for route group handling', function (): void {
$routeMiddlewareUnassignedGuard = '/' . uniqid();
$routeUnspecifiedGuard = '/' . uniqid();
$defaultGuardClass = 'Illuminate\Auth\SessionGuard';
$sdkGuardClass = 'Auth0\Laravel\Auth\Guard';
config(['auth.defaults.guard' => 'web']);
Route::middleware('guard:legacyGuard')->group(function () use ($routeUnspecifiedGuard, $routeMiddlewareUnassignedGuard) {
Route::get($routeUnspecifiedGuard, function (): string {
return get_class(auth()->guard());
});
Route::middleware('guard')->get($routeMiddlewareUnassignedGuard, function (): string {
return get_class(auth()->guard());
});
});
$this->get($routeUnspecifiedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($sdkGuardClass);
$this->get($routeMiddlewareUnassignedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($defaultGuardClass);
$this->get($routeUnspecifiedGuard)
->assertStatus(Response::HTTP_OK)
->assertSee($sdkGuardClass);
});
================================================
FILE: tests/Unit/ServiceProviderTest.php
================================================
group('ServiceProvider');
function setupGuardImpersonation(
array $profile = [],
array $scope = [],
array $permissions = [],
): Authenticatable {
Auth::shouldUse('legacyGuard');
$imposter = new ImposterUser(array_merge([
'sub' => uniqid(),
'name' => uniqid(),
'email' => uniqid() . '@example.com',
], $profile));
Auth::guard()->setImpersonating(CredentialEntity::create(
user: $imposter,
idToken: (string) Generator::create((createRsaKeys())->private),
accessToken: (string) Generator::create((createRsaKeys())->private),
accessTokenScope: $scope,
accessTokenDecoded: [
'permissions' => $permissions,
],
));
return $imposter;
}
function resetGuard(
?Authenticatable $imposter = null,
): void {
Auth::shouldUse('web');
if (null !== $imposter) {
Auth::setUser($imposter);
}
config([
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null,
]);
}
it('provides the expected classes', function (): void {
$service = app(ServiceProvider::class, ['app' => $this->app]);
expect($service->provides())
->toBe([
Auth0::class,
AuthenticateMiddleware::class,
AuthenticateOptionalMiddleware::class,
AuthenticationGuard::class,
AuthenticatorMiddleware::class,
AuthorizationGuard::class,
AuthorizeMiddleware::class,
AuthorizeOptionalMiddleware::class,
AuthorizerMiddleware::class,
CacheBridge::class,
CacheItemBridge::class,
CallbackController::class,
Configuration::class,
Guard::class,
GuardMiddleware::class,
LoginController::class,
LogoutController::class,
Service::class,
SessionBridge::class,
UserProvider::class,
UserRepository::class,
]);
});
it('creates a Service singleton with `auth0` alias', function (): void {
$singleton1 = $this->app->make('auth0');
$singleton2 = $this->app->make(Service::class);
expect($singleton1)
->toBeInstanceOf(Service::class);
expect($singleton2)
->toBeInstanceOf(Service::class);
expect($singleton1)
->toBe($singleton2);
});
it('does NOT create a Guard singleton', function (): void {
$singleton1 = auth()->guard('legacyGuard');
$singleton2 = $this->app->make(Guard::class);
expect($singleton1)
->toBeInstanceOf(Guard::class);
expect($singleton2)
->toBeInstanceOf(Guard::class);
expect($singleton1)
->not()->toBe($singleton2);
});
it('creates a UserRepository singleton', function (): void {
$singleton1 = $this->app->make('auth0.repository');
$singleton2 = $this->app->make(UserRepository::class);
expect($singleton1)
->toBeInstanceOf(UserRepository::class);
expect($singleton2)
->toBeInstanceOf(UserRepository::class);
expect($singleton1)
->toBe($singleton2);
});
it('does NOT a Provider singleton', function (): void {
$singleton1 = Auth::createUserProvider('auth0-provider');
$singleton2 = $this->app->make(UserProvider::class);
expect($singleton1)
->toBeInstanceOf(UserProvider::class);
expect($singleton2)
->toBeInstanceOf(UserProvider::class);
expect($singleton1)
->not()->toBe($singleton2);
});
it('creates a AuthenticateMiddleware singleton', function (): void {
$singleton1 = $this->app->make(AuthenticateMiddleware::class);
$singleton2 = $this->app->make(AuthenticateMiddleware::class);
expect($singleton1)
->toBeInstanceOf(AuthenticateMiddleware::class);
expect($singleton2)
->toBeInstanceOf(AuthenticateMiddleware::class);
expect($singleton1)
->toBe($singleton2);
});
it('creates a AuthenticateOptionalMiddleware singleton', function (): void {
$singleton1 = $this->app->make(AuthenticateOptionalMiddleware::class);
$singleton2 = $this->app->make(AuthenticateOptionalMiddleware::class);
expect($singleton1)
->toBeInstanceOf(AuthenticateOptionalMiddleware::class);
expect($singleton2)
->toBeInstanceOf(AuthenticateOptionalMiddleware::class);
expect($singleton1)
->toBe($singleton2);
});
it('creates a AuthorizeMiddleware singleton', function (): void {
$singleton1 = $this->app->make(AuthorizeMiddleware::class);
$singleton2 = $this->app->make(AuthorizeMiddleware::class);
expect($singleton1)
->toBeInstanceOf(AuthorizeMiddleware::class);
expect($singleton2)
->toBeInstanceOf(AuthorizeMiddleware::class);
expect($singleton1)
->toBe($singleton2);
});
it('creates a AuthorizeOptionalMiddleware singleton', function (): void {
$singleton1 = $this->app->make(AuthorizeOptionalMiddleware::class);
$singleton2 = $this->app->make(AuthorizeOptionalMiddleware::class);
expect($singleton1)
->toBeInstanceOf(AuthorizeOptionalMiddleware::class);
expect($singleton2)
->toBeInstanceOf(AuthorizeOptionalMiddleware::class);
expect($singleton1)
->toBe($singleton2);
});
it('creates a LoginController singleton', function (): void {
$singleton1 = $this->app->make(LoginController::class);
$singleton2 = $this->app->make(LoginController::class);
expect($singleton1)
->toBeInstanceOf(LoginController::class);
expect($singleton2)
->toBeInstanceOf(LoginController::class);
expect($singleton1)
->toBe($singleton2);
});
it('creates a LogoutController singleton', function (): void {
$singleton1 = $this->app->make(LogoutController::class);
$singleton2 = $this->app->make(LogoutController::class);
expect($singleton1)
->toBeInstanceOf(LogoutController::class);
expect($singleton2)
->toBeInstanceOf(LogoutController::class);
expect($singleton1)
->toBe($singleton2);
});
it('creates a CallbackController singleton', function (): void {
$singleton1 = $this->app->make(CallbackController::class);
$singleton2 = $this->app->make(CallbackController::class);
expect($singleton1)
->toBeInstanceOf(CallbackController::class);
expect($singleton2)
->toBeInstanceOf(CallbackController::class);
expect($singleton1)
->toBe($singleton2);
});
test('Gate::check(`scope`) returns true when a match hits', function (): void {
setupGuardImpersonation(
scope: [uniqid(), 'testScope', uniqid()],
);
expect(Gate::check('scope', 'testScope'))
->toBeTrue();
});
test('Gate::check(`scope`) returns false when a match misses', function (): void {
setupGuardImpersonation(
scope: [uniqid()],
);
expect(Gate::check('scope', 'testScope'))
->toBeFalse();
});
test('Gate::check(`scope`) returns false when an incompatible Guard is used', function (): void {
$imposter = setupGuardImpersonation(
scope: [uniqid(), 'testScope', uniqid()],
);
resetGuard($imposter);
expect(Gate::check('scope', 'testScope'))
->toBeFalse();
});
test('Gate::check(`permission`) returns true when a match hits', function (): void {
setupGuardImpersonation(
permissions: [uniqid(), 'testPermission', uniqid()],
);
expect(Gate::check('permission', 'testPermission'))
->toBeTrue();
});
test('Gate::check(`permission`) returns false when a match misses', function (): void {
setupGuardImpersonation(
permissions: [uniqid()],
);
expect(Gate::check('permission', 'testPermission'))
->toBeFalse();
});
test('Gate::check(`permission`) returns false when an incompatible Guard is used', function (): void {
$imposter = setupGuardImpersonation(
permissions: [uniqid(), 'testPermission', uniqid()],
);
resetGuard($imposter);
expect(Gate::check('permission', 'testPermission'))
->toBeFalse();
});
test('policies `can(scope)` middleware returns true when a match hits', function (): void {
Route::get('/test', function () {
return response()->json(['status' => 'OK']);
})->can('scope:testScope');
setupGuardImpersonation(
scope: [uniqid(), 'testScope', uniqid()],
);
$this->getJson('/test')
->assertOK();
});
test('policies `can(scope)` middleware returns false when a match misses', function (): void {
Route::get('/test', function () {
})->can('scope:testScope');
setupGuardImpersonation(
scope: [uniqid()],
);
$this->getJson('/test')
->assertStatus(Response::HTTP_FORBIDDEN);
});
test('policies `can(scope)` middleware returns false when an incompatible Guard is used', function (): void {
Route::get('/test', function () {
return response()->json(['status' => 'OK']);
})->can('scope:testScope');
$imposter = setupGuardImpersonation(
scope: [uniqid(), 'testScope', uniqid()],
);
resetGuard($imposter);
$this->getJson('/test')
->assertStatus(Response::HTTP_FORBIDDEN);
});
test('policies `can(permission)` middleware returns true when a match hits', function (): void {
Route::get('/test', function () {
return response()->json(['status' => 'OK']);
})->can('testing:123');
setupGuardImpersonation(
permissions: [uniqid(), 'testing:123', uniqid()],
);
$this->getJson('/test')
->assertOK();
});
test('policies `can(permission)` middleware returns false when a match misses', function (): void {
Route::get('/test', function () {
return response()->json(['status' => 'OK']);
})->can('testing:123');
setupGuardImpersonation(
permissions: [uniqid(), 'testing:456', uniqid()],
);
$this->getJson('/test')
->assertStatus(Response::HTTP_FORBIDDEN);
});
test('policies `can(permission)` middleware returns false when an incompatible Guard is used', function (): void {
Route::get('/test', function () {
return response()->json(['status' => 'OK']);
})->can('testing:123');
$imposter = setupGuardImpersonation(
permissions: [uniqid(), 'testing:123', uniqid()],
);
resetGuard($imposter);
$this->getJson('/test')
->assertStatus(Response::HTTP_FORBIDDEN);
});
test('auth0.registerGuards === true registers guards', function (): void {
config(['auth0.registerGuards' => true]);
$service = app(ServiceProvider::class, ['app' => $this->app]);
/**
* @var ServiceProvider $service
*/
$service->register();
expect(config('auth.guards.auth0-session'))
->toBeArray()
->toHaveKey('driver', 'auth0.authenticator')
->toHaveKey('configuration', 'web')
->toHaveKey('provider', 'auth0-provider');
expect(config('auth.guards.auth0-api'))
->toBeArray()
->toHaveKey('driver', 'auth0.authorizer')
->toHaveKey('configuration', 'api')
->toHaveKey('provider', 'auth0-provider');
expect(config('auth.providers.auth0-provider'))
->toBeArray()
->toHaveKey('driver', 'auth0.provider')
->toHaveKey('repository', 'auth0.repository');
});
test('auth0.registerGuards === true registers guards, but does not overwrite an existing auth.guards.auth0-session entry', function (): void {
config([
'auth0.registerGuards' => true,
'auth.guards.auth0-session' => [
'driver' => 'session',
'provider' => 'users',
]
]);
$service = app(ServiceProvider::class, ['app' => $this->app]);
/**
* @var ServiceProvider $service
*/
$service->register();
expect(config('auth.guards.auth0-session'))
->toBeArray()
->toHaveKey('driver', 'session')
->toHaveKey('provider', 'users')
->not()->toHaveKey('configuration');
expect(config('auth.guards.auth0-api'))
->toBeArray()
->toHaveKey('driver', 'auth0.authorizer')
->toHaveKey('configuration', 'api')
->toHaveKey('provider', 'auth0-provider');
expect(config('auth.providers.auth0-provider'))
->toBeArray()
->toHaveKey('driver', 'auth0.provider')
->toHaveKey('repository', 'auth0.repository');
});
test('auth0.registerGuards === true registers guards, but does not overwrite an existing auth.guards.auth0-api entry', function (): void {
config([
'auth0.registerGuards' => true,
'auth.guards.auth0-api' => [
'driver' => 'api',
'provider' => 'users',
]
]);
$service = app(ServiceProvider::class, ['app' => $this->app]);
/**
* @var ServiceProvider $service
*/
$service->register();
expect(config('auth.guards.auth0-session'))
->toBeArray()
->toHaveKey('driver', 'auth0.authenticator')
->toHaveKey('configuration', 'web')
->toHaveKey('provider', 'auth0-provider');
expect(config('auth.guards.auth0-api'))
->toBeArray()
->toHaveKey('driver', 'api')
->toHaveKey('provider', 'users')
->not()->toHaveKey('configuration');
expect(config('auth.providers.auth0-provider'))
->toBeArray()
->toHaveKey('driver', 'auth0.provider')
->toHaveKey('repository', 'auth0.repository');
});
test('auth0.registerGuards === true registers guards, but does not overwrite an existing auth.providers.auth0-provider entry', function (): void {
config([
'auth0.registerGuards' => true,
'auth.providers.auth0-provider' => [
'driver' => 'database',
'repository' => 'users',
]
]);
$service = app(ServiceProvider::class, ['app' => $this->app]);
/**
* @var ServiceProvider $service
*/
$service->register();
expect(config('auth.guards.auth0-session'))
->toBeArray()
->toHaveKey('driver', 'auth0.authenticator')
->toHaveKey('configuration', 'web')
->toHaveKey('provider', 'auth0-provider');
expect(config('auth.guards.auth0-api'))
->toBeArray()
->toHaveKey('driver', 'auth0.authorizer')
->toHaveKey('configuration', 'api')
->toHaveKey('provider', 'auth0-provider');
expect(config('auth.providers.auth0-provider'))
->toBeArray()
->toHaveKey('driver', 'database')
->toHaveKey('repository', 'users');
});
test('auth0.registerMiddleware === true registers middleware', function (): void {
config(['auth0.registerMiddleware' => true]);
$service = app(ServiceProvider::class, ['app' => $this->app]);
/**
* @var ServiceProvider $service
*/
$service->registerMiddleware(app('router'));
/**
* @var \Illuminate\Foundation\Http\Kernel $kernel
*/
$middleware = app('router')->getMiddlewareGroups();
expect($middleware)
->toBeArray()
->toHaveKeys(['web', 'api']);
expect($middleware['web'])
->toContain(AuthenticatorMiddleware::class);
expect($middleware['api'])
->toContain(AuthorizerMiddleware::class);
});
test('auth0.registerAuthenticationRoutes === true registers routes', function (): void {
config(['auth0.registerAuthenticationRoutes' => true]);
$service = app(ServiceProvider::class, ['app' => $this->app]);
/**
* @var ServiceProvider $service
*/
$service->registerRoutes();
$routes = (array) Route::getRoutes()->get('GET');
expect($routes)
->toHaveKeys(['login', 'logout', 'callback']);
});
================================================
FILE: tests/Unit/ServiceTest.php
================================================
group('Service');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$this->config = $this->sdk->configuration();
$this->session = $this->config->getSessionStorage();
});
it('returns a Management API class', function (): void {
expect($this->laravel->management())->toBeInstanceOf(ManagementInterface::class);
});
it('can get/set the configuration', function (): void {
expect($this->laravel->getConfiguration())->toBeInstanceOf(SdkConfiguration::class);
$configuration = new SdkConfiguration(['strategy' => 'none', 'domain' => uniqid() . '.auth0.test']);
$this->laravel->setConfiguration($configuration);
expect($this->laravel->getConfiguration())->toBe($configuration);
$domain = uniqid() . '.auth0.test';
$configuration->setDomain($domain);
expect($this->laravel->getConfiguration()->getDomain())->toBe($domain);
$configuration = new SdkConfiguration(['strategy' => 'none', 'domain' => uniqid() . '.auth0.test']);
$this->laravel->setConfiguration($configuration);
expect($this->laravel->getConfiguration())->toBe($configuration);
$sdk = $this->laravel->getSdk();
$configuration = new SdkConfiguration(['strategy' => 'none', 'domain' => uniqid() . '.auth0.test']);
$this->laravel->setConfiguration($configuration);
expect($this->laravel->getConfiguration())->toBe($configuration);
expect($sdk->configuration())->toBe($configuration);
});
it('can get the sdk credentials', function (): void {
expect($this->laravel->getCredentials())
->toBeNull();
$this->session->set('user', ['sub' => 'hello|world']);
$this->session->set('idToken', (string) Generator::create((createRsaKeys())->private));
$this->session->set('accessToken', (string) Generator::create((createRsaKeys())->private));
$this->session->set('accessTokenScope', [uniqid()]);
$this->session->set('accessTokenExpiration', time() - 1000);
// As we manually set the session values, we need to refresh the SDK state to ensure it's in sync.
$this->sdk->refreshState();
expect($this->laravel->getCredentials())
->toBeObject()
->toHaveProperty('accessToken', $this->session->get('accessToken'))
->toHaveProperty('accessTokenScope', $this->session->get('accessTokenScope'))
->toHaveProperty('accessTokenExpiration', $this->session->get('accessTokenExpiration'))
->toHaveProperty('idToken', $this->session->get('idToken'))
->toHaveProperty('user', $this->session->get('user'));
});
it('can get/set the SDK', function (): void {
expect($this->laravel->getSdk())->toBeInstanceOf(SdkContract::class);
$sdk = new SDKAuth0(['strategy' => 'none']);
$this->laravel->setSdk($sdk);
expect($this->laravel->getSdk())->toBeInstanceOf(SdkContract::class);
});
it('can reset the internal static state', function (): void {
$cache = spl_object_id($this->laravel->getSdk());
unset($this->laravel); // Force the object to be destroyed. Static state will remain.
$laravel = app('auth0');
$updated = spl_object_id($laravel->getSdk());
expect($cache)->toBe($updated);
$laravel->reset(); // Reset the static state.
$laravel = app('auth0');
$updated = spl_object_id($laravel->getSdk());
expect($cache)->not->toBe($updated);
});
test('bootStrategy() rejects non-string values', function (): void {
$method = new ReflectionMethod(Service::class, 'bootStrategy');
$method->setAccessible(true);
expect($method->invoke($this->laravel, ['strategy' => 123]))
->toMatchArray(['strategy' => SdkConfiguration::STRATEGY_REGULAR]);
});
test('bootSessionStorage() behaves as expected', function (): void {
$method = new ReflectionMethod(Service::class, 'bootSessionStorage');
$method->setAccessible(true);
expect($method->invoke($this->laravel, []))
->sessionStorage->toBeInstanceOf(SessionBridgeContract::class);
expect($method->invoke($this->laravel, ['sessionStorage' => null]))
->sessionStorage->toBeInstanceOf(SessionBridgeContract::class);
expect($method->invoke($this->laravel, ['sessionStorage' => false]))
->sessionStorage->toBeNull();
expect($method->invoke($this->laravel, ['sessionStorage' => CacheBridge::class]))
->sessionStorage->toBeNull();
expect($method->invoke($this->laravel, ['sessionStorage' => MemoryStore::class]))
->sessionStorage->toBeInstanceOf(MemoryStore::class);
$this->app->singleton('testStore', static fn (): MemoryStore => app(MemoryStore::class));
expect($method->invoke($this->laravel, ['sessionStorage' => 'testStore']))
->sessionStorage->toBeInstanceOf(MemoryStore::class);
});
test('bootTransientStorage() behaves as expected', function (): void {
$method = new ReflectionMethod(Service::class, 'bootTransientStorage');
$method->setAccessible(true);
expect($method->invoke($this->laravel, []))
->transientStorage->toBeInstanceOf(SessionBridgeContract::class);
expect($method->invoke($this->laravel, ['transientStorage' => null]))
->transientStorage->toBeInstanceOf(SessionBridgeContract::class);
expect($method->invoke($this->laravel, ['transientStorage' => false]))
->transientStorage->toBeNull();
expect($method->invoke($this->laravel, ['transientStorage' => CacheBridge::class]))
->transientStorage->toBeNull();
expect($method->invoke($this->laravel, ['transientStorage' => MemoryStore::class]))
->transientStorage->toBeInstanceOf(MemoryStore::class);
$this->app->singleton('testStore', static fn (): MemoryStore => app(MemoryStore::class));
expect($method->invoke($this->laravel, ['transientStorage' => 'testStore']))
->transientStorage->toBeInstanceOf(MemoryStore::class);
});
test('bootTokenCache() behaves as expected', function (): void {
$method = new ReflectionMethod(Service::class, 'bootTokenCache');
$method->setAccessible(true);
expect($method->invoke($this->laravel, []))
->tokenCache->toBeInstanceOf(CacheBridgeContract::class);
expect($method->invoke($this->laravel, ['tokenCache' => null]))
->tokenCache->toBeInstanceOf(CacheBridgeContract::class);
expect($method->invoke($this->laravel, ['tokenCache' => CacheBridge::class]))
->tokenCache->toBeInstanceOf(CacheBridgeContract::class);
expect($method->invoke($this->laravel, ['tokenCache' => false]))
->tokenCache->toBeNull();
expect($method->invoke($this->laravel, ['tokenCache' => MemoryStore::class]))
->tokenCache->toBeNull();
expect($method->invoke($this->laravel, ['tokenCache' => 'cache.psr6']))
->tokenCache->toBeInstanceOf(CacheItemPoolInterface::class);
});
test('bootBackchannelLogoutCache() behaves as expected', function (): void {
$method = new ReflectionMethod(Service::class, 'bootBackchannelLogoutCache');
$method->setAccessible(true);
expect($method->invoke($this->laravel, []))
->backchannelLogoutCache->toBeInstanceOf(CacheBridgeContract::class);
expect($method->invoke($this->laravel, ['backchannelLogoutCache' => null]))
->backchannelLogoutCache->toBeInstanceOf(CacheBridgeContract::class);
expect($method->invoke($this->laravel, ['backchannelLogoutCache' => CacheBridge::class]))
->backchannelLogoutCache->toBeInstanceOf(CacheBridgeContract::class);
expect($method->invoke($this->laravel, ['backchannelLogoutCache' => false]))
->backchannelLogoutCache->toBeNull();
expect($method->invoke($this->laravel, ['backchannelLogoutCache' => MemoryStore::class]))
->backchannelLogoutCache->toBeNull();
expect($method->invoke($this->laravel, ['backchannelLogoutCache' => 'cache.psr6']))
->backchannelLogoutCache->toBeInstanceOf(CacheItemPoolInterface::class);
});
// test('bootManagementTokenCache() behaves as expected', function (): void {
// $method = new ReflectionMethod(Service::class, 'bootManagementTokenCache');
// $method->setAccessible(true);
// expect($method->invoke($this->laravel, []))
// ->managementTokenCache->toBeInstanceOf(CacheBridgeContract::class);
// expect($method->invoke($this->laravel, ['managementTokenCache' => null]))
// ->managementTokenCache->toBeInstanceOf(CacheBridgeContract::class);
// expect($method->invoke($this->laravel, ['managementTokenCache' => CacheBridgeContract::class]))
// ->managementTokenCache->toBeInstanceOf(CacheBridgeContract::class);
// expect($method->invoke($this->laravel, ['managementTokenCache' => false]))
// ->managementTokenCache->toBeNull();
// expect($method->invoke($this->laravel, ['managementTokenCache' => MemoryStore::class]))
// ->managementTokenCache->toBeNull();
// expect($method->invoke($this->laravel, ['managementTokenCache' => 'cache.psr6']))
// ->managementTokenCache->toBeInstanceOf(CacheItemPoolInterface::class);
// });
test('json() behaves as expected', function (): void {
$factory = new ResponseFactory;
$response = $factory->createResponse(200);
$response->getBody()->write('{"foo":"bar"}');
expect(Service::json($response))
->toBe(['foo' => 'bar']);
$response = $factory->createResponse(500);
$response->getBody()->write('{"foo":"bar"}');
expect(Service::json($response))
->toBeNull();
$response = $factory->createResponse(200);
$response->getBody()->write(json_encode(true));
expect(Service::json($response))
->toBeNull();
});
test('routes() behaves as expected', function (): void {
Service::routes();
expect((array) Route::getRoutes()->get('GET'))
->toHaveKeys(['login', 'logout', 'callback']);
});
================================================
FILE: tests/Unit/Traits/ActingAsAuth0UserTest.php
================================================
group('trait', 'impersonation', 'acting-as-auth0-user');
uses(ActingAsAuth0User::class);
beforeEach(function (): void {
$this->secret = uniqid();
$this->user = ['sub' => uniqid(), 'scope' => 'openid profile email read:messages'];
});
it('impersonates with other guards', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK);
expect(auth()->guard()->user())->not()->toBeNull();
});
it('impersonates a user against auth0.authenticate', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$route = '/' . uniqid();
Route::middleware('auth0.authenticate')->get($route, function () use ($route) {
return response()->json([
'user' => auth()->user(),
'status' => $route
]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_SESSION)
->withoutExceptionHandling()
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJsonFragment(['sub' => $this->user['sub']]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('impersonates a user against auth0.authenticate.optional', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional')->get($route, function () use ($route) {
return response()->json([
'user' => auth()->user(),
'status' => $route
]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJsonFragment(['sub' => $this->user['sub']]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('impersonates a user against auth0.authenticate using a scope', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$route = '/' . uniqid();
Route::middleware('auth0.authenticate:read:messages')->get($route, function () use ($route) {
return response()->json([
'user' => auth()->user(),
'status' => $route
]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJsonFragment(['sub' => $this->user['sub']]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('impersonates a user against auth0.authorize', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => [uniqid()],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route) {
return response()->json([
'user' => auth()->user(),
'status' => $route
]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJsonFragment(['sub' => $this->user['sub']]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('impersonates a user against auth0.authorize.optional', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => [uniqid()],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional')->get($route, function () use ($route) {
return response()->json([
'user' => auth()->user(),
'status' => $route
]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJsonFragment(['sub' => $this->user['sub']]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
it('impersonates a user against auth0.authorize using a scope', function (): void {
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => [uniqid()],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
$route = '/' . uniqid();
Route::middleware('auth0.authorize:read:messages')->get($route, function () use ($route) {
return response()->json([
'user' => auth()->user(),
'status' => $route
]);
});
$this->actingAsAuth0User(attributes: $this->user, source: Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJsonFragment(['sub' => $this->user['sub']]);
expect($this->guard)
->user()->toBeInstanceOf(UserContract::class);
});
================================================
FILE: tests/Unit/Traits/ImpersonateTest.php
================================================
group('trait', 'impersonate');
uses(Impersonate::class);
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_API,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.audience' => [uniqid()],
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
'auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256,
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
});
it('impersonates with other guards', function (): void {
config([
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->impersonate($imposter)
->getJson($route)
->assertStatus(Response::HTTP_OK);
expect($this->guard)
->user()->toBeNull();
});
it('impersonates a user against auth0.authenticate', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->impersonate($imposter, Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJson(['user' => json_encode($imposter->getUser())]);
expect($this->guard)
->user()->toBeInstanceOf(ImposterUser::class)
->toBe($imposter->getUser());
});
it('impersonates a user against auth0.authenticate.optional', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate.optional')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->impersonate($imposter, Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJson(['user' => json_encode($imposter->getUser())]);
expect($this->guard)
->user()->toBeInstanceOf(ImposterUser::class)
->toBe($imposter->getUser());
});
it('impersonates a user against auth0.authenticate using a scope', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authenticate:read:messages')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->impersonate($imposter, Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJson(['user' => json_encode($imposter->getUser())]);
expect($this->guard)
->user()->toBeInstanceOf(ImposterUser::class)
->toBe($imposter->getUser());
});
it('impersonates a user against auth0.authorize', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->impersonate($imposter, Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJson(['user' => json_encode($imposter->getUser())]);
expect($this->guard)
->user()->toBeInstanceOf(ImposterUser::class)
->toBe($imposter->getUser());
});
it('impersonates a user against auth0.authorize.optional', function (): void {
$route = '/' . uniqid();
Route::middleware('auth0.authorize.optional')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
$this->impersonate($imposter, Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJson(['user' => json_encode($imposter->getUser())]);
expect($this->guard)
->user()->toBeInstanceOf(ImposterUser::class)
->toBe($imposter->getUser());
});
it('impersonates a user against auth0.authorize using a scope', function (): void {
$route = '/' . uniqid();
$imposter = CredentialEntity::create(
user: new ImposterUser(['sub' => uniqid()]),
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600
);
Route::middleware('auth0.authorize:read:messages')->get($route, function () use ($route): string {
return json_encode(['user' => json_encode(auth()->user()), 'status' => $route]);
});
$this->impersonate($imposter, Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK)
->assertJson(['status' => $route])
->assertJson(['user' => json_encode($imposter->getUser())]);
expect($this->guard)
->user()->toBeInstanceOf(ImposterUser::class)
->toBe($imposter->getUser());
});
it('AuthenticationGuard returns the impersonated user', function (): void {
config([
'auth.defaults.guard' => 'auth0-session',
]);
$route = '/' . uniqid();
Route::get($route, function () use ($route): string {
return json_encode(['route' => get_class(auth()->guard()), 'user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = new ImposterUser(['sub' => uniqid()]);
$credential = CredentialEntity::create(
user: $imposter,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600,
refreshToken: uniqid(),
);
$response = $this->impersonate($credential)
->getJson($route)
->assertStatus(Response::HTTP_OK);
expect($response->json())
->route->toBe(AuthenticationGuard::class)
->user->json()->sub->toBe($imposter->getAuthIdentifier());
expect(auth('legacyGuard'))
->user()->toBeNull();
expect(auth('auth0-api'))
->user()->toBeNull();
expect(auth('auth0-session'))
->isImpersonating()->toBeTrue()
->user()->toEqual($imposter)
->find()->toEqual($credential)
->findSession()->toEqual($credential)
->getCredential()->toEqual($credential);
$client = new MockHttpClient(requestLimit: 0);
$this->sdk->configuration()->setHttpClient($client);
expect(auth('auth0-session'))
->refreshUser();
auth('auth0-session')->setUser(new ImposterUser(['sub' => uniqid()]));
expect(auth('auth0-session'))
->isImpersonating()->toBeFalse()
->user()->not()->toEqual($imposter)
->find()->not()->toEqual($credential)
->findSession()->not()->toEqual($credential)
->getCredential()->not()->toEqual($credential);
});
it('AuthorizationGuard returns the impersonated user', function (): void {
config([
'auth.defaults.guard' => 'auth0-api',
]);
$route = '/' . uniqid();
Route::get($route, function () use ($route): string {
return json_encode(['route' => get_class(auth()->guard()), 'user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = new ImposterUser(['sub' => uniqid()]);
$credential = CredentialEntity::create(
user: $imposter,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600,
refreshToken: uniqid(),
);
$response = $this->impersonate($credential)
->getJson($route)
->assertStatus(Response::HTTP_OK);
expect($response->json())
->route->toBe(AuthorizationGuard::class)
->user->json()->sub->toBe($imposter->getAuthIdentifier());
expect(auth('legacyGuard'))
->user()->toBeNull();
expect(auth('auth0-session'))
->user()->toBeNull();
expect(auth('auth0-api'))
->isImpersonating()->toBeTrue()
->getImposterSource()->toBe(Guard::SOURCE_TOKEN)
->user()->toEqual($imposter)
->find()->toEqual($credential)
->findToken()->toEqual($credential)
->getCredential()->toEqual($credential);
$client = new MockHttpClient(requestLimit: 0);
$this->sdk->configuration()->setHttpClient($client);
expect(auth('auth0-api'))
->refreshUser();
auth('auth0-api')->setUser(new ImposterUser(['sub' => uniqid()]));
expect(auth('auth0-api'))
->isImpersonating()->toBeFalse()
->user()->not()->toEqual($imposter)
->find()->not()->toEqual($credential)
->findToken()->not()->toEqual($credential)
->getCredential()->not()->toEqual($credential);
});
it('Guard returns the impersonated user', function (): void {
config([
'auth.defaults.guard' => 'legacyGuard',
]);
$route = '/' . uniqid();
Route::get($route, function () use ($route): string {
return json_encode(['route' => get_class(auth()->guard()), 'user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = new ImposterUser(['sub' => uniqid()]);
$credential = CredentialEntity::create(
user: $imposter,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600,
refreshToken: uniqid(),
);
$response = $this->impersonate(credential: $credential, source: Guard::SOURCE_SESSION)
->getJson($route)
->assertStatus(Response::HTTP_OK);
expect($response->json())
->route->toBe(Guard::class)
->user->json()->sub->toBe($imposter->getAuthIdentifier());
expect(auth('auth0-api'))
->user()->toBeNull();
expect(auth('auth0-session'))
->user()->toBeNull();
expect(auth('legacyGuard'))
->isImpersonating()->toBeTrue()
->getImposterSource()->toBe(Guard::SOURCE_SESSION)
->user()->toEqual($imposter)
->find()->toEqual($credential)
->getCredential()->toEqual($credential);
$client = new MockHttpClient(requestLimit: 0);
$this->sdk->configuration()->setHttpClient($client);
auth('legacyGuard')->refreshUser();
auth('legacyGuard')->setUser(new ImposterUser(['sub' => uniqid()]));
expect(auth('legacyGuard'))
->isImpersonating()->toBeFalse()
->user()->not()->toEqual($imposter)
->find()->not()->toEqual($credential)
->getCredential()->not()->toEqual($credential);
});
it('Guard clears the impersonated user during logout()', function (): void {
config([
'auth.defaults.guard' => 'legacyGuard',
]);
$route = '/' . uniqid();
Route::get($route, function () use ($route): string {
return json_encode(['route' => get_class(auth()->guard()), 'user' => json_encode(auth()->user()), 'status' => $route]);
});
$imposter = new ImposterUser(['sub' => uniqid()]);
$credential = CredentialEntity::create(
user: $imposter,
idToken: mockIdToken(algorithm: Token::ALGO_HS256),
accessToken: mockAccessToken(algorithm: Token::ALGO_HS256),
accessTokenScope: ['openid', 'profile', 'email', 'read:messages'],
accessTokenExpiration: time() + 3600,
refreshToken: uniqid(),
);
$response = $this->impersonate(credential: $credential, source: Guard::SOURCE_TOKEN)
->getJson($route)
->assertStatus(Response::HTTP_OK);
expect($response->json())
->route->toBe(Guard::class)
->user->json()->sub->toBe($imposter->getAuthIdentifier());
expect(auth('legacyGuard'))
->isImpersonating()->toBeTrue()
->getImposterSource()->toBe(Guard::SOURCE_TOKEN)
->user()->toEqual($imposter)
->find()->toEqual($credential)
->getCredential()->toEqual($credential);
auth('legacyGuard')->logout();
expect(auth('legacyGuard'))
->isImpersonating()->toBeFalse()
->user()->toBeNull()
->find()->toBeNull()
->getCredential()->toBeNull();
});
================================================
FILE: tests/Unit/UserProviderTest.php
================================================
group('UserProvider');
beforeEach(function (): void {
$this->secret = uniqid();
config([
'auth0.AUTH0_CONFIG_VERSION' => 2,
'auth0.guards.default.strategy' => SdkConfiguration::STRATEGY_REGULAR,
'auth0.guards.default.domain' => uniqid() . '.auth0.com',
'auth0.guards.default.clientId' => uniqid(),
'auth0.guards.default.clientSecret' => $this->secret,
'auth0.guards.default.cookieSecret' => uniqid(),
]);
$this->laravel = app('auth0');
$this->guard = auth('legacyGuard');
$this->sdk = $this->laravel->getSdk();
});
test('retrieveByToken() returns null when an incompatible guard token is used', function (): void {
config([
'auth.defaults.guard' => 'web',
'auth.guards.legacyGuard' => null
]);
$route = '/' . uniqid();
Route::get($route, function () {
$provider = Auth::createUserProvider('auth0-provider');
$credential = $provider->retrieveByToken('token', '');
if (null === $credential) {
return response()->json(['status' => 'OK']);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
$this->getJson($route)
->assertOK();
});
test('retrieveByToken() returns null when an invalid token is provided', function (): void {
config(['auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256]);
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://123.' . config('auth0.guards.default.domain') . '/',
'sub' => 'hello|world',
'aud' => config('auth0.guards.default.clientId'),
'iat' => time(),
'exp' => time() + 60,
'azp' => config('auth0.guards.default.clientId'),
'scope' => 'openid profile email'
], []);
$route = '/' . uniqid();
Route::get($route, function () use ($token) {
$provider = Auth::createUserProvider('auth0-provider');
$credential = $provider->retrieveByToken('token', $token);
if (null === $credential) {
return response()->json(['status' => 'OK']);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
$this->getJson($route)
->assertOK();
});
test('retrieveByToken() returns a user when a valid token is provided', function (): void {
config(['auth0.guards.default.tokenAlgorithm' => Token::ALGO_HS256]);
$token = Generator::create($this->secret, Token::ALGO_HS256, [
"iss" => 'https://' . config('auth0.guards.default.domain') . '/',
'sub' => 'hello|world',
'aud' => config('auth0.guards.default.clientId'),
'iat' => time(),
'exp' => time() + 60,
'azp' => config('auth0.guards.default.clientId'),
'scope' => 'openid profile email'
], []);
$route = '/' . uniqid();
Route::get($route, function () use ($token) {
$provider = Auth::createUserProvider('auth0-provider');
$credential = $provider->retrieveByToken('token', (string) $token);
if (null !== $credential) {
return response()->json(['status' => 'OK']);
}
abort(Response::HTTP_UNAUTHORIZED, 'Unauthorized');
});
$this->getJson($route)
->assertOK();
});
test('validateCredentials() always returns false', function (): void {
$provider = Auth::createUserProvider('auth0-provider');
$user = new StatefulUser();
expect($provider->validateCredentials($user, []))
->toBeFalse();
});
test('getRepository() throws an error when an non-existent repository provider is set', function (): void {
$provider = new UserProvider(['model' => 'MISSING']);
$provider->getRepository();
})->throws(BindingResolutionException::class);
test('getRepository() throws an error when an invalid repository provider is set', function (): void {
$provider = new UserProvider(['model' => ['ARRAY']]);
$provider->getRepository();
})->throws(BindingResolutionException::class);
test('setRepository() sets the repository model', function (): void {
$provider = new UserProvider(['model' => uniqid()]);
$repository = new UserRepository();
$provider->setRepository($repository::class);
expect($provider->getRepository())
->toBeInstanceOf($repository::class);
});
test('setRepository() with the same repository identifier uses the cached repository instance', function (): void {
$provider = new UserProvider(['model' => 'MISSING']);
$repository = new UserRepository();
$provider->setRepository($repository::class);
expect($provider->getRepository())
->toBeInstanceOf($repository::class);
$provider->setRepository($repository::class);
expect($provider->getRepository())
->toBeInstanceOf($repository::class);
});
test('retrieveByCredentials() returns `null` when an empty array is provided', function (): void {
$provider = new UserProvider(['model' => uniqid()]);
$repository = new UserRepository();
expect($provider->retrieveByCredentials([]))->toBeNull();
});
================================================
FILE: tests/Unit/UserRepositoryTest.php
================================================
group('UserRepository');
it('returns a stateful user model from session queries', function (): void {
$repository = $this->app['auth0.repository'];
expect($repository->fromSession(['name' => 'Stateful']))
->toBeInstanceOf(StatefulUser::class)
->name->toBe('Stateful');
});
it('returns a stateless user model from access token queries', function (): void {
$repository = $this->app['auth0.repository'];
expect($repository->fromAccessToken(['name' => 'Stateless']))
->toBeInstanceOf(StatelessUser::class)
->name->toBe('Stateless');
});
================================================
FILE: tests/Unit/Users/UserTest.php
================================================
group('stateful', 'model', 'model.user');
it('fills attributes provided to the constructor', function (): void {
$user = new ImposterUser(['testing' => 'testing']);
expect($user->testing)
->toBe('testing');
});
it('fills attributes', function (): void {
$user = new ImposterUser();
$user->fill(['testing' => 'testing']);
expect($user->testing)
->toBe('testing');
});
it('sets attributes with magic', function (): void {
$user = new ImposterUser();
$user->testing = 'testing';
expect($user->testing)
->toBe('testing');
});
it('sets attributes', function (): void {
$user = new ImposterUser();
$user->setAttribute('testing', 'testing');
expect($user->getAttribute('testing'))
->toBe('testing');
});
it('gets attributes array', function (): void {
$user = new ImposterUser([
'testing' => 'testing',
'testing2' => 'testing2',
]);
expect($user->getAttributes())
->toBeArray()
->toContain('testing')
->toContain('testing2');
});
it('supports getting the identifier', function (): void {
$user = new ImposterUser(['sub' => 'testing']);
expect($user->getAuthIdentifier())
->toBe('testing');
});
it('supports getting the identifier name', function (): void {
$user = new ImposterUser(['sub' => 'testing']);
expect($user->getAuthIdentifierName())
->toBe('id');
});
it('supports getting the password', function (): void {
$user = new ImposterUser();
expect($user->getAuthPassword())
->toBe('');
});
it('supports getting the password name', function (): void {
$user = new ImposterUser();
expect($user->getAuthPasswordName())
->toBe('password');
});
it('supports getting the remember token', function (): void {
$user = new ImposterUser();
expect($user->getRememberToken())
->toBe('');
});
it('supports getting the remember token name', function (): void {
$user = new ImposterUser();
expect($user->getRememberTokenName())
->toBe('');
});
it('supports setting the remember token', function (): void {
$user = new ImposterUser();
expect($user->setRememberToken('testing'))
->toBeNull();
});
it('supports JSON serialization', function (): void {
$user = new ImposterUser(['testing' => 'testing']);
expect($user->jsonSerialize())
->toBeArray()
->toContain('testing');
expect(json_encode($user))
->toBeJson('{"testing":"testing"}');
});