Full Code of thephpleague/oauth2-server for AI

master 948b278fc873 cached
143 files
564.1 KB
135.2k tokens
739 symbols
1 requests
Download .txt
Showing preview only (604K chars total). Download the full file or copy to clipboard to get everything.
Repository: thephpleague/oauth2-server
Branch: master
Commit: 948b278fc873
Files: 143
Total size: 564.1 KB

Directory structure:
gitextract_nrzlm6b4/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── backwards-compatibility.yml
│       ├── coding-standards.yml
│       ├── static-analysis.yml
│       └── tests.yml
├── .gitignore
├── .scrutinizer.yml
├── .styleci.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── examples/
│   ├── README.md
│   ├── composer.json
│   ├── public/
│   │   ├── api.php
│   │   ├── auth_code.php
│   │   ├── client_credentials.php
│   │   ├── device_code.php
│   │   ├── implicit.php
│   │   ├── middleware_use.php
│   │   ├── password.php
│   │   └── refresh_token.php
│   └── src/
│       ├── Entities/
│       │   ├── AccessTokenEntity.php
│       │   ├── AuthCodeEntity.php
│       │   ├── ClientEntity.php
│       │   ├── DeviceCodeEntity.php
│       │   ├── RefreshTokenEntity.php
│       │   ├── ScopeEntity.php
│       │   └── UserEntity.php
│       └── Repositories/
│           ├── AccessTokenRepository.php
│           ├── AuthCodeRepository.php
│           ├── ClientRepository.php
│           ├── DeviceCodeRepository.php
│           ├── RefreshTokenRepository.php
│           ├── ScopeRepository.php
│           └── UserRepository.php
├── phpcs.xml.dist
├── phpstan.neon.dist
├── phpunit.xml.dist
├── src/
│   ├── AuthorizationServer.php
│   ├── AuthorizationValidators/
│   │   ├── AuthorizationValidatorInterface.php
│   │   └── BearerTokenValidator.php
│   ├── CodeChallengeVerifiers/
│   │   ├── CodeChallengeVerifierInterface.php
│   │   ├── PlainVerifier.php
│   │   └── S256Verifier.php
│   ├── CryptKey.php
│   ├── CryptKeyInterface.php
│   ├── CryptTrait.php
│   ├── Entities/
│   │   ├── AccessTokenEntityInterface.php
│   │   ├── AuthCodeEntityInterface.php
│   │   ├── ClientEntityInterface.php
│   │   ├── DeviceCodeEntityInterface.php
│   │   ├── RefreshTokenEntityInterface.php
│   │   ├── ScopeEntityInterface.php
│   │   ├── TokenInterface.php
│   │   ├── Traits/
│   │   │   ├── AccessTokenTrait.php
│   │   │   ├── AuthCodeTrait.php
│   │   │   ├── ClientTrait.php
│   │   │   ├── DeviceCodeTrait.php
│   │   │   ├── EntityTrait.php
│   │   │   ├── RefreshTokenTrait.php
│   │   │   ├── ScopeTrait.php
│   │   │   └── TokenEntityTrait.php
│   │   └── UserEntityInterface.php
│   ├── EventEmitting/
│   │   ├── AbstractEvent.php
│   │   ├── EmitterAwareInterface.php
│   │   ├── EmitterAwarePolyfill.php
│   │   └── EventEmitter.php
│   ├── Exception/
│   │   ├── OAuthServerException.php
│   │   └── UniqueTokenIdentifierConstraintViolationException.php
│   ├── Grant/
│   │   ├── AbstractAuthorizeGrant.php
│   │   ├── AbstractGrant.php
│   │   ├── AuthCodeGrant.php
│   │   ├── ClientCredentialsGrant.php
│   │   ├── DeviceCodeGrant.php
│   │   ├── GrantTypeInterface.php
│   │   ├── ImplicitGrant.php
│   │   ├── PasswordGrant.php
│   │   └── RefreshTokenGrant.php
│   ├── Middleware/
│   │   ├── AuthorizationServerMiddleware.php
│   │   └── ResourceServerMiddleware.php
│   ├── RedirectUriValidators/
│   │   ├── RedirectUriValidator.php
│   │   └── RedirectUriValidatorInterface.php
│   ├── Repositories/
│   │   ├── AccessTokenRepositoryInterface.php
│   │   ├── AuthCodeRepositoryInterface.php
│   │   ├── ClientRepositoryInterface.php
│   │   ├── DeviceCodeRepositoryInterface.php
│   │   ├── RefreshTokenRepositoryInterface.php
│   │   ├── RepositoryInterface.php
│   │   ├── ScopeRepositoryInterface.php
│   │   └── UserRepositoryInterface.php
│   ├── RequestAccessTokenEvent.php
│   ├── RequestEvent.php
│   ├── RequestRefreshTokenEvent.php
│   ├── RequestTypes/
│   │   ├── AuthorizationRequest.php
│   │   └── AuthorizationRequestInterface.php
│   ├── ResourceServer.php
│   └── ResponseTypes/
│       ├── AbstractResponseType.php
│       ├── BearerTokenResponse.php
│       ├── DeviceCodeResponse.php
│       ├── RedirectResponse.php
│       └── ResponseTypeInterface.php
└── tests/
    ├── AuthorizationServerTest.php
    ├── AuthorizationValidators/
    │   └── BearerTokenValidatorTest.php
    ├── CodeChallengeVerifiers/
    │   ├── PlainVerifierTest.php
    │   └── S256VerifierTest.php
    ├── EventEmitting/
    │   └── EmitterAwarePolyfillTest.php
    ├── Exception/
    │   └── OAuthServerExceptionTest.php
    ├── Grant/
    │   ├── AbstractGrantTest.php
    │   ├── AuthCodeGrantTest.php
    │   ├── ClientCredentialsGrantTest.php
    │   ├── DeviceCodeGrantTest.php
    │   ├── ImplicitGrantTest.php
    │   ├── PasswordGrantTest.php
    │   └── RefreshTokenGrantTest.php
    ├── Middleware/
    │   ├── AuthorizationServerMiddlewareTest.php
    │   └── ResourceServerMiddlewareTest.php
    ├── PHPStan/
    │   └── AbstractGrantExtension.php
    ├── RedirectUriValidators/
    │   └── RedirectUriValidatorTest.php
    ├── ResourceServerTest.php
    ├── ResponseTypes/
    │   ├── BearerResponseTypeTest.php
    │   ├── BearerTokenResponseWithParams.php
    │   └── DeviceCodeResponseTypeTest.php
    ├── Stubs/
    │   ├── .gitattributes
    │   ├── AccessTokenEntity.php
    │   ├── AuthCodeEntity.php
    │   ├── ClientEntity.php
    │   ├── CryptTraitStub.php
    │   ├── DeviceCodeEntity.php
    │   ├── GrantType.php
    │   ├── RefreshTokenEntity.php
    │   ├── ScopeEntity.php
    │   ├── StubResponseType.php
    │   ├── UserEntity.php
    │   ├── private.key
    │   ├── private.key.crlf
    │   └── public.key
    └── Utils/
        ├── CryptKeyTest.php
        └── CryptTraitTest.php

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
* text=auto

/examples export-ignore
/tests export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/.scrutinizer.yml export-ignore
/.styleci.yml export-ignore
/phpstan.neon export-ignore
/phpunit.xml.dist export-ignore
/CHANGELOG.md export-ignore
/CONTRIBUTING.md export-ignore
/README.md export-ignore
/CODE_OF_CONDUCT.md export-ignore


================================================
FILE: .github/FUNDING.yml
================================================
github: [sephster]


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: composer
  directory: "/"
  schedule:
    interval: daily
    time: "11:00"
  open-pull-requests-limit: 10
  ignore:
  - dependency-name: league/event
    versions:
    - 3.0.0


================================================
FILE: .github/workflows/backwards-compatibility.yml
================================================
name: "Backwards compatibility check"

on:
  pull_request:

jobs:
  bc-check:
    name: "Backwards compatibility check"

    runs-on: "ubuntu-latest"

    steps:
      - name: "Checkout"
        uses: "actions/checkout@v4"
        with:
          fetch-depth: 0
      - name: Fix git safe.directory in container
        run: mkdir -p /home/runner/work/_temp/_github_home && printf "[safe]\n\tdirectory = /github/workspace" > /home/runner/work/_temp/_github_home/.gitconfig
      - name: "Backwards Compatibility Check"
        uses: docker://nyholm/roave-bc-check-ga
        with:
          args: --from=${{ github.event.pull_request.base.sha }}


================================================
FILE: .github/workflows/coding-standards.yml
================================================
name: Coding Standards

on:
  pull_request:
  push:

jobs:
  coding-standards:
    name: Coding Standards

    runs-on: ${{ matrix.operating-system }}

    strategy:
      matrix:
        php-version:
          - 8.5
        operating-system:
          - ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install PHP
        uses: shivammathur/setup-php@v2
        with:
          coverage: none
          php-version: ${{ matrix.php-version }}
          ini-values: memory_limit=-1
          tools: composer:v2, cs2pr

      - name: Install Dependencies
        run: composer update --prefer-stable --prefer-dist --no-interaction --no-progress

      - name: Run Codesniffer
        run: vendor/bin/phpcs


================================================
FILE: .github/workflows/static-analysis.yml
================================================
name: Static Analysis

on:
  push:
  pull_request:

jobs:
  static-analysis:
    name: Static Analysis

    runs-on: ${{ matrix.operating-system }}

    strategy:
      matrix:
        php-version: [8.2, 8.3, 8.4, 8.5]
        composer-stability: [prefer-lowest, prefer-stable]
        operating-system:
          - ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install PHP
        uses: shivammathur/setup-php@v2
        with:
          coverage: none
          php-version: ${{ matrix.php-version }}
          ini-values: memory_limit=-1
          tools: composer:v2, cs2pr

      - name: Install Dependencies
        run: composer update --${{ matrix.composer-stability }} --prefer-dist --no-interaction --no-progress

      - name: Run Static Analysis
        run: vendor/bin/phpstan analyse


================================================
FILE: .github/workflows/tests.yml
================================================
name: tests

on:
  push:
  pull_request:
  schedule:
    - cron: "0 0 * * *"

jobs:
  tests:
    strategy:
      fail-fast: false
      matrix:
        php: [8.2, 8.3, 8.4, 8.5]
        os: [ubuntu-latest, windows-latest]
        stability: [prefer-lowest, prefer-stable]

    runs-on: ${{ matrix.os }}

    name: PHP ${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: dom, curl, libxml, mbstring, sodium, zip
          coverage: pcov

      - name: Install dependencies
        run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress

      - name: Install Scrutinizer/Ocular
        run: composer global require scrutinizer/ocular

      - name: Execute tests
        run: vendor/bin/phpunit --coverage-clover=coverage.clover

      - name: Code coverage
        if: ${{ github.ref == 'refs/heads/master' && github.repository == 'thephpleague/oauth2-server' && startsWith(matrix.os, 'ubuntu') }}
        run: ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover


================================================
FILE: .gitignore
================================================
/vendor
/composer.lock
phpunit.xml
.phpunit.result.cache
.idea
/examples/vendor
examples/public.key
examples/private.key
build
*.orig


================================================
FILE: .scrutinizer.yml
================================================
build:
    environment:
        php:
            version: 8.3.3
    nodes:
        analysis:
            tests:
                override:
                    - php-scrutinizer-run
filter:
    excluded_paths:
        - tests/*
        - vendor/*
checks:
    php:
        code_rating: true
        remove_extra_empty_lines: true
        remove_php_closing_tag: true
        remove_trailing_whitespace: true
        fix_use_statements:
            remove_unused: true
            preserve_multiple: false
            preserve_blanklines: true
            order_alphabetically: true
        fix_php_opening_tag: true
        fix_linefeed: true
        fix_line_ending: true
        fix_identation_4spaces: true
        fix_doc_comments: true
tools:
    external_code_coverage:
        timeout: 1800
    php_code_coverage: false
    php_code_sniffer:
        config:
            standard: PSR2
        filter:
            paths: ['src']
    php_loc:
        enabled: true
        excluded_dirs: [vendor, tests, examples]
    php_cpd:
        enabled: true
        excluded_dirs: [vendor, tests, examples]


================================================
FILE: .styleci.yml
================================================
preset: psr12

risky: true

enabled:
  - blank_line_before_return
  - fully_qualified_strict_types
  - hash_to_slash_comment
  - include
  - method_separation
  - native_function_casing
  - no_duplicate_semicolons
  - no_multiline_whitespace_before_semicolons
  - no_php4_constructor
  - no_short_bool_cast
  - no_singleline_whitespace_before_semicolons
  - no_trailing_comma_in_singleline_array
  - no_unused_imports
  - no_whitespace_before_comma_in_array
  - ordered_imports
  - phpdoc_align
  - phpdoc_indent
  - phpdoc_inline_tag
  - phpdoc_no_access
  - phpdoc_no_simplified_null_return
  - phpdoc_property
  - phpdoc_scalar
  - phpdoc_separation
  - phpdoc_to_comment
  - phpdoc_trim
  - phpdoc_type_to_var
  - phpdoc_types
  - phpdoc_var_without_name
  - print_to_echo
  - short_array_syntax
  - single_quote
  - spaces_cast
  - standardize_not_equal
  - trailing_comma_in_multiline_array
  - trim_array_spaces
  - whitespace_after_comma_in_array


================================================
FILE: CHANGELOG.md
================================================
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Changed

- User ID is now passed to the finalizeScopes method for the Refresh Grant (PR #1414)

### Removed

- Removed support for PHP 8.1 (PR #1500)

## [9.3.0] - released 2025-11-25

### Added

- Added sensitive parameter to avoid sensitive data being included in stack traces (PR #1483)
- Support for PHP 8.5 (PR #1492)

### Fixed

- Made the Bearer header case insensitive to match the specs correctly (PR #1491)

## [9.2.0] - released 2025-02-15

### Added

- Added a new function to the provided ClientTrait, `supportsGrantType` to allow the auth server to issue the response `unauthorized_client` when applicable (PR #1420)

### Fixed

- Fix a bug on setting interval visibility of device authorization grant (PR #1410)
- Fix a bug where the new poll date were not persisted when `slow_down` error happens, because the exception is thrown before calling `persistDeviceCode`. (PR #1410)
- Fix a bug where `slow_down` error response may have been returned even after the user has completed the auth flow (already approved / denied the request). (PR #1410)
- Clients only validated for Refresh, Device Code, and Password grants if the client is confidential (PR #1420)
- Emit `RequestAccessTokenEvent` and `RequestRefreshTokenEvent` events instead of the general `RequestEvent` event when an access / refresh token is issued using device authorization grant. (PR #1467)

### Changed

- Key permission checks ignored on Windows regardless of userland choice as cannot be run successfully on this OS (PR #1447)

## [9.1.0] - released 2024-11-21

### Added

- Support for PHP 8.4 (PR #1454)

### Fixed

- In the Auth Code grant, when requesting an access token with an invalid auth code, we now respond with an invalid_grant error instead of invalid_request (PR #1433)
- Fixed spec compliance issue where device access token request was mistakenly expecting to receive scopes in the request (PR #1412)
- Refresh tokens pre version 9 might have had user IDs set as ints which meant they were incorrectly rejected. We now cast these values to strings to allow old refresh tokens (PR #1436)

## [9.0.1] - released 2024-10-14

### Fixed

- Auto-generated event emitter is now persisted. Previously, a new emitter was generated every time (PR #1428)
- Fixed bug where you could not omit a redirect uri even if one had not been specified during the auth request (PR #1428)
- Fixed bug where "state" parameter wasn't present on `invalid_scope` error response and wasn't on fragment part of `access_denied` redirect URI on Implicit grant (PR #1298)
- Fixed bug where disabling refresh token revocation via `revokeRefreshTokens(false)` unintentionally disables issuing new refresh token (PR #1449)

## [9.0.0] - released 2024-05-13

### Added

- Device Authorization Grant added (PR #1074)
- GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #1375)
- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044)
- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094)
- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110)
- Added function `getKeyContents()` to the `CryptKeyInterface` (PR #1375)

### Fixed

- Basic authorization is now case insensitive (PR #1403)
- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082)

### Changed

- All interfaces now specify types for all params and return values. Strict typing enforced (PR #1074)
- Request parameters are now parsed into strings to use internally in the library (PR #1402)
- Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111)
- Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112)
- AccessTokenEntityInterface now requires the implementation of `toString()` instead of the magic method `__toString()` (PR #1395)

### Removed

- Removed message property from OAuthException HTTP response. Now just use error_description as per the OAuth 2 spec (PR #1375)

## [9.0.0-RC1] - released 2024-03-27

### Added

- Device Authorization Grant added (PR #1074)
- GrantTypeInterface has a new function, `revokeRefreshTokens()` for enabling or disabling refresh tokens after use (PR #1375)
- A CryptKeyInterface to allow developers to change the CryptKey implementation with greater ease (PR #1044)
- The authorization server can now finalize scopes when a client uses a refresh token (PR #1094)
- An AuthorizationRequestInterface to make it easier to extend the AuthorizationRequest (PR #1110)
- Added function `getKeyContents()` to the `CryptKeyInterface` (PR #1375)

### Fixed

- If a refresh token has expired, been revoked, cannot be decrypted, or does not belong to the correct client, the server will now issue an `invalid_grant` error and a HTTP 400 response. In previous versions the server incorrectly issued an `invalid_request` and HTTP 401 response (PR #1042) (PR #1082)

### Changed

- Authorization Request objects are now created through the factory method, `createAuthorizationRequest()` (PR #1111)
- Changed parameters for `finalizeScopes()` to allow a reference to an auth code ID (PR #1112)
- AccessTokenEntityInterface now requires the implementation of `toString()` instead of the magic method `__toString()` (PR #1395)

### Removed

- Removed message property from OAuthException HTTP response. Now just use error_description as per the OAuth 2 spec (PR #1375)

## [8.5.4] - released 2023-08-25

### Added

- Support for league/uri ^7.0 (PR #1367)

## [8.5.3] - released 2023-07-06

### Security

- If a key string is provided to the CryptKey constructor with an invalid
  passphrase, the LogicException message generated will expose the given key.
  The key is no longer leaked via this exception (PR #1353)

## [8.5.2] - released 2023-06-16

### Changed

- Bumped the versions for laminas/diactoros and psr/http-message to support
  PSR-7 v2.0 (PR #1339)

## [8.5.1] - released 2023-04-04

### Fixed

- Fixed PHP version constraints and lcobucci/clock version constraint to support PHP 8.1 (PR #1336)

## [8.5.0] - released 2023-04-03

### Added

- Support for PHP 8.1 and 8.2 (PR #1333)

### Removed

- Support PHP 7.2, 7.3, and 7.4 (PR #1333)

## [8.4.1] - released 2023-03-22

### Fixed

- Fix deprecation notices for PHP 8.x (PR #1329)

## [8.4.0] - released 2023-02-15

### Added

- You can now set a leeway for time drift between servers when validating a JWT (PR #1304)

### Security

- Access token requests that contain a code_verifier but are not bound to a code_challenge will be rejected to prevent
  a PKCE downgrade attack (PR #1326)

## [8.3.6] - released 2022-11-14

### Fixed

- Use LooseValidAt instead of StrictValidAt so that users aren't forced to use claims such as NBF in their JWT tokens (PR #1312)

## [8.3.5] - released 2022-05-12

### Fixed

- Use InMemory::plainText('empty', 'empty') instead of InMemory::plainText('') to avoid [new empty string exception](https://github.com/lcobucci/jwt/pull/833) thrown by lcobucci/jwt (PR #1282)

## [8.3.4] - released 2022-04-07

### Fixed

- Server previously rejected valid uris with custom schemes. Now use league/uri for parsing to accept all valid uris (PR #1274)

## [8.3.3] - released 2021-10-11

### Security

- Removed the use of `LocalFileReference()` in lcobucci/jwt. Function deprecated as per [GHSA-7322-jrq4-x5hf](https://github.com/lcobucci/jwt/security/advisories/GHSA-7322-jrq4-x5hf) (PR #1249)

## [8.3.2] - released 2021-07-27

### Changed

- Conditionally support the `StrictValidAt()` method in lcobucci/jwt so we can use version 4.1.x or greater of the library (PR #1236)
- When providing invalid credentials, the library now responds with the error message _The user credentials were incorrect_ (PR #1230)
- Keys are always stored in memory now and are not written to a file in the /tmp directory (PR #1180)
- The regex for matching the bearer token has been simplified (PR #1238)

## [8.3.1] - released 2021-06-04

### Fixed

- Revert check on clientID. We will no longer require this to be a string (PR #1233)

## [8.3.0] - released 2021-06-03

### Added

- The server will now validate redirect uris according to rfc8252 (PR #1203)
- Events emitted now include the refresh token and access token payloads (PR #1211)
- Use the `revokeRefreshTokens()` function to decide whether refresh tokens are revoked or not upon use (PR #1189)

### Changed

- Keys are now validated using `openssl_pkey_get_private()` and `openssl_pkey_get_public()` instead of regex matching (PR #1215)

### Fixed

- The server will now only recognise and handle an authorization header if the value of the header is non-empty. This is to circumvent issues where some common frameworks set this header even if no value is present (PR #1170)
- Added type validation for redirect uri, client ID, client secret, scopes, auth code, state, username, and password inputs (PR #1210)
- Allow scope "0" to be used. Previously this was removed from a request because it failed an `empty()` check (PR #1181)

## [8.2.4] - released 2020-12-10

### Fixed

- Reverted the enforcement of at least one redirect_uri for a client. This change has instead been moved to version 9 (PR #1169)

## [8.2.3] - released 2020-12-02

### Added

- Re-added support for PHP 7.2 (PR #1165, #1167)

## [8.2.2] - released 2020-11-30

### Fixed

- Fix issue where the private key passphrase isn't correctly passed to JWT library (PR #1164)

## [8.2.1] - released 2020-11-26

### Fixed

- If you have a password on your private key, it is now passed correctly to the JWT configuration object. (PR #1159)

## [8.2.0] - released 2020-11-25

### Added

- Add a `getRedirectUri` function to the `OAuthServerException` class (PR #1123)
- Support for PHP 8.0 (PR #1146)

### Removed

- Removed support for PHP 7.2 (PR #1146)

### Fixed

- Fix typo in parameter hint. `code_challenged` changed to `code_challenge`. Thrown by Auth Code Grant when the code challenge does not match the regex. (PR #1130)
- Undefined offset was returned when no client redirect URI was set. Now throw an invalidClient exception if no redirect URI is set against a client (PR #1140)

## [8.1.1] - released 2020-07-01

### Fixed

- If you provide a valid redirect_uri with the auth code grant and an invalid scope, the server will use the given
  redirect_uri instead of the default client redirect uri (PR #1126)

## [8.1.0] - released 2020-04-29

### Added

- Added support for PHP 7.4 (PR #1075)

### Changed

- If an error is encountered when running `preg_match()` to validate an RSA key, the server will now throw a RuntimeException (PR #1047)
- Replaced deprecated methods with recommended ones when using `Lcobucci\JWT\Builder` to build a JWT token. (PR #1060)
- When storing a key, we no longer touch the file before writing it as this is an unnecessary step (PR #1064)
- Prefix native PHP functions in namespaces with backslashes for micro-optimisations (PR #1071)

### Removed

- Support for PHP 7.1 (PR #1075)

### Fixed

- Clients are now explicitly prevented from using the Client Credentials grant unless they are confidential to conform
  with the OAuth2 spec (PR #1035)
- Abstract method `getIdentifier()` added to AccessTokenTrait. The trait cannot be used without the `getIdentifier()`
  method being defined (PR #1051)
- An exception is now thrown if a refresh token is accidentally sent in place of an authorization code when using the
  Auth Code Grant (PR #1057)
- Can now send access token request without being forced to specify a redirect URI (PR #1096)
- In the BearerTokenValidator, if an implementation is using PDO, there is a possibility that a RuntimeException will be thrown when checking if an access token is revoked. This scenario no longer incorrectly issues an exception with a hint mentioning an issue with JSON decoding. (PR #1107)

## [8.0.0] - released 2019-07-13

### Added

- Flag, `requireCodeChallengeForPublicClients`, used to reject public clients that do not provide a code challenge for the Auth Code Grant; use AuthCodeGrant::disableRequireCodeCallengeForPublicClients() to turn off this requirement (PR #938)
- Public clients can now use the Auth Code Grant (PR #938)
- `isConfidential` getter added to `ClientEntity` to identify type of client (PR #938)
- Function `validateClient()` added to validate clients which was previously performed by the `getClientEntity()` function (PR #938)
- Add a new function to the AbstractGrant class called `getClientEntityOrFail()`. This is a wrapper around the `getClientEntity()` function that ensures we emit and throw an exception if the repo doesn't return a client entity. (PR #1010)

### Changed

- Replace `convertToJWT()` interface with a more generic `__toString()` to improve extensibility; AccessTokenEntityInterface now requires `setPrivateKey(CryptKey $privateKey)` so `__toString()` has everything it needs to work (PR #874)
- The `invalidClient()` function accepts a PSR-7 compliant `$serverRequest` argument to avoid accessing the `$_SERVER` global variable and improve testing (PR #899)
- `issueAccessToken()` in the Abstract Grant no longer sets access token client, user ID or scopes. These values should already have been set when calling `getNewToken()` (PR #919)
- No longer need to enable PKCE with `enableCodeExchangeProof` flag. Any client sending a code challenge will initiate PKCE checks. (PR #938)
- Function `getClientEntity()` no longer performs client validation (PR #938)
- Password Grant now returns an invalid_grant error instead of invalid_credentials if a user cannot be validated (PR #967)
- Use `DateTimeImmutable()` instead of `DateTime()`, `time()` instead of `(new DateTime())->getTimeStamp()`, and `DateTime::getTimeStamp()` instead of `DateTime::format('U')` (PR #963)

### Removed

- `enableCodeExchangeProof` flag (PR #938)
- Support for PHP 7.0 (PR #1014)
- Remove JTI claim from JWT header (PR #1031)

## [7.4.0] - released 2019-05-05

### Changed

- RefreshTokenRepository can now return null, allowing refresh tokens to be optional. (PR #649)

## [7.3.3] - released 2019-03-29

### Added

- Added `error_description` to the error payload to improve standards compliance. The contents of this are copied from the existing `message` value. (PR #1006)

### Deprecated

- Error payload will not issue `message` value in the next major release (PR #1006)

## [7.3.2] - released 2018-11-21

### Fixed

- Revert setting keys on response type to be inside `getResponseType()` function instead of AuthorizationServer constructor (PR #969)

## [7.3.1] - released 2018-11-15

### Fixed

- Fix issue with previous release where interface had changed for the AuthorizationServer. Reverted to the previous interface while maintaining functionality changes (PR #970)

## [7.3.0] - released 2018-11-13

### Changed

- Moved the `finalizeScopes()` call from `validateAuthorizationRequest` method to the `completeAuthorizationRequest` method so it is called just before the access token is issued (PR #923)

### Added

- Added a ScopeTrait to provide an implementation for jsonSerialize (PR #952)
- Ability to nest exceptions (PR #965)

### Fixed

- Fix issue where AuthorizationServer is not stateless as ResponseType could store state of a previous request (PR #960)

## [7.2.0] - released 2018-06-23

### Changed

- Added new`validateRedirectUri` method AbstractGrant to remove three instances of code duplication (PR #912)
- Allow 640 as a crypt key file permission (PR #917)

### Added

- Function `hasRedirect()` added to `OAuthServerException` (PR #703)

### Fixed

- Catch and handle `BadMethodCallException` from the `verify()` method of the JWT token in the `validateAuthorization` method (PR #904)

## [4.1.7] - released 2018-06-23

### Fixed

- Ensure `empty()` function call only contains variable to be compatible with PHP 5.4 (PR #918)

## [7.1.1] - released 2018-05-21

### Fixed

- No longer set a WWW-Authenticate header for invalid clients if the client did not send an Authorization header in the original request (PR #902)

## [7.1.0] - released 2018-04-22

### Changed

- Changed hint for unsupportedGrantType exception so it no longer references the grant type parameter which isn't always expected (PR #893)
- Upgrade PHPStan checks to level 7 (PR #856)

### Added

- Added event emitters for issued access and refresh tokens (PR #860)
- Can now use Defuse\Crypto\Key for encryption/decryption of keys which is faster than the Cryto class (PR #812)

### Removed

- Remove paragone/random_compat from dependencies

## [7.0.0] - released 2018-02-18

### Added

- Use PHPStan for static analysis of code (PR #848)
- Enforce stricter static analysis checks and upgrade library dependencies (PR #852)
- Provide PHPStan coverage for tests and update PHPUnit (PR #849)
- Get and set methods for OAuth Server Exception payloads. Allow implementer to specify the JSON encode options (PR #719)

### Changed

- ClientRepository interface will now accept null for the Grant type to improve extensibility options (PR #607)
- Do not issue an error if key file permissions are 400 or 440 (PR #839)
- Skip key file creation if the file already exists (PR #845)
- Change changelog format and update readme

### Removed

- Support for PHP 5.6
- Support for version 5.x and 6.x of the library

### Fixed

- PKCE implementation (PR #744)
- Set correct redirect URI when validating scopes (PR #840)
- S256 code challenege method (PR #842)
- Accept RSA key with CRLF line endings (PR #805)

## [6.1.1] - 2017-12-23

- Removed check on empty scopes

## [6.1.0] - 2017-12-23

- Changed the token type issued by the Implicit Grant to be Bearer instead of bearer. (PR #724)
- Replaced call to array_key_exists() with the faster isset() on the Implicit Grant. (PR #749)
- Allow specification of query delimiter character in the Password Grant (PR #801)
- Add Zend Diactoros library dependency to examples (PR #678)
- Can set default scope for the authorization endpoint. If no scope is passed during an authorization request, the default scope will be used if set. If not, the server will issue an invalid scope exception (PR #811)
- Added validation for redirect URIs on the authorization end point to ensure exactly one redirection URI has been passed (PR #573)

## [6.0.2] - 2017-08-03

- An invalid refresh token that can't be decrypted now returns a HTTP 401 error instead of HTTP 400 (Issue #759)
- Removed chmod from CryptKey and add toggle to disable checking (Issue #776)
- Fixes invalid code challenge method payload key name (Issue #777)

## [6.0.1] - 2017-07-19

To address feedback from the security release the following change has been made:

- If an RSA key cannot be chmod'ed to 600 then it will now throw a E_USER_NOTICE instead of an exception.

## [6.0.0] - 2017-07-01

- Breaking change: The `AuthorizationServer` constructor now expects an encryption key string instead of a public key
- Remove support for HHVM
- Remove support for PHP 5.5

## [5.1.4] - 2017-07-01

- Fixed multiple security vulnerabilities as a result of a security audit paid for by the [Mozilla Secure Open Source Fund](https://wiki.mozilla.org/MOSS/Secure_Open_Source). All users of this library are encouraged to update as soon as possible to this version or version 6.0 or greater.
- It is recommended on each `AuthorizationServer` instance you set the `setEncryptionKey()`. This will result in stronger encryption being used. If this method is not set messages will be sent to the defined error handling routines (using `error_log`). Please see the examples and documentation for examples.
- TravisCI now tests PHP 7.1 (Issue #671)
- Fix middleware example fatal error (Issue #682)
- Fix typo in the first README sentence (Issue #690)
- Corrected DateInterval from 1 min to 1 month (Issue #709)

## [5.1.3] - 2016-10-12

- Fixed WWW-Authenticate header (Issue #669)
- Increase the recommended RSA key length from 1024 to 2048 bits (Issue #668)

## [5.1.2] - 2016-09-19

- Fixed `finalizeScopes` call (Issue #650)

## [4.1.6] - 2016-09-13

- Less restrictive on Authorization header check (Issue #652)

## [5.1.1] - 2016-07-26

- Improved test suite (Issue #614)
- Updated docblocks (Issue #616)
- Replace `array_shift` with `foreach` loop (Issue #621)
- Allow easy addition of custom fields to Bearer token response (Issue #624)
- Key file auto-generation from string (Issue #625)

## [5.1.0] - 2016-06-28

- Implemented RFC7636 (Issue #574)
- Unify middleware exception responses (Issue #578)
- Updated examples (Issue #589)
- Ensure state is in access denied redirect (Issue #597)
- Remove redundant `isExpired()` method from entity interfaces and traits (Issue #600)
- Added a check for unique access token constraint violation (Issue #601)
- Look at Authorization header directly for HTTP Basic auth checks (Issue #604)
- Added catch Runtime exception when parsing JWT string (Issue #605)
- Allow `paragonie/random_compat` 2.x (Issue #606)
- Added `indigophp/hash-compat` to Composer suggestions and `require-dev` for PHP 5.5 support

## [5.0.3] - 2016-05-04

- Fix hints in PasswordGrant (Issue #560)
- Add meaning of `Resource owner` to terminology.md (Issue #561)
- Use constant for event name instead of explicit string (Issue #563)
- Remove unused request property (Issue #564)
- Correct wrong phpdoc (Issue #569)
- Fixed typo in exception string (Issue #570)

## [5.0.2] - 2016-04-18

- `state` parameter is now correctly returned after implicit grant authorization
- Small code and docblock improvements

## [5.0.1] - 2016-04-18

- Fixes an issue (#550) whereby it was unclear whether or not to validate a client's secret during a request.

## [5.0.0] - 2016-04-17

Version 5 is a complete code rewrite.

- Renamed Server class to AuthorizationServer
- Added ResourceServer class
- Run unit tests again PHP 5.5.9 as it's the minimum supported version
- Enable PHPUnit 5.0 support
- Improved examples and documentation
- Make it clearer that the implicit grant doesn't support refresh tokens
- Improved refresh token validation errors
- Fixed refresh token expiry date

## [5.0.0-RC2] - 2016-04-10

- Allow multiple client redirect URIs (Issue #511)
- Remove unused mac token interface (Issue #503)
- Handle RSA key passphrase (Issue #502)
- Remove access token repository from response types (Issue #501)
- Remove unnecessary methods from entity interfaces (Issue #490)
- Ensure incoming JWT hasn't expired (Issue #509)
- Fix client identifier passed where user identifier is expected (Issue #498)
- Removed built-in entities; added traits to for quick re-use (Issue #504)
- Redirect uri is required only if the "redirect_uri" parameter was included in the authorization request (Issue #514)
- Removed templating for auth code and implicit grants (Issue #499)

## [5.0.0-RC1] - 2016-03-24

Version 5 is a complete code rewrite.

- JWT support
- PSR-7 support
- Improved exception errors
- Replace all occurrences of the term "Storage" with "Repository"
- Simplify repositories
- Entities conform to interfaces and use traits
- Auth code grant updated
  - Allow support for public clients
  - Add support for #439
- Client credentials grant updated
- Password grant updated
  - Allow support for public clients
- Refresh token grant updated
- Implement Implicit grant
- Bearer token output type
- Remove MAC token output type
- Authorization server rewrite
- Resource server class moved to PSR-7 middleware
- Tests
- Much much better documentation

## [4.1.5] - 2016-01-04

- Enable Symfony 3.0 support (#412)

## [4.1.4] - 2015-11-13

- Fix for determining access token in header (Issue #328)
- Refresh tokens are now returned for MAC responses (Issue #356)
- Added integration list to readme (Issue #341)
- Expose parameter passed to exceptions (Issue #345)
- Removed duplicate routing setup code (Issue #346)
- Docs fix (Issues #347, #360, #380)
- Examples fix (Issues #348, #358)
- Fix typo in docblock (Issue #352)
- Improved timeouts for MAC tokens (Issue #364)
- `hash_hmac()` should output raw binary data, not hexits (Issue #370)
- Improved regex for matching all Base64 characters (Issue #371)
- Fix incorrect signature parameter (Issue #372)
- AuthCodeGrant and RefreshTokenGrant don't require client_secret (Issue #377)
- Added priority argument to event listener (Issue #388)

## [4.1.3] - 2015-03-22

- Docblock, namespace and inconsistency fixes (Issue #303)
- Docblock type fix (Issue #310)
- Example bug fix (Issue #300)
- Updated league/event to ~2.1 (Issue #311)
- Fixed missing session scope (Issue #319)
- Updated interface docs (Issue #323)
- `.travis.yml` updates

## [4.1.2] - 2015-01-01

- Remove side-effects in hash_equals() implementation (Issue #290)

## [4.1.1] - 2014-12-31

- Changed `symfony/http-foundation` dependency version to `~2.4` so package can be installed in Laravel `4.1.*`

## [4.1.0] - 2014-12-27

- Added MAC token support (Issue #158)
- Fixed example init code (Issue #280)
- Toggle refresh token rotation (Issue #286)
- Docblock fixes

## [4.0.5] - 2014-12-15

- Prevent duplicate session in auth code grant (Issue #282)

## [4.0.4] - 2014-12-03

- Ensure refresh token hasn't expired (Issue #270)

## [4.0.3] - 2014-12-02

- Fix bad type hintings (Issue #267)
- Do not forget to set the expire time (Issue #268)

## [4.0.2] - 2014-11-21

- Improved interfaces (Issue #255)
- Learnt how to spell delimiter and so `getScopeDelimiter()` and `setScopeDelimiter()` methods have been renamed
- Docblock improvements (Issue #254)

## [4.0.1] - 2014-11-09

- Alias the master branch in composer.json (Issue #243)
- Numerous PHP CodeSniffer fixes (Issue #244)
- .travis.yml update (Issue #245)
- The getAccessToken method should return an AccessTokenEntity object instead of a string in ResourceServer.php (#246)

## [4.0.0] - 2014-11-08

- Complete rewrite
- Check out the documentation - [http://oauth2.thephpleague.com](http://oauth2.thephpleague.com)

## [3.2.0] - 2014-04-16

- Added the ability to change the algorithm that is used to generate the token strings (Issue #151)

## [3.1.2] - 2014-02-26

- Support Authorization being an environment variable. [See more](http://fortrabbit.com/docs/essentials/quirks-and-constraints#authorization-header)

## [3.1.1] - 2013-12-05

- Normalize headers when `getallheaders()` is available (Issues #108 and #114)

## [3.1.0] - 2013-12-05

- No longer necessary to inject the authorisation server into a grant, the server will inject itself
- Added test for 1419ba8cdcf18dd034c8db9f7de86a2594b68605

## [3.0.1] - 2013-12-02

- Forgot to tell TravisCI from testing PHP 5.3

## [3.0.0] - 2013-12-02

- Fixed spelling of Implicit grant class (Issue #84)
- Travis CI now tests for PHP 5.5
- Fixes for checking headers for resource server (Issues #79 and #)
- The word "bearer" now has a capital "B" in JSON output to match OAuth 2.0 spec
- All grants no longer remove old sessions by default
- All grants now support custom access token TTL (Issue #92)
- All methods which didn't before return a value now return `$this` to support method chaining
- Removed the build in DB providers - these will be put in their own repos to remove baggage in the main repository
- Removed support for PHP 5.3 because this library now uses traits and will use other modern PHP features going forward
- Moved some grant related functions into a trait to reduce duplicate code

## [2.1.1] - 2013-06-02

- Added conditional `isValid()` flag to check for Authorization header only (thanks @alexmcroberts)
- Fixed semantic meaning of `requireScopeParam()` and `requireStateParam()` by changing their default value to true
- Updated some duff docblocks
- Corrected array key call in Resource.php (Issue #63)

## [2.1.0] - 2013-05-10

- Moved zetacomponents/database to "suggest" in composer.json. If you rely on this feature you now need to include " zetacomponents/database" into "require" key in your own composer.json. (Issue #51)
- New method in Refresh grant called `rotateRefreshTokens()`. Pass in `true` to issue a new refresh token each time an access token is refreshed. This parameter needs to be set to true in order to request reduced scopes with the new access token. (Issue #47)
- Rename `key` column in oauth_scopes table to `scope` as `key` is a reserved SQL word. (Issue #45)
- The `scope` parameter is no longer required by default as per the RFC. (Issue #43)
- You can now set multiple default scopes by passing an array into `setDefaultScope()`. (Issue #42)
- The password and client credentials grants now allow for multiple sessions per user. (Issue #32)
- Scopes associated to authorization codes are not held in their own table (Issue #44)
- Database schema updates.

## [2.0.5] - 2013-05-09

- Fixed `oauth_session_token_scopes` table primary key
- Removed `DEFAULT ''` that has slipped into some tables
- Fixed docblock for `SessionInterface::associateRefreshToken()`

## [2.0.4] - 2013-05-09

- Renamed primary key in oauth_client_endpoints table
- Adding missing column to oauth_session_authcodes

### Security

- A refresh token should be bound to a client ID

## [2.0.3] - 2013-05-08

- Fixed a link to code in composer.json

## [2.0.2] - 2013-05-08

- Updated README with wiki guides
- Removed `null` as default parameters in some methods in the storage interfaces
- Fixed license copyright

## [2.0.0] - 2013-05-08

**If you're upgrading from v1.0.8 there are lots of breaking changes**

- Rewrote the session storage interface from scratch so methods are more obvious
- Included a PDO driver which implements the storage interfaces so the library is more "get up and go"
- Further normalised the database structure so all sessions no longer contain infomation related to authorization grant (which may or may not be enabled)
- A session can have multiple associated access tokens
- Individual grants can have custom expire times for access tokens
- Authorization codes now have a TTL of 10 minutes by default (can be manually set)
- Refresh tokens now have a TTL of one week by default (can be manually set)
- The client credentials grant will no longer gives out refresh tokens as per the specification

## [1.0.8] - 2013-03-18

- Fixed check for required state parameter
- Fixed check that user's credentials are correct in Password grant

## [1.0.7] - 2013-03-04

- Added method `requireStateParam()`
- Added method `requireScopeParam()`

## [1.0.6] - 2013-02-22

- Added links to tutorials in the README
- Added missing `state` parameter request to the `checkAuthoriseParams()` method.

## [1.0.5] - 2013-02-21

- Fixed the SQL example for SessionInterface::getScopes()

## [1.0.3] - 2013-02-20

- Changed all instances of the "authentication server" to "authorization server"

## [1.0.2] - 2013-02-20

- Fixed MySQL create table order
- Fixed version number in composer.json

## [1.0.1] - 2013-02-19

- Updated AuthServer.php to use `self::getParam()`

## 1.0.0 - 2013-02-15

- First major release

[Unreleased]: https://github.com/thephpleague/oauth2-server/compare/9.3.0...HEAD
[9.3.0]: https://github.com/thephpleague/oauth2-server/compare/9.2.0...9.3.0
[9.2.0]: https://github.com/thephpleague/oauth2-server/compare/9.1.0...9.2.0
[9.1.0]: https://github.com/thephpleague/oauth2-server/compare/9.0.1...9.1.0
[9.0.1]: https://github.com/thephpleague/oauth2-server/compare/9.0.0...9.0.1
[9.0.0]: https://github.com/thephpleague/oauth2-server/compare/9.0.0-RC1...9.0.0
[9.0.0-RC1]: https://github.com/thephpleague/oauth2-server/compare/8.5.4...9.0.0-RC1
[8.5.4]: https://github.com/thephpleague/oauth2-server/compare/8.5.3...8.5.4
[8.5.3]: https://github.com/thephpleague/oauth2-server/compare/8.5.2...8.5.3
[8.5.2]: https://github.com/thephpleague/oauth2-server/compare/8.5.1...8.5.2
[8.5.1]: https://github.com/thephpleague/oauth2-server/compare/8.5.0...8.5.1
[8.5.0]: https://github.com/thephpleague/oauth2-server/compare/8.4.1...8.5.0
[8.4.1]: https://github.com/thephpleague/oauth2-server/compare/8.4.0...8.4.1
[8.4.0]: https://github.com/thephpleague/oauth2-server/compare/8.3.6...8.4.0
[8.3.6]: https://github.com/thephpleague/oauth2-server/compare/8.3.5...8.3.6
[8.3.5]: https://github.com/thephpleague/oauth2-server/compare/8.3.4...8.3.5
[8.3.4]: https://github.com/thephpleague/oauth2-server/compare/8.3.3...8.3.4
[8.3.3]: https://github.com/thephpleague/oauth2-server/compare/8.3.2...8.3.3
[8.3.2]: https://github.com/thephpleague/oauth2-server/compare/8.3.1...8.3.2
[8.3.1]: https://github.com/thephpleague/oauth2-server/compare/8.3.0...8.3.1
[8.3.0]: https://github.com/thephpleague/oauth2-server/compare/8.2.4...8.3.0
[8.2.4]: https://github.com/thephpleague/oauth2-server/compare/8.2.3...8.2.4
[8.2.3]: https://github.com/thephpleague/oauth2-server/compare/8.2.2...8.2.3
[8.2.2]: https://github.com/thephpleague/oauth2-server/compare/8.2.1...8.2.2
[8.2.1]: https://github.com/thephpleague/oauth2-server/compare/8.2.0...8.2.1
[8.2.0]: https://github.com/thephpleague/oauth2-server/compare/8.1.1...8.2.0
[8.1.1]: https://github.com/thephpleague/oauth2-server/compare/8.1.0...8.1.1
[8.1.0]: https://github.com/thephpleague/oauth2-server/compare/8.0.0...8.1.0
[8.0.0]: https://github.com/thephpleague/oauth2-server/compare/7.4.0...8.0.0
[7.4.0]: https://github.com/thephpleague/oauth2-server/compare/7.3.3...7.4.0
[7.3.3]: https://github.com/thephpleague/oauth2-server/compare/7.3.2...7.3.3
[7.3.2]: https://github.com/thephpleague/oauth2-server/compare/7.3.1...7.3.2
[7.3.1]: https://github.com/thephpleague/oauth2-server/compare/7.3.0...7.3.1
[7.3.0]: https://github.com/thephpleague/oauth2-server/compare/7.2.0...7.3.0
[7.2.0]: https://github.com/thephpleague/oauth2-server/compare/7.1.1...7.2.0
[7.1.1]: https://github.com/thephpleague/oauth2-server/compare/7.1.0...7.1.1
[7.1.0]: https://github.com/thephpleague/oauth2-server/compare/7.0.0...7.1.0
[7.0.0]: https://github.com/thephpleague/oauth2-server/compare/6.1.1...7.0.0
[6.1.1]: https://github.com/thephpleague/oauth2-server/compare/6.0.0...6.1.1
[6.1.0]: https://github.com/thephpleague/oauth2-server/compare/6.0.2...6.1.0
[6.0.2]: https://github.com/thephpleague/oauth2-server/compare/6.0.1...6.0.2
[6.0.1]: https://github.com/thephpleague/oauth2-server/compare/6.0.0...6.0.1
[6.0.0]: https://github.com/thephpleague/oauth2-server/compare/5.1.4...6.0.0
[5.1.4]: https://github.com/thephpleague/oauth2-server/compare/5.1.3...5.1.4
[5.1.3]: https://github.com/thephpleague/oauth2-server/compare/5.1.2...5.1.3
[5.1.2]: https://github.com/thephpleague/oauth2-server/compare/5.1.1...5.1.2
[5.1.1]: https://github.com/thephpleague/oauth2-server/compare/5.1.0...5.1.1
[5.1.0]: https://github.com/thephpleague/oauth2-server/compare/5.0.2...5.1.0
[5.0.3]: https://github.com/thephpleague/oauth2-server/compare/5.0.3...5.0.2
[5.0.2]: https://github.com/thephpleague/oauth2-server/compare/5.0.1...5.0.2
[5.0.1]: https://github.com/thephpleague/oauth2-server/compare/5.0.0...5.0.1
[5.0.0]: https://github.com/thephpleague/oauth2-server/compare/5.0.0-RC2...5.0.0
[5.0.0-RC2]: https://github.com/thephpleague/oauth2-server/compare/5.0.0-RC1...5.0.0-RC2
[5.0.0-RC1]: https://github.com/thephpleague/oauth2-server/compare/4.1.5...5.0.0-RC1
[4.1.7]: https://github.com/thephpleague/oauth2-server/compare/4.1.6...4.1.7
[4.1.6]: https://github.com/thephpleague/oauth2-server/compare/4.1.5...4.1.6
[4.1.5]: https://github.com/thephpleague/oauth2-server/compare/4.1.4...4.1.5
[4.1.4]: https://github.com/thephpleague/oauth2-server/compare/4.1.3...4.1.4
[4.1.3]: https://github.com/thephpleague/oauth2-server/compare/4.1.2...4.1.3
[4.1.2]: https://github.com/thephpleague/oauth2-server/compare/4.1.1...4.1.2
[4.1.1]: https://github.com/thephpleague/oauth2-server/compare/4.0.0...4.1.1
[4.1.0]: https://github.com/thephpleague/oauth2-server/compare/4.0.5...4.1.0
[4.0.5]: https://github.com/thephpleague/oauth2-server/compare/4.0.4...4.0.5
[4.0.4]: https://github.com/thephpleague/oauth2-server/compare/4.0.3...4.0.4
[4.0.3]: https://github.com/thephpleague/oauth2-server/compare/4.0.2...4.0.3
[4.0.2]: https://github.com/thephpleague/oauth2-server/compare/4.0.1...4.0.2
[4.0.1]: https://github.com/thephpleague/oauth2-server/compare/4.0.0...4.0.1
[4.0.0]: https://github.com/thephpleague/oauth2-server/compare/3.2.0...4.0.0
[3.2.0]: https://github.com/thephpleague/oauth2-server/compare/3.1.2...3.2.0
[3.1.2]: https://github.com/thephpleague/oauth2-server/compare/3.1.1...3.1.2
[3.1.1]: https://github.com/thephpleague/oauth2-server/compare/3.1.0...3.1.1
[3.1.0]: https://github.com/thephpleague/oauth2-server/compare/3.0.1...3.1.0
[3.0.1]: https://github.com/thephpleague/oauth2-server/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/thephpleague/oauth2-server/compare/2.1.1...3.0.0
[2.1.1]: https://github.com/thephpleague/oauth2-server/compare/2.1.0...2.1.1
[2.1.0]: https://github.com/thephpleague/oauth2-server/compare/2.0.5...2.1.0
[2.0.5]: https://github.com/thephpleague/oauth2-server/compare/2.0.4...2.0.5
[2.0.4]: https://github.com/thephpleague/oauth2-server/compare/2.0.3...2.0.4
[2.0.3]: https://github.com/thephpleague/oauth2-server/compare/2.0.2...2.0.3
[2.0.2]: https://github.com/thephpleague/oauth2-server/compare/2.0.0...2.0.2
[2.0.0]: https://github.com/thephpleague/oauth2-server/compare/1.0.8...2.0.0
[1.0.8]: https://github.com/thephpleague/oauth2-server/compare/1.0.7...1.0.8
[1.0.7]: https://github.com/thephpleague/oauth2-server/compare/1.0.6...1.0.7
[1.0.6]: https://github.com/thephpleague/oauth2-server/compare/1.0.5...1.0.6
[1.0.5]: https://github.com/thephpleague/oauth2-server/compare/1.0.3...1.0.5
[1.0.3]: https://github.com/thephpleague/oauth2-server/compare/1.0.2...1.0.3
[1.0.2]: https://github.com/thephpleague/oauth2-server/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/thephpleague/oauth2-server/compare/1.0.0...1.0.1


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance, race,
religion, or sexual identity and orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
  advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
  professional setting

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at andrew@noexceptions.io. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html

[homepage]: https://www.contributor-covenant.org


================================================
FILE: CONTRIBUTING.md
================================================
Thanks for contributing to this project.


**Please submit your pull request against the `master` branch only.**


Please ensure that you run `phpunit` from the project root after you've made any changes.

If you've added something new please create a new unit test, if you've changed something please update any unit tests as appropritate.

We're trying to ensure there is **100%** test code coverage (including testing PHP errors and exceptions) so please ensure any new/updated tests cover all of your changes.

Thank you,

@alexbilbie


================================================
FILE: LICENSE
================================================
MIT License

Copyright (C) Alex Bilbie

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
================================================
# PHP OAuth 2.0 Server

[![Latest Version](http://img.shields.io/packagist/v/league/oauth2-server.svg?style=flat-square)](https://github.com/thephpleague/oauth2-server/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md)
[![Build Status](https://github.com/thephpleague/oauth2-server/workflows/tests/badge.svg)](https://github.com/thephpleague/oauth2-server/actions)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/oauth2-server.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/oauth2-server)
[![Total Downloads](https://img.shields.io/packagist/dt/league/oauth2-server.svg?style=flat-square)](https://packagist.org/packages/league/oauth2-server)

`league/oauth2-server` is a standards compliant implementation of an [OAuth 2.0](https://tools.ietf.org/html/rfc6749) authorization server written in PHP which makes working with OAuth 2.0 trivial. You can easily configure an OAuth 2.0 server to protect your API with access tokens, or allow clients to request new access tokens and refresh them.

Out of the box it supports the following grants:

- Authorization code grant
- Client credentials grant
- Device authorization grant
- Implicit grant
- Refresh grant
- Resource owner password credentials grant

The following RFCs are implemented:

- [RFC6749 "OAuth 2.0"](https://tools.ietf.org/html/rfc6749)
- [RFC6750 "The OAuth 2.0 Authorization Framework: Bearer Token Usage"](https://tools.ietf.org/html/rfc6750)
- [RFC7519 "JSON Web Token (JWT)"](https://tools.ietf.org/html/rfc7519)
- [RFC7636 "Proof Key for Code Exchange by OAuth Public Clients"](https://tools.ietf.org/html/rfc7636)
- [RFC8628 "OAuth 2.0 Device Authorization Grant](https://tools.ietf.org/html/rfc8628)

This library was created by Alex Bilbie. Find him on Twitter at [@alexbilbie](https://twitter.com/alexbilbie).

## Requirements

The latest version of this package supports the following versions of PHP:

- PHP 8.2
- PHP 8.3
- PHP 8.4
- PHP 8.5

The `openssl` and `json` extensions are also required.

All HTTP messages passed to the server should be [PSR-7 compliant](https://www.php-fig.org/psr/psr-7/). This ensures interoperability with other packages and frameworks.

## Installation

```
composer require league/oauth2-server
```

## Documentation

The library documentation can be found at [https://oauth2.thephpleague.com](https://oauth2.thephpleague.com).
You can contribute to the documentation in the [gh-pages branch](https://github.com/thephpleague/oauth2-server/tree/gh-pages/).

## Testing

The library uses [PHPUnit](https://phpunit.de/) for unit tests.

```
vendor/bin/phpunit
```

## Continuous Integration

We use [Github Actions](https://github.com/features/actions), [Scrutinizer](https://scrutinizer-ci.com/), and [StyleCI](https://styleci.io/) for continuous integration. Check out [our](https://github.com/thephpleague/oauth2-server/blob/master/.github/workflows/tests.yml) [configuration](https://github.com/thephpleague/oauth2-server/blob/master/.scrutinizer.yml) [files](https://github.com/thephpleague/oauth2-server/blob/master/.styleci.yml) if you'd like to know more.

## Community Integrations

- [Drupal](https://www.drupal.org/project/simple_oauth)
- [Laravel Passport](https://github.com/laravel/passport)
- [OAuth 2 Server for CakePHP 3](https://github.com/uafrica/oauth-server)
- [OAuth 2 Server for Mezzio](https://github.com/mezzio/mezzio-authentication-oauth2)
- [OAuth 2 Server Bundle (Symfony)](https://github.com/thephpleague/oauth2-server-bundle)
- [Heimdall for CodeIgniter 4](https://github.com/ezralazuardy/heimdall)

## Changelog

See the [project changelog](https://github.com/thephpleague/oauth2-server/blob/master/CHANGELOG.md)

## Contributing

Contributions are always welcome. Please see [CONTRIBUTING.md](https://github.com/thephpleague/oauth2-server/blob/master/CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](https://github.com/thephpleague/oauth2-server/blob/master/CODE_OF_CONDUCT.md) for details.

## Support

Bugs and feature request are tracked on [GitHub](https://github.com/thephpleague/oauth2-server/issues).

If you have any questions about OAuth _please_ open a ticket here; please **don't** email the address below.

## Security

If you discover any security related issues, please email `andrew@noexceptions.io` instead of using the issue tracker.

## License

This package is released under the MIT License. See the bundled [LICENSE](https://github.com/thephpleague/oauth2-server/blob/master/LICENSE) file for details.

## Credits

This code is principally developed and maintained by [Andy Millington](https://twitter.com/Sephster).

Between 2012 and 2017 this library was developed and maintained by [Alex Bilbie](https://alexbilbie.com/).

PHP OAuth 2.0 Server is one of many packages provided by The PHP League. To find out more, please visit [our website](https://thephpleague.com).

Special thanks to [all of these awesome contributors](https://github.com/thephpleague/oauth2-server/contributors).

Additional thanks go to the [Mozilla Secure Open Source Fund](https://wiki.mozilla.org/MOSS/Secure_Open_Source) for funding a security audit of this library.

The initial code was developed as part of the [Linkey](http://linkey.blogs.lincoln.ac.uk) project which was funded by [JISC](http://jisc.ac.uk) under the Access and Identity Management programme.


================================================
FILE: composer.json
================================================
{
  "name": "league/oauth2-server",
  "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.",
  "homepage": "https://oauth2.thephpleague.com/",
  "license": "MIT",
  "require": {
    "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
    "ext-openssl": "*",
    "league/event": "^3.0",
    "league/uri": "^7.8",
    "lcobucci/jwt": "^5.6",
    "psr/http-message": "^2.0",
    "defuse/php-encryption": "^2.4",
    "ext-json": "*",
    "lcobucci/clock": "^3.3",
    "psr/http-server-middleware": "^1.0"
  },
  "require-dev": {
    "phpunit/phpunit": "^11.5.50",
    "laminas/laminas-diactoros": "^3.8",
    "phpstan/phpstan": "^2.1.38",
    "phpstan/phpstan-phpunit": "^2.0.12",
    "roave/security-advisories": "dev-master",
    "phpstan/extension-installer": "^1.4.3",
    "phpstan/phpstan-deprecation-rules": "^2.0",
    "phpstan/phpstan-strict-rules": "^2.0",
    "slevomat/coding-standard": "^8.27.1",
    "php-parallel-lint/php-parallel-lint": "^1.4",
    "squizlabs/php_codesniffer": "^4.0",
    "paragonie/random_compat": "^9.99.100"
  },
  "repositories": [
    {
      "type": "git",
      "url": "https://github.com/thephpleague/oauth2-server.git"
    }
  ],
  "keywords": [
    "oauth",
    "oauth2",
    "oauth 2",
    "oauth 2.0",
    "server",
    "auth",
    "authorization",
    "authorisation",
    "authentication",
    "resource",
    "api",
    "protect",
    "secure"
  ],
  "authors": [
    {
      "name": "Alex Bilbie",
      "email": "hello@alexbilbie.com",
      "homepage": "http://www.alexbilbie.com",
      "role": "Developer"
    },
    {
      "name": "Andy Millington",
      "email": "andrew@noexceptions.io",
      "homepage": "https://www.noexceptions.io",
      "role": "Developer"
    }
  ],
  "replace": {
    "lncd/oauth2": "*",
    "league/oauth2server": "*"
  },
  "autoload": {
    "psr-4": {
      "League\\OAuth2\\Server\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "LeagueTests\\": "tests/"
    }
  },
  "config": {
    "allow-plugins": {
      "ocramius/package-versions": true,
      "phpstan/extension-installer": true,
      "dealerdirect/phpcodesniffer-composer-installer": false
    }
  }
}


================================================
FILE: examples/README.md
================================================
# Example implementations (via [`Slim 3`](https://github.com/slimphp/Slim/tree/3.x))

## Installation

0. Run `composer install` in this directory to install dependencies
0. Create a private key `openssl genrsa -out private.key 2048`
0. Export the public key `openssl rsa -in private.key -pubout > public.key`
0. Start local PHP server `php -S 127.0.0.1:4444 -t public/`

## Testing the client credentials grant example

Send the following cURL request:

```
curl -X "POST" "http://localhost:4444/client_credentials.php/access_token" \
	-H "Content-Type: application/x-www-form-urlencoded" \
	-H "Accept: 1.0" \
	--data-urlencode "grant_type=client_credentials" \
	--data-urlencode "client_id=myawesomeapp" \
	--data-urlencode "client_secret=abc123" \
	--data-urlencode "scope=basic email"
```

## Testing the password grant example

Send the following cURL request:

```
curl -X "POST" "http://localhost:4444/password.php/access_token" \
	-H "Content-Type: application/x-www-form-urlencoded" \
	-H "Accept: 1.0" \
	--data-urlencode "grant_type=password" \
	--data-urlencode "client_id=myawesomeapp" \
	--data-urlencode "client_secret=abc123" \
	--data-urlencode "username=alex" \
	--data-urlencode "password=whisky" \
	--data-urlencode "scope=basic email"
```

## Testing the refresh token grant example

Send the following cURL request. Replace `{{REFRESH_TOKEN}}` with a refresh token from another grant above:

```
curl -X "POST" "http://localhost:4444/refresh_token.php/access_token" \
	-H "Content-Type: application/x-www-form-urlencoded" \
	-H "Accept: 1.0" \
	--data-urlencode "grant_type=refresh_token" \
	--data-urlencode "client_id=myawesomeapp" \
	--data-urlencode "client_secret=abc123" \
	--data-urlencode "refresh_token={{REFRESH_TOKEN}}"
```

## Testing the device authorization grant example

Send the following cURL request. This will return a device code which can be exchanged for an access token.

```
curl -X "POST" "http://localhost:4444/device_code.php/device_authorization" \
	-H "Content-Type: application/x-www-form-urlencoded" \
	-H "Accept: 1.0" \
	--data-urlencode "client_id=myawesomeapp" \
	--data-urlencode "client_secret=abc123" \
	--data-urlencode "scope=basic email"
```

We have set up the example so that a user ID is already associated with the device code. In a production application you
would implement an authorization view to allow a user to authorize the device.

Issue the following cURL request to exchange your device code for an access token. Replace `{{DEVICE_CODE}}` with the
device code returned from your first cURL post:

```
curl -X "POST" "http://localhost:4444/device_code.php/access_token" \
	-H "Content-Type: application/x-www-form-urlencoded" \
	-H "Accept: 1.0" \
	--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
	--data-urlencode "device_code={{DEVICE_CODE}}" \
	--data-urlencode "client_id=myawesomeapp" \
	--data-urlencode "client_secret=abc123"
```


================================================
FILE: examples/composer.json
================================================
{
    "require": {
        "slim/slim": "^3.12.3"
    },
    "require-dev": {
        "league/event": "^3.0",
        "lcobucci/jwt": "^3.4.6 || ^4.0.4",
        "psr/http-message": "^1.0.1",
        "defuse/php-encryption": "^2.2.1",
        "laminas/laminas-diactoros": "^2.5.0"
    },
    "autoload": {
        "psr-4": {
            "OAuth2ServerExamples\\": "src/",
            "League\\OAuth2\\Server\\": "../src/"
        }
    }
}


================================================
FILE: examples/public/api.php
================================================
<?php

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
use League\OAuth2\Server\ResourceServer;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    // Add the resource server to the DI container
    ResourceServer::class => function () {
        $server = new ResourceServer(
            new AccessTokenRepository(),            // instance of AccessTokenRepositoryInterface
            'file://' . __DIR__ . '/../public.key'  // the authorization server's public key
        );

        return $server;
    },
]);

// Add the resource server middleware which will intercept and validate requests
$app->add(
    new ResourceServerMiddleware(
        $app->getContainer()->get(ResourceServer::class)
    )
);

// An example endpoint secured with OAuth 2.0
$app->get(
    '/users',
    function (ServerRequestInterface $request, ResponseInterface $response) {
        $users = [
            [
                'id'    => 123,
                'name'  => 'Alex',
                'email' => 'alex@thephpleague.com',
            ],
            [
                'id'    => 124,
                'name'  => 'Frank',
                'email' => 'frank@thephpleague.com',
            ],
            [
                'id'    => 125,
                'name'  => 'Phil',
                'email' => 'phil@thephpleague.com',
            ],
        ];

        $totalUsers = count($users);

        // If the access token doesn't have the `basic` scope hide users' names
        if (in_array('basic', $request->getAttribute('oauth_scopes')) === false) {
            for ($i = 0; $i < $totalUsers; $i++) {
                unset($users[$i]['name']);
            }
        }

        // If the access token doesn't have the `email` scope hide users' email addresses
        if (in_array('email', $request->getAttribute('oauth_scopes')) === false) {
            for ($i = 0; $i < $totalUsers; $i++) {
                unset($users[$i]['email']);
            }
        }

        $response->getBody()->write(json_encode($users));

        return $response->withStatus(200);
    }
);

$app->run();


================================================
FILE: examples/public/auth_code.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use Laminas\Diactoros\Stream;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AuthCodeGrant;
use OAuth2ServerExamples\Entities\UserEntity;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\AuthCodeRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
    AuthorizationServer::class => function () {
        // Init our repositories
        $clientRepository = new ClientRepository();
        $scopeRepository = new ScopeRepository();
        $accessTokenRepository = new AccessTokenRepository();
        $authCodeRepository = new AuthCodeRepository();
        $refreshTokenRepository = new RefreshTokenRepository();

        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';

        // Setup the authorization server
        $server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKeyPath,
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
        );

        // Enable the authentication code grant on the server with a token TTL of 1 hour
        $server->enableGrantType(
            new AuthCodeGrant(
                $authCodeRepository,
                $refreshTokenRepository,
                new DateInterval('PT10M')
            ),
            new DateInterval('PT1H')
        );

        return $server;
    },
]);

$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        // Validate the HTTP request and return an AuthorizationRequest object.
        // The auth request object can be serialized into a user's session
        $authRequest = $server->validateAuthorizationRequest($request);

        // Once the user has logged in set the user on the AuthorizationRequest
        $authRequest->setUser(new UserEntity());

        // Once the user has approved or denied the client update the status
        // (true = approved, false = denied)
        $authRequest->setAuthorizationApproved(true);

        // Return the HTTP redirect response
        return $server->completeAuthorizationRequest($authRequest, $response);
    } catch (OAuthServerException $exception) {
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        $body = new Stream('php://temp', 'r+');
        $body->write($exception->getMessage());

        return $response->withStatus(500)->withBody($body);
    }
});

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        return $server->respondToAccessTokenRequest($request, $response);
    } catch (OAuthServerException $exception) {
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        $body = new Stream('php://temp', 'r+');
        $body->write($exception->getMessage());

        return $response->withStatus(500)->withBody($body);
    }
});

$app->run();


================================================
FILE: examples/public/client_credentials.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use Laminas\Diactoros\Stream;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\ClientCredentialsGrant;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
    AuthorizationServer::class => function () {
        // Init our repositories
        $clientRepository = new ClientRepository(); // instance of ClientRepositoryInterface
        $scopeRepository = new ScopeRepository(); // instance of ScopeRepositoryInterface
        $accessTokenRepository = new AccessTokenRepository(); // instance of AccessTokenRepositoryInterface

        // Path to public and private keys
        $privateKey = 'file://' . __DIR__ . '/../private.key';
        //$privateKey = new CryptKey('file://path/to/private.key', 'passphrase'); // if private key has a pass phrase

        // Setup the authorization server
        $server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKey,
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
        );

        // Enable the client credentials grant on the server
        $server->enableGrantType(
            new ClientCredentialsGrant(),
            new DateInterval('PT1H') // access tokens will expire after 1 hour
        );

        return $server;
    },
]);

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        // Try to respond to the request
        return $server->respondToAccessTokenRequest($request, $response);
    } catch (OAuthServerException $exception) {
        // All instances of OAuthServerException can be formatted into a HTTP response
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        // Unknown exception
        $body = new Stream('php://temp', 'r+');
        $body->write($exception->getMessage());

        return $response->withStatus(500)->withBody($body);
    }
});

$app->run();


================================================
FILE: examples/public/device_code.php
================================================
<?php

/**
 * @author    Andrew Millington <andrew@noexceptions.io>
 * @copyright Copyright (c) Alex Bilbie
 * @license   http://mit-license.org/
 *
 * @link      https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use Laminas\Diactoros\Stream;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\DeviceCodeGrant;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\DeviceCodeRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
    AuthorizationServer::class => function () {
        // Init our repositories
        $clientRepository = new ClientRepository();
        $scopeRepository = new ScopeRepository();
        $accessTokenRepository = new AccessTokenRepository();
        $refreshTokenRepository = new RefreshTokenRepository();
        $deviceCodeRepository = new DeviceCodeRepository();

        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';

        // Set up the authorization server
        $server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKeyPath,
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
        );

        // Enable the device code grant on the server with a token TTL of 1 hour
        $server->enableGrantType(
            new DeviceCodeGrant(
                $deviceCodeRepository,
                $refreshTokenRepository,
                new DateInterval('PT10M'),
                'http://foo/bar'
            ),
            new DateInterval('PT1H')
        );

        return $server;
    },
]);

$app->post('/device_authorization', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        $deviceCodeResponse = $server->respondToDeviceAuthorizationRequest($request, $response);

        return $deviceCodeResponse;

        // Extract the device code. Usually we would then assign the user ID to
        // the device code but for the purposes of this example, we've hard
        // coded it in the response above.
        // $deviceCode = json_decode((string) $deviceCodeResponse->getBody());

        // Once the user has logged in and approved the request, set the user on the device code
        // $server->completeDeviceAuthorizationRequest($deviceCode->user_code, 1);
    } catch (OAuthServerException $exception) {
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        $body = new Stream('php://temp', 'r+');
        $body->write($exception->getMessage());

        return $response->withStatus(500)->withBody($body);
    }
});

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        return $server->respondToAccessTokenRequest($request, $response);
    } catch (OAuthServerException $exception) {
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        $body = new Stream('php://temp', 'r+');
        $body->write($exception->getMessage());

        return $response->withStatus(500)->withBody($body);
    }
});

$app->run();


================================================
FILE: examples/public/implicit.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use Laminas\Diactoros\Stream;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\ImplicitGrant;
use OAuth2ServerExamples\Entities\UserEntity;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
    AuthorizationServer::class => function () {
        // Init our repositories
        $clientRepository = new ClientRepository();
        $scopeRepository = new ScopeRepository();
        $accessTokenRepository = new AccessTokenRepository();

        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';

        // Setup the authorization server
        $server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKeyPath,
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
        );

        // Enable the implicit grant on the server with a token TTL of 1 hour
        $server->enableGrantType(new ImplicitGrant(new DateInterval('PT1H')));

        return $server;
    },
]);

$app->get('/authorize', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        // Validate the HTTP request and return an AuthorizationRequest object.
        // The auth request object can be serialized into a user's session
        $authRequest = $server->validateAuthorizationRequest($request);

        // Once the user has logged in set the user on the AuthorizationRequest
        $authRequest->setUser(new UserEntity());

        // Once the user has approved or denied the client update the status
        // (true = approved, false = denied)
        $authRequest->setAuthorizationApproved(true);

        // Return the HTTP redirect response
        return $server->completeAuthorizationRequest($authRequest, $response);
    } catch (OAuthServerException $exception) {
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        $body = new Stream('php://temp', 'r+');
        $body->write($exception->getMessage());

        return $response->withStatus(500)->withBody($body);
    }
});

$app->run();


================================================
FILE: examples/public/middleware_use.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use Laminas\Diactoros\Stream;
use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Grant\AuthCodeGrant;
use League\OAuth2\Server\Grant\RefreshTokenGrant;
use League\OAuth2\Server\Middleware\AuthorizationServerMiddleware;
use League\OAuth2\Server\Middleware\ResourceServerMiddleware;
use League\OAuth2\Server\ResourceServer;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\AuthCodeRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
    AuthorizationServer::class => function () {
        // Init our repositories
        $clientRepository = new ClientRepository();
        $accessTokenRepository = new AccessTokenRepository();
        $scopeRepository = new ScopeRepository();
        $authCodeRepository = new AuthCodeRepository();
        $refreshTokenRepository = new RefreshTokenRepository();

        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';

        // Setup the authorization server
        $server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKeyPath,
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
        );

        // Enable the authentication code grant on the server with a token TTL of 1 hour
        $server->enableGrantType(
            new AuthCodeGrant(
                $authCodeRepository,
                $refreshTokenRepository,
                new DateInterval('PT10M')
            ),
            new DateInterval('PT1H')
        );

        // Enable the refresh token grant on the server with a token TTL of 1 month
        $server->enableGrantType(
            new RefreshTokenGrant($refreshTokenRepository),
            new DateInterval('P1M')
        );

        return $server;
    },
    ResourceServer::class => function () {
        $publicKeyPath = 'file://' . __DIR__ . '/../public.key';

        $server = new ResourceServer(
            new AccessTokenRepository(),
            $publicKeyPath
        );

        return $server;
    },
]);

// Access token issuer
$app->post('/access_token', function (): void {
})->add(new AuthorizationServerMiddleware($app->getContainer()->get(AuthorizationServer::class)));

// Secured API
$app->group('/api', function (): void {
    $app->get('/user', function (ServerRequestInterface $request, ResponseInterface $response) {
        $params = [];

        if (in_array('basic', $request->getAttribute('oauth_scopes', []))) {
            $params = [
                'id'   => 1,
                'name' => 'Alex',
                'city' => 'London',
            ];
        }

        if (in_array('email', $request->getAttribute('oauth_scopes', []))) {
            $params['email'] = 'alex@example.com';
        }

        $body = new Stream('php://temp', 'r+');
        $body->write(json_encode($params));

        return $response->withBody($body);
    });
})->add(new ResourceServerMiddleware($app->getContainer()->get(ResourceServer::class)));

$app->run();


================================================
FILE: examples/public/password.php
================================================
<?php

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\PasswordGrant;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use OAuth2ServerExamples\Repositories\UserRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    // Add the authorization server to the DI container
    AuthorizationServer::class => function () {
        // Setup the authorization server
        $server = new AuthorizationServer(
            new ClientRepository(),                 // instance of ClientRepositoryInterface
            new AccessTokenRepository(),            // instance of AccessTokenRepositoryInterface
            new ScopeRepository(),                  // instance of ScopeRepositoryInterface
            'file://' . __DIR__ . '/../private.key',    // path to private key
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'      // encryption key
        );

        $grant = new PasswordGrant(
            new UserRepository(),           // instance of UserRepositoryInterface
            new RefreshTokenRepository()    // instance of RefreshTokenRepositoryInterface
        );
        $grant->setRefreshTokenTTL(new DateInterval('P1M')); // refresh tokens will expire after 1 month

        // Enable the password grant on the server with a token TTL of 1 hour
        $server->enableGrantType(
            $grant,
            new DateInterval('PT1H') // access tokens will expire after 1 hour
        );

        return $server;
    },
]);

$app->post(
    '/access_token',
    function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
        /* @var \League\OAuth2\Server\AuthorizationServer $server */
        $server = $app->getContainer()->get(AuthorizationServer::class);

        try {
            // Try to respond to the access token request
            return $server->respondToAccessTokenRequest($request, $response);
        } catch (OAuthServerException $exception) {
            // All instances of OAuthServerException can be converted to a PSR-7 response
            return $exception->generateHttpResponse($response);
        } catch (Exception $exception) {
            // Catch unexpected exceptions
            $body = $response->getBody();
            $body->write($exception->getMessage());

            return $response->withStatus(500)->withBody($body);
        }
    }
);

$app->run();


================================================
FILE: examples/public/refresh_token.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

include __DIR__ . '/../vendor/autoload.php';

use League\OAuth2\Server\AuthorizationServer;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\RefreshTokenGrant;
use OAuth2ServerExamples\Repositories\AccessTokenRepository;
use OAuth2ServerExamples\Repositories\ClientRepository;
use OAuth2ServerExamples\Repositories\RefreshTokenRepository;
use OAuth2ServerExamples\Repositories\ScopeRepository;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\App;

$app = new App([
    'settings' => [
        'displayErrorDetails' => true,
    ],
    AuthorizationServer::class => function () {
        // Init our repositories
        $clientRepository = new ClientRepository();
        $accessTokenRepository = new AccessTokenRepository();
        $scopeRepository = new ScopeRepository();
        $refreshTokenRepository = new RefreshTokenRepository();

        $privateKeyPath = 'file://' . __DIR__ . '/../private.key';

        // Setup the authorization server
        $server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKeyPath,
            'lxZFUEsBCJ2Yb14IF2ygAHI5N4+ZAUXXaSeeJm6+twsUmIen'
        );

        // Enable the refresh token grant on the server
        $grant = new RefreshTokenGrant($refreshTokenRepository);
        $grant->setRefreshTokenTTL(new DateInterval('P1M')); // The refresh token will expire in 1 month

        $server->enableGrantType(
            $grant,
            new DateInterval('PT1H') // The new access token will expire after 1 hour
        );

        return $server;
    },
]);

$app->post('/access_token', function (ServerRequestInterface $request, ResponseInterface $response) use ($app) {
    /* @var \League\OAuth2\Server\AuthorizationServer $server */
    $server = $app->getContainer()->get(AuthorizationServer::class);

    try {
        return $server->respondToAccessTokenRequest($request, $response);
    } catch (OAuthServerException $exception) {
        return $exception->generateHttpResponse($response);
    } catch (Exception $exception) {
        $response->getBody()->write($exception->getMessage());

        return $response->withStatus(500);
    }
});

$app->run();


================================================
FILE: examples/src/Entities/AccessTokenEntity.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\Traits\AccessTokenTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;

class AccessTokenEntity implements AccessTokenEntityInterface
{
    use AccessTokenTrait;
    use TokenEntityTrait;
    use EntityTrait;
}


================================================
FILE: examples/src/Entities/AuthCodeEntity.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\Traits\AuthCodeTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;

class AuthCodeEntity implements AuthCodeEntityInterface
{
    use EntityTrait;
    use TokenEntityTrait;
    use AuthCodeTrait;
}


================================================
FILE: examples/src/Entities/ClientEntity.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\Traits\ClientTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;

class ClientEntity implements ClientEntityInterface
{
    use EntityTrait;
    use ClientTrait;

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function setRedirectUri(string $uri): void
    {
        $this->redirectUri = $uri;
    }

    public function setConfidential(): void
    {
        $this->isConfidential = true;
    }
}


================================================
FILE: examples/src/Entities/DeviceCodeEntity.php
================================================
<?php

/**
 * @author Andrew Millington <andrew@noexceptions.io>
 * @copyright Copyright (c) Alex Bilbie
 * @license http://mit-license.org/
 *
 * @link https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\DeviceCodeEntityInterface;
use League\OAuth2\Server\Entities\Traits\DeviceCodeTrait;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\TokenEntityTrait;

class DeviceCodeEntity implements DeviceCodeEntityInterface
{
    use EntityTrait;
    use DeviceCodeTrait;
    use TokenEntityTrait;
}


================================================
FILE: examples/src/Entities/RefreshTokenEntity.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\RefreshTokenTrait;

class RefreshTokenEntity implements RefreshTokenEntityInterface
{
    use RefreshTokenTrait;
    use EntityTrait;
}


================================================
FILE: examples/src/Entities/ScopeEntity.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Entities\Traits\EntityTrait;
use League\OAuth2\Server\Entities\Traits\ScopeTrait;

class ScopeEntity implements ScopeEntityInterface
{
    use EntityTrait;
    use ScopeTrait;
}


================================================
FILE: examples/src/Entities/UserEntity.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Entities;

use League\OAuth2\Server\Entities\UserEntityInterface;

class UserEntity implements UserEntityInterface
{
    /**
     * Return the user's identifier.
     */
    public function getIdentifier(): string
    {
        return '1';
    }
}


================================================
FILE: examples/src/Repositories/AccessTokenRepository.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use OAuth2ServerExamples\Entities\AccessTokenEntity;

class AccessTokenRepository implements AccessTokenRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity): void
    {
        // Some logic here to save the access token to a database
    }

    /**
     * {@inheritdoc}
     */
    public function revokeAccessToken($tokenId): void
    {
        // Some logic here to revoke the access token
    }

    /**
     * {@inheritdoc}
     */
    public function isAccessTokenRevoked($tokenId): bool
    {
        return false; // Access token hasn't been revoked
    }

    /**
     * {@inheritdoc}
     */
    public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null): AccessTokenEntityInterface
    {
        $accessToken = new AccessTokenEntity();

        $accessToken->setClient($clientEntity);

        foreach ($scopes as $scope) {
            $accessToken->addScope($scope);
        }

        if ($userIdentifier !== null) {
            $accessToken->setUserIdentifier((string) $userIdentifier);
        }

        return $accessToken;
    }
}


================================================
FILE: examples/src/Repositories/AuthCodeRepository.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use OAuth2ServerExamples\Entities\AuthCodeEntity;

class AuthCodeRepository implements AuthCodeRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity): void
    {
        // Some logic to persist the auth code to a database
    }

    /**
     * {@inheritdoc}
     */
    public function revokeAuthCode($codeId): void
    {
        // Some logic to revoke the auth code in a database
    }

    /**
     * {@inheritdoc}
     */
    public function isAuthCodeRevoked($codeId): bool
    {
        return false; // The auth code has not been revoked
    }

    /**
     * {@inheritdoc}
     */
    public function getNewAuthCode(): AuthCodeEntityInterface
    {
        return new AuthCodeEntity();
    }
}


================================================
FILE: examples/src/Repositories/ClientRepository.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use OAuth2ServerExamples\Entities\ClientEntity;

use function array_key_exists;
use function password_hash;
use function password_verify;

class ClientRepository implements ClientRepositoryInterface
{
    private const CLIENT_NAME = 'My Awesome App';
    private const REDIRECT_URI = 'http://foo/bar';

    /**
     * {@inheritdoc}
     */
    public function getClientEntity(string $clientIdentifier): ?ClientEntityInterface
    {
        $client = new ClientEntity();

        $client->setIdentifier($clientIdentifier);
        $client->setName(self::CLIENT_NAME);
        $client->setRedirectUri(self::REDIRECT_URI);
        $client->setConfidential();

        return $client;
    }

    /**
     * {@inheritdoc}
     */
    public function validateClient($clientIdentifier, $clientSecret, $grantType): bool
    {
        $clients = [
            'myawesomeapp' => [
                'secret'          => password_hash('abc123', PASSWORD_BCRYPT),
                'name'            => self::CLIENT_NAME,
                'redirect_uri'    => self::REDIRECT_URI,
                'is_confidential' => true,
            ],
        ];

        // Check if client is registered
        if (array_key_exists($clientIdentifier, $clients) === false) {
            return false;
        }

        if (password_verify($clientSecret, $clients[$clientIdentifier]['secret']) === false) {
            return false;
        }

        return true;
    }
}


================================================
FILE: examples/src/Repositories/DeviceCodeRepository.php
================================================
<?php

/**
 * @author    Andrew Millington <andrew@noexceptions.io>
 * @copyright Copyright (c) Alex Bilbie
 * @license   http://mit-license.org/
 *
 * @link      https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use DateTimeImmutable;
use League\OAuth2\Server\Entities\DeviceCodeEntityInterface;
use League\OAuth2\Server\Repositories\DeviceCodeRepositoryInterface;
use OAuth2ServerExamples\Entities\ClientEntity;
use OAuth2ServerExamples\Entities\DeviceCodeEntity;
use OAuth2ServerExamples\Entities\ScopeEntity;

class DeviceCodeRepository implements DeviceCodeRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function getNewDeviceCode(): DeviceCodeEntityInterface
    {
        return new DeviceCodeEntity();
    }

    /**
     * {@inheritdoc}
     */
    public function persistDeviceCode(DeviceCodeEntityInterface $deviceCodeEntity): void
    {
        // Some logic to persist a new device code to a database
    }

    /**
     * {@inheritdoc}
     */
    public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceCodeEntityInterface
    {
        $clientEntity = new ClientEntity();
        $clientEntity->setIdentifier('myawesomeapp');

        $deviceCodeEntity = new DeviceCodeEntity();

        $deviceCodeEntity->setIdentifier($deviceCode);
        $deviceCodeEntity->setExpiryDateTime(new DateTimeImmutable('now +1 hour'));
        $deviceCodeEntity->setClient($clientEntity);
        $deviceCodeEntity->setLastPolledAt(new DateTimeImmutable());

        $scopes = [];
        foreach ($scopes as $scope) {
            $scopeEntity = new ScopeEntity();
            $scopeEntity->setIdentifier($scope);
            $deviceCodeEntity->addScope($scopeEntity);
        }

        // The user identifier should be set when the user authenticates on the
        // OAuth server, along with whether they approved the request
        $deviceCodeEntity->setUserApproved(true);
        $deviceCodeEntity->setUserIdentifier('1');

        return $deviceCodeEntity;
    }

    /**
     * {@inheritdoc}
     */
    public function revokeDeviceCode($codeId): void
    {
        // Some logic to revoke device code
    }

    /**
     * {@inheritdoc}
     */
    public function isDeviceCodeRevoked($codeId): bool
    {
        return false;
    }
}


================================================
FILE: examples/src/Repositories/RefreshTokenRepository.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use OAuth2ServerExamples\Entities\RefreshTokenEntity;

class RefreshTokenRepository implements RefreshTokenRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity): void
    {
        // Some logic to persist the refresh token in a database
    }

    /**
     * {@inheritdoc}
     */
    public function revokeRefreshToken($tokenId): void
    {
        // Some logic to revoke the refresh token in a database
    }

    /**
     * {@inheritdoc}
     */
    public function isRefreshTokenRevoked($tokenId): bool
    {
        return false; // The refresh token has not been revoked
    }

    /**
     * {@inheritdoc}
     */
    public function getNewRefreshToken(): ?RefreshTokenEntityInterface
    {
        return new RefreshTokenEntity();
    }
}


================================================
FILE: examples/src/Repositories/ScopeRepository.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use OAuth2ServerExamples\Entities\ScopeEntity;

use function array_key_exists;

class ScopeRepository implements ScopeRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function getScopeEntityByIdentifier(string $identifier): ?ScopeEntityInterface
    {
        $scopes = [
            'basic' => [
                'description' => 'Basic details about you',
            ],
            'email' => [
                'description' => 'Your email address',
            ],
        ];

        if (array_key_exists($identifier, $scopes) === false) {
            return null;
        }

        $scope = new ScopeEntity();
        $scope->setIdentifier($identifier);

        return $scope;
    }

    /**
     * {@inheritdoc}
     */
    public function finalizeScopes(
        array $scopes,
        $grantType,
        ClientEntityInterface $clientEntity,
        $userIdentifier = null,
        $authCodeId = null
    ): array {
        // Example of programmatically modifying the final scope of the access token
        if ((int) $userIdentifier === 1) {
            $scope = new ScopeEntity();
            $scope->setIdentifier('email');
            $scopes[] = $scope;
        }

        return $scopes;
    }
}


================================================
FILE: examples/src/Repositories/UserRepository.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace OAuth2ServerExamples\Repositories;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use OAuth2ServerExamples\Entities\UserEntity;

class UserRepository implements UserRepositoryInterface
{
    /**
     * {@inheritdoc}
     */
    public function getUserEntityByUserCredentials(
        $username,
        $password,
        $grantType,
        ClientEntityInterface $clientEntity
    ): ?UserEntityInterface {
        if ($username === 'alex' && $password === 'whisky') {
            return new UserEntity();
        }

        return null;
    }
}


================================================
FILE: phpcs.xml.dist
================================================
<?xml version="1.0"?>
<ruleset
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd"
>
    <arg name="basepath" value="." />
    <arg name="extensions" value="php" />
    <arg name="parallel" value="80" />
    <arg name="colors" />
    <arg name="cache" value=".phpcs.cache" />
    <arg value="p" />

    <file>src</file>
    <file>tests</file>
    <file>examples</file>

    <exclude-pattern>examples/vendor/*</exclude-pattern>

    <rule ref="PSR12">
        <exclude name="Generic.Files.LineLength.TooLong" />
    </rule>

    <config name="installed_paths" value="../../slevomat/coding-standard" />
    <rule ref="SlevomatCodingStandard.TypeHints.DeclareStrictTypes">
        <properties>
            <property name="spacesCountAroundEqualsSign" type="int" value="0" />
        </properties>
    </rule>
    <rule ref="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly" />
    <rule ref="SlevomatCodingStandard.Namespaces.AlphabeticallySortedUses" />
    <rule ref="SlevomatCodingStandard.TypeHints.ParameterTypeHint" />
    <rule ref="SlevomatCodingStandard.TypeHints.ReturnTypeHint" />
    <rule ref="SlevomatCodingStandard.TypeHints.PropertyTypeHint" />
    <rule ref="SlevomatCodingStandard.Commenting.EmptyComment" />
    <rule ref="SlevomatCodingStandard.Classes.RequireConstructorPropertyPromotion" />
    <rule ref="SlevomatCodingStandard.TypeHints.NullableTypeForNullDefaultValue" />
</ruleset>


================================================
FILE: phpstan.neon.dist
================================================
parameters:
    level: 8
    paths:
        - src
        - tests
    ignoreErrors:
        - 
            message: '#Deprecated since v5.5, please use {@see self::withValidationConstraints\(\)} instead#'
            reportUnmatched: false


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true"
         failOnRisky="true"
         failOnWarning="true"
         stopOnError="true"
         stopOnFailure="true"
>
  <source>
    <include>
      <directory>src</directory>
    </include>
  </source>

  <testsuites>
    <testsuite name="Tests">
      <directory>./tests/</directory>
    </testsuite>
  </testsuites>
</phpunit>


================================================
FILE: src/AuthorizationServer.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server;

use DateInterval;
use Defuse\Crypto\Key;
use League\OAuth2\Server\EventEmitting\EmitterAwareInterface;
use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\GrantTypeInterface;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface;
use League\OAuth2\Server\ResponseTypes\AbstractResponseType;
use League\OAuth2\Server\ResponseTypes\BearerTokenResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use SensitiveParameter;

class AuthorizationServer implements EmitterAwareInterface
{
    use EmitterAwarePolyfill;

    /**
     * @var GrantTypeInterface[]
     */
    protected array $enabledGrantTypes = [];

    /**
     * @var DateInterval[]
     */
    protected array $grantTypeAccessTokenTTL = [];

    protected CryptKeyInterface $privateKey;

    protected CryptKeyInterface $publicKey;

    protected ResponseTypeInterface $responseType;

    private string|Key $encryptionKey;

    private string $defaultScope = '';

    private bool $revokeRefreshTokens = true;

    /**
     * New server instance
     */
    public function __construct(
        private ClientRepositoryInterface $clientRepository,
        private AccessTokenRepositoryInterface $accessTokenRepository,
        private ScopeRepositoryInterface $scopeRepository,
        #[SensitiveParameter]
        CryptKeyInterface|string $privateKey,
        #[SensitiveParameter]
        Key|string $encryptionKey,
        ResponseTypeInterface|null $responseType = null
    ) {
        if ($privateKey instanceof CryptKeyInterface === false) {
            $privateKey = new CryptKey($privateKey);
        }

        $this->privateKey = $privateKey;
        $this->encryptionKey = $encryptionKey;

        if ($responseType === null) {
            $responseType = new BearerTokenResponse();
        } else {
            $responseType = clone $responseType;
        }

        $this->responseType = $responseType;
    }

    /**
     * Enable a grant type on the server
     */
    public function enableGrantType(GrantTypeInterface $grantType, DateInterval|null $accessTokenTTL = null): void
    {
        if ($accessTokenTTL === null) {
            $accessTokenTTL = new DateInterval('PT1H');
        }

        $grantType->setAccessTokenRepository($this->accessTokenRepository);
        $grantType->setClientRepository($this->clientRepository);
        $grantType->setScopeRepository($this->scopeRepository);
        $grantType->setDefaultScope($this->defaultScope);
        $grantType->setPrivateKey($this->privateKey);
        $grantType->setEmitter($this->getEmitter());
        $grantType->setEncryptionKey($this->encryptionKey);
        $grantType->revokeRefreshTokens($this->revokeRefreshTokens);

        $this->enabledGrantTypes[$grantType->getIdentifier()] = $grantType;
        $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()] = $accessTokenTTL;
    }

    /**
     * Validate an authorization request
     *
     * @throws OAuthServerException
     */
    public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface
    {
        foreach ($this->enabledGrantTypes as $grantType) {
            if ($grantType->canRespondToAuthorizationRequest($request)) {
                return $grantType->validateAuthorizationRequest($request);
            }
        }

        throw OAuthServerException::unsupportedGrantType();
    }

    /**
     * Complete an authorization request
     */
    public function completeAuthorizationRequest(
        AuthorizationRequestInterface $authRequest,
        ResponseInterface $response
    ): ResponseInterface {
        return $this->enabledGrantTypes[$authRequest->getGrantTypeId()]
            ->completeAuthorizationRequest($authRequest)
            ->generateHttpResponse($response);
    }

    /**
     * Respond to device authorization request
     *
     * @throws OAuthServerException
     */
    public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        foreach ($this->enabledGrantTypes as $grantType) {
            if ($grantType->canRespondToDeviceAuthorizationRequest($request)) {
                return $grantType
                    ->respondToDeviceAuthorizationRequest($request)
                    ->generateHttpResponse($response);
            }
        }

        throw OAuthServerException::unsupportedGrantType();
    }

    /**
     * Complete a device authorization request
     */
    public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void
    {
        $this->enabledGrantTypes['urn:ietf:params:oauth:grant-type:device_code']
          ->completeDeviceAuthorizationRequest($deviceCode, $userId, $userApproved);
    }

    /**
     * Return an access token response.
     *
     * @throws OAuthServerException
     */
    public function respondToAccessTokenRequest(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
    {
        foreach ($this->enabledGrantTypes as $grantType) {
            if (!$grantType->canRespondToAccessTokenRequest($request)) {
                continue;
            }

            $tokenResponse = $grantType->respondToAccessTokenRequest(
                $request,
                $this->getResponseType(),
                $this->grantTypeAccessTokenTTL[$grantType->getIdentifier()]
            );

            return $tokenResponse->generateHttpResponse($response);
        }

        throw OAuthServerException::unsupportedGrantType();
    }

    /**
     * Get the token type that grants will return in the HTTP response.
     */
    protected function getResponseType(): ResponseTypeInterface
    {
        $responseType = clone $this->responseType;

        if ($responseType instanceof AbstractResponseType) {
            $responseType->setPrivateKey($this->privateKey);
        }

        $responseType->setEncryptionKey($this->encryptionKey);

        return $responseType;
    }

    /**
     * Set the default scope for the authorization server.
     */
    public function setDefaultScope(string $defaultScope): void
    {
        $this->defaultScope = $defaultScope;
    }

    /**
     * Sets whether to revoke refresh tokens or not (for all grant types).
     */
    public function revokeRefreshTokens(bool $revokeRefreshTokens): void
    {
        $this->revokeRefreshTokens = $revokeRefreshTokens;
    }
}


================================================
FILE: src/AuthorizationValidators/AuthorizationValidatorInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\AuthorizationValidators;

use Psr\Http\Message\ServerRequestInterface;

interface AuthorizationValidatorInterface
{
    /**
     * Determine the access token in the authorization header and append OAUth
     * properties to the request as attributes.
     */
    public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface;
}


================================================
FILE: src/AuthorizationValidators/BearerTokenValidator.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\AuthorizationValidators;

use DateInterval;
use DateTimeZone;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Exception;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\UnencryptedToken;
use Lcobucci\JWT\Validation\Constraint\LooseValidAt;
use Lcobucci\JWT\Validation\Constraint\SignedWith;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use League\OAuth2\Server\CryptKeyInterface;
use League\OAuth2\Server\CryptTrait;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;

use function date_default_timezone_get;
use function preg_replace;
use function trim;

class BearerTokenValidator implements AuthorizationValidatorInterface
{
    use CryptTrait;

    protected CryptKeyInterface $publicKey;

    private Configuration $jwtConfiguration;

    public function __construct(private AccessTokenRepositoryInterface $accessTokenRepository, private ?DateInterval $jwtValidAtDateLeeway = null)
    {
    }

    /**
     * Set the public key
     */
    public function setPublicKey(CryptKeyInterface $key): void
    {
        $this->publicKey = $key;

        $this->initJwtConfiguration();
    }

    /**
     * Initialise the JWT configuration.
     */
    private function initJwtConfiguration(): void
    {
        $this->jwtConfiguration = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::plainText('empty', 'empty')
        );

        $clock = new SystemClock(new DateTimeZone(date_default_timezone_get()));

        $publicKeyContents = $this->publicKey->getKeyContents();

        if ($publicKeyContents === '') {
            throw new RuntimeException('Public key is empty');
        }

        // TODO: next major release: replace deprecated method and remove phpstan ignored error
        $this->jwtConfiguration->setValidationConstraints(
            new LooseValidAt($clock, $this->jwtValidAtDateLeeway),
            new SignedWith(
                new Sha256(),
                InMemory::plainText($publicKeyContents, $this->publicKey->getPassPhrase() ?? '')
            )
        );
    }

    /**
     * {@inheritdoc}
     */
    public function validateAuthorization(ServerRequestInterface $request): ServerRequestInterface
    {
        if ($request->hasHeader('authorization') === false) {
            throw OAuthServerException::accessDenied('Missing "Authorization" header');
        }

        $header = $request->getHeader('authorization');
        $jwt = trim((string) preg_replace('/^\s*Bearer\s/i', '', $header[0]));

        if ($jwt === '') {
            throw OAuthServerException::accessDenied('Missing "Bearer" token');
        }

        try {
            // Attempt to parse the JWT
            $token = $this->jwtConfiguration->parser()->parse($jwt);
        } catch (Exception $exception) {
            throw OAuthServerException::accessDenied($exception->getMessage(), null, $exception);
        }

        try {
            // Attempt to validate the JWT
            $constraints = $this->jwtConfiguration->validationConstraints();
            $this->jwtConfiguration->validator()->assert($token, ...$constraints);
        } catch (RequiredConstraintsViolated $exception) {
            throw OAuthServerException::accessDenied('Access token could not be verified', null, $exception);
        }

        if (!$token instanceof UnencryptedToken) {
            throw OAuthServerException::accessDenied('Access token is not an instance of UnencryptedToken');
        }

        $claims = $token->claims();

        // Check if token has been revoked
        if ($this->accessTokenRepository->isAccessTokenRevoked($claims->get('jti'))) {
            throw OAuthServerException::accessDenied('Access token has been revoked');
        }

        // Return the request with additional attributes
        return $request
            ->withAttribute('oauth_access_token_id', $claims->get('jti'))
            ->withAttribute('oauth_client_id', $claims->get('aud')[0])
            ->withAttribute('oauth_user_id', $claims->get('sub'))
            ->withAttribute('oauth_scopes', $claims->get('scopes'));
    }
}


================================================
FILE: src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php
================================================
<?php

/**
 * @author      Lukáš Unger <lookymsc@gmail.com>
 * @copyright   Copyright (c) Lukáš Unger
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\CodeChallengeVerifiers;

interface CodeChallengeVerifierInterface
{
    /**
     * Return code challenge method.
     */
    public function getMethod(): string;

    /**
     * Verify the code challenge.
     */
    public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool;
}


================================================
FILE: src/CodeChallengeVerifiers/PlainVerifier.php
================================================
<?php

/**
 * @author      Lukáš Unger <lookymsc@gmail.com>
 * @copyright   Copyright (c) Lukáš Unger
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\CodeChallengeVerifiers;

use function hash_equals;

class PlainVerifier implements CodeChallengeVerifierInterface
{
    /**
     * Return code challenge method.
     */
    public function getMethod(): string
    {
        return 'plain';
    }

    /**
     * Verify the code challenge.
     */
    public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool
    {
        return hash_equals($codeVerifier, $codeChallenge);
    }
}


================================================
FILE: src/CodeChallengeVerifiers/S256Verifier.php
================================================
<?php

/**
 * @author      Lukáš Unger <lookymsc@gmail.com>
 * @copyright   Copyright (c) Lukáš Unger
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\CodeChallengeVerifiers;

use function base64_encode;
use function hash;
use function hash_equals;
use function rtrim;
use function strtr;

class S256Verifier implements CodeChallengeVerifierInterface
{
    /**
     * Return code challenge method.
     */
    public function getMethod(): string
    {
        return 'S256';
    }

    /**
     * Verify the code challenge.
     */
    public function verifyCodeChallenge(string $codeVerifier, string $codeChallenge): bool
    {
        return hash_equals(
            strtr(rtrim(base64_encode(hash('sha256', $codeVerifier, true)), '='), '+/', '-_'),
            $codeChallenge
        );
    }
}


================================================
FILE: src/CryptKey.php
================================================
<?php

/**
 * Cryptography key holder.
 *
 * @author      Julián Gutiérrez <juliangut@gmail.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server;

use LogicException;
use OpenSSLAsymmetricKey;
use SensitiveParameter;

use function decoct;
use function file_get_contents;
use function fileperms;
use function in_array;
use function is_file;
use function is_readable;
use function openssl_pkey_get_details;
use function openssl_pkey_get_private;
use function openssl_pkey_get_public;
use function sprintf;
use function trigger_error;

class CryptKey implements CryptKeyInterface
{
    private const FILE_PREFIX = 'file://';

    /**
     * @var string Key contents
     */
    protected string $keyContents;

    protected string $keyPath;

    public function __construct(
        string $keyPath,
        #[SensitiveParameter]
        protected ?string $passPhrase = null,
        bool $keyPermissionsCheck = true
    ) {
        if (str_starts_with($keyPath, self::FILE_PREFIX) === false && $this->isValidKey($keyPath, $this->passPhrase ?? '')) {
            $this->keyContents = $keyPath;
            $this->keyPath = '';
            // There's no file, so no need for permission check.
            $keyPermissionsCheck = false;
        } elseif (is_file($keyPath)) {
            if (str_starts_with($keyPath, self::FILE_PREFIX) === false) {
                $keyPath = self::FILE_PREFIX . $keyPath;
            }

            if (!is_readable($keyPath)) {
                throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath));
            }

            $keyContents = file_get_contents($keyPath);

            if ($keyContents === false) {
                throw new LogicException('Unable to read key from file ' . $keyPath);
            }

            $this->keyContents = $keyContents;
            $this->keyPath = $keyPath;

            if (!$this->isValidKey($this->keyContents, $this->passPhrase ?? '')) {
                throw new LogicException('Unable to read key from file ' . $keyPath);
            }
        } else {
            throw new LogicException('Invalid key supplied');
        }

        if ($keyPermissionsCheck === true && PHP_OS_FAMILY !== 'Windows') {
            // Verify the permissions of the key
            $keyPathPerms = decoct(fileperms($this->keyPath) & 0777);
            if (in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) {
                trigger_error(
                    sprintf(
                        'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s',
                        $this->keyPath,
                        $keyPathPerms
                    ),
                    E_USER_NOTICE
                );
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getKeyContents(): string
    {
        return $this->keyContents;
    }

    /**
     * Validate key contents.
     */
    private function isValidKey(
        #[SensitiveParameter]
        string $contents,
        #[SensitiveParameter]
        string $passPhrase
    ): bool {
        $privateKey = openssl_pkey_get_private($contents, $passPhrase);

        $key = $privateKey instanceof OpenSSLAsymmetricKey ? $privateKey : openssl_pkey_get_public($contents);

        if ($key === false) {
            return false;
        }

        $details = openssl_pkey_get_details($key);

        return $details !== false && in_array(
            $details['type'] ?? -1,
            [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC],
            true
        );
    }

    /**
     * {@inheritdoc}
     */
    public function getKeyPath(): string
    {
        return $this->keyPath;
    }

    /**
     * {@inheritdoc}
     */
    public function getPassPhrase(): ?string
    {
        return $this->passPhrase;
    }
}


================================================
FILE: src/CryptKeyInterface.php
================================================
<?php

declare(strict_types=1);

namespace League\OAuth2\Server;

interface CryptKeyInterface
{
    /**
     * Retrieve key path.
     */
    public function getKeyPath(): string;

    /**
     * Retrieve key pass phrase.
     */
    public function getPassPhrase(): ?string;

    /**
     * Get key contents
     *
     * @return string Key contents
     */
    public function getKeyContents(): string;
}


================================================
FILE: src/CryptTrait.php
================================================
<?php

/**
 * Encrypt/decrypt with encryptionKey.
 *
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server;

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Exception\EnvironmentIsBrokenException;
use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
use Defuse\Crypto\Key;
use Exception;
use InvalidArgumentException;
use LogicException;
use SensitiveParameter;

use function is_string;

trait CryptTrait
{
    protected string|Key|null $encryptionKey = null;

    /**
     * Encrypt data with encryptionKey.
     *
     * @throws LogicException
     */
    protected function encrypt(string $unencryptedData): string
    {
        try {
            if ($this->encryptionKey instanceof Key) {
                return Crypto::encrypt($unencryptedData, $this->encryptionKey);
            }

            if (is_string($this->encryptionKey)) {
                return Crypto::encryptWithPassword($unencryptedData, $this->encryptionKey);
            }

            throw new LogicException('Encryption key not set when attempting to encrypt');
        } catch (Exception $e) {
            throw new LogicException($e->getMessage(), 0, $e);
        }
    }

    /**
     * Decrypt data with encryptionKey.
     *
     * @throws LogicException
     */
    protected function decrypt(string $encryptedData): string
    {
        try {
            if ($this->encryptionKey instanceof Key) {
                return Crypto::decrypt($encryptedData, $this->encryptionKey);
            }

            if (is_string($this->encryptionKey)) {
                return Crypto::decryptWithPassword($encryptedData, $this->encryptionKey);
            }

            throw new LogicException('Encryption key not set when attempting to decrypt');
        } catch (WrongKeyOrModifiedCiphertextException $e) {
            $exceptionMessage = 'The authcode or decryption key/password used '
                . 'is not correct';

            throw new InvalidArgumentException($exceptionMessage, 0, $e);
        } catch (EnvironmentIsBrokenException $e) {
            $exceptionMessage = 'Auth code decryption failed. This is likely '
                . 'due to an environment issue or runtime bug in the '
                . 'decryption library';

            throw new LogicException($exceptionMessage, 0, $e);
        } catch (Exception $e) {
            throw new LogicException($e->getMessage(), 0, $e);
        }
    }

    public function setEncryptionKey(
        #[SensitiveParameter]
        Key|string|null $key = null
    ): void {
        $this->encryptionKey = $key;
    }
}


================================================
FILE: src/Entities/AccessTokenEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

use League\OAuth2\Server\CryptKeyInterface;

interface AccessTokenEntityInterface extends TokenInterface
{
    /**
     * Set a private key used to encrypt the access token.
     */
    public function setPrivateKey(CryptKeyInterface $privateKey): void;

    /**
     * Generate a string representation of the access token.
     */
    public function toString(): string;
}


================================================
FILE: src/Entities/AuthCodeEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

interface AuthCodeEntityInterface extends TokenInterface
{
    public function getRedirectUri(): string|null;

    public function setRedirectUri(string $uri): void;
}


================================================
FILE: src/Entities/ClientEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

interface ClientEntityInterface
{
    /**
     * Get the client's identifier.
     *
     * @return non-empty-string
     */
    public function getIdentifier(): string;

    /**
     * Get the client's name.
     */
    public function getName(): string;

    /**
     * Returns the registered redirect URI (as a string). Alternatively return
     * an indexed array of redirect URIs.
     *
     * @return string|string[]
     */
    public function getRedirectUri(): string|array;

    /**
     * Returns true if the client is confidential.
     */
    public function isConfidential(): bool;

    /*
     * Returns true if the client supports the given grant type.
     *
     * TODO: To be added in a future major release.
     */
    // public function supportsGrantType(string $grantType): bool;
}


================================================
FILE: src/Entities/DeviceCodeEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

use DateTimeImmutable;

interface DeviceCodeEntityInterface extends TokenInterface
{
    public function getUserCode(): string;

    public function setUserCode(string $userCode): void;

    public function getVerificationUri(): string;

    public function setVerificationUri(string $verificationUri): void;

    public function getVerificationUriComplete(): string;

    public function getLastPolledAt(): ?DateTimeImmutable;

    public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void;

    public function getInterval(): int;

    public function setInterval(int $interval): void;

    public function getUserApproved(): bool;

    public function setUserApproved(bool $userApproved): void;
}


================================================
FILE: src/Entities/RefreshTokenEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

use DateTimeImmutable;

interface RefreshTokenEntityInterface
{
    /**
     * Get the token's identifier.
     *
     * @return non-empty-string
     */
    public function getIdentifier(): string;

    /**
     * Set the token's identifier.
     *
     * @param non-empty-string $identifier
     */
    public function setIdentifier(string $identifier): void;

    /**
     * Get the token's expiry date time.
     */
    public function getExpiryDateTime(): DateTimeImmutable;

    /**
     * Set the date time when the token expires.
     */
    public function setExpiryDateTime(DateTimeImmutable $dateTime): void;

    /**
     * Set the access token that the refresh token was associated with.
     */
    public function setAccessToken(AccessTokenEntityInterface $accessToken): void;

    /**
     * Get the access token that the refresh token was originally associated with.
     */
    public function getAccessToken(): AccessTokenEntityInterface;
}


================================================
FILE: src/Entities/ScopeEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

use JsonSerializable;

interface ScopeEntityInterface extends JsonSerializable
{
    /**
     * Get the scope's identifier.
     *
     * @return non-empty-string
     */
    public function getIdentifier(): string;
}


================================================
FILE: src/Entities/TokenInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

use DateTimeImmutable;

interface TokenInterface
{
    /**
     * Get the token's identifier.
     *
     * @return non-empty-string
     */
    public function getIdentifier(): string;

    /**
     * Set the token's identifier.
     *
     * @param non-empty-string $identifier
     */
    public function setIdentifier(string $identifier): void;

    /**
     * Get the token's expiry date time.
     */
    public function getExpiryDateTime(): DateTimeImmutable;

    /**
     * Set the date time when the token expires.
     */
    public function setExpiryDateTime(DateTimeImmutable $dateTime): void;

    /**
     * Set the identifier of the user associated with the token.
     *
     * @param non-empty-string $identifier
     */
    public function setUserIdentifier(string $identifier): void;

    /**
     * Get the token user's identifier.
     *
     * @return non-empty-string|null
     */
    public function getUserIdentifier(): string|null;

    /**
     * Get the client that the token was issued to.
     */
    public function getClient(): ClientEntityInterface;

    /**
     * Set the client that the token was issued to.
     */
    public function setClient(ClientEntityInterface $client): void;

    /**
     * Associate a scope with the token.
     */
    public function addScope(ScopeEntityInterface $scope): void;

    /**
     * Return an array of scopes associated with the token.
     *
     * @return ScopeEntityInterface[]
     */
    public function getScopes(): array;
}


================================================
FILE: src/Entities/Traits/AccessTokenTrait.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

use DateTimeImmutable;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Signer\Rsa\Sha256;
use Lcobucci\JWT\Token;
use League\OAuth2\Server\CryptKeyInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use RuntimeException;
use SensitiveParameter;

trait AccessTokenTrait
{
    private CryptKeyInterface $privateKey;

    private Configuration $jwtConfiguration;

    /**
     * Set the private key used to encrypt this access token.
     */
    public function setPrivateKey(
        #[SensitiveParameter]
        CryptKeyInterface $privateKey
    ): void {
        $this->privateKey = $privateKey;
    }

    /**
     * Initialise the JWT Configuration.
     */
    public function initJwtConfiguration(): void
    {
        $privateKeyContents = $this->privateKey->getKeyContents();

        if ($privateKeyContents === '') {
            throw new RuntimeException('Private key is empty');
        }

        $this->jwtConfiguration = Configuration::forAsymmetricSigner(
            new Sha256(),
            InMemory::plainText($privateKeyContents, $this->privateKey->getPassPhrase() ?? ''),
            InMemory::plainText('empty', 'empty')
        );
    }

    /**
     * Generate a JWT from the access token
     */
    private function convertToJWT(): Token
    {
        $this->initJwtConfiguration();

        return $this->jwtConfiguration->builder()
            ->permittedFor($this->getClient()->getIdentifier())
            ->identifiedBy($this->getIdentifier())
            ->issuedAt(new DateTimeImmutable())
            ->canOnlyBeUsedAfter(new DateTimeImmutable())
            ->expiresAt($this->getExpiryDateTime())
            ->relatedTo($this->getSubjectIdentifier())
            ->withClaim('scopes', $this->getScopes())
            ->getToken($this->jwtConfiguration->signer(), $this->jwtConfiguration->signingKey());
    }

    /**
     * Generate a string representation from the access token
     */
    public function toString(): string
    {
        return $this->convertToJWT()->toString();
    }

    abstract public function getClient(): ClientEntityInterface;

    abstract public function getExpiryDateTime(): DateTimeImmutable;

    /**
     * @return non-empty-string|null
     */
    abstract public function getUserIdentifier(): string|null;

    /**
     * @return ScopeEntityInterface[]
     */
    abstract public function getScopes(): array;

    /**
     * @return non-empty-string
     */
    abstract public function getIdentifier(): string;

    /**
     * @return non-empty-string
     */
    private function getSubjectIdentifier(): string
    {
        return $this->getUserIdentifier() ?? $this->getClient()->getIdentifier();
    }
}


================================================
FILE: src/Entities/Traits/AuthCodeTrait.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

trait AuthCodeTrait
{
    protected ?string $redirectUri = null;

    public function getRedirectUri(): string|null
    {
        return $this->redirectUri;
    }

    public function setRedirectUri(string $uri): void
    {
        $this->redirectUri = $uri;
    }
}


================================================
FILE: src/Entities/Traits/ClientTrait.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

trait ClientTrait
{
    protected string $name;

    /**
     * @var string|string[]
     */
    protected string|array $redirectUri;

    protected bool $isConfidential = false;

    /**
     * Get the client's name.
     *
     *
     * @codeCoverageIgnore
     */
    public function getName(): string
    {
        return $this->name;
    }

    /**
     * Returns the registered redirect URI (as a string). Alternatively return
     * an indexed array of redirect URIs.
     *
     * @return string|string[]
     */
    public function getRedirectUri(): string|array
    {
        return $this->redirectUri;
    }

    /**
     * Returns true if the client is confidential.
     */
    public function isConfidential(): bool
    {
        return $this->isConfidential;
    }

    /**
     * Returns true if the client supports the given grant type.
     */
    public function supportsGrantType(string $grantType): bool
    {
        return true;
    }
}


================================================
FILE: src/Entities/Traits/DeviceCodeTrait.php
================================================
<?php

/**
 * @author      Andrew Millington <andrew@noexceptions.io>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

use DateTimeImmutable;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;

trait DeviceCodeTrait
{
    private bool $userApproved = false;
    private bool $includeVerificationUriComplete = false;
    private int $interval = 5;
    private string $userCode;
    private string $verificationUri;
    private ?DateTimeImmutable $lastPolledAt = null;

    public function getUserCode(): string
    {
        return $this->userCode;
    }

    public function setUserCode(string $userCode): void
    {
        $this->userCode = $userCode;
    }

    public function getVerificationUri(): string
    {
        return $this->verificationUri;
    }

    public function setVerificationUri(string $verificationUri): void
    {
        $this->verificationUri = $verificationUri;
    }

    public function getVerificationUriComplete(): string
    {
        return $this->verificationUri . '?user_code=' . $this->userCode;
    }

    abstract public function getClient(): ClientEntityInterface;

    abstract public function getExpiryDateTime(): DateTimeImmutable;

    /**
     * @return ScopeEntityInterface[]
     */
    abstract public function getScopes(): array;

    /**
     * @return non-empty-string
     */
    abstract public function getIdentifier(): string;

    public function getLastPolledAt(): ?DateTimeImmutable
    {
        return $this->lastPolledAt;
    }

    public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void
    {
        $this->lastPolledAt = $lastPolledAt;
    }

    public function getInterval(): int
    {
        return $this->interval;
    }

    public function setInterval(int $interval): void
    {
        $this->interval = $interval;
    }

    public function getUserApproved(): bool
    {
        return $this->userApproved;
    }

    public function setUserApproved(bool $userApproved): void
    {
        $this->userApproved = $userApproved;
    }

    public function getVerificationUriCompleteInAuthResponse(): bool
    {
        return $this->includeVerificationUriComplete;
    }

    public function setVerificationUriCompleteInAuthResponse(bool $includeVerificationUriComplete): void
    {
        $this->includeVerificationUriComplete = $includeVerificationUriComplete;
    }
}


================================================
FILE: src/Entities/Traits/EntityTrait.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

trait EntityTrait
{
    /**
     * @var non-empty-string
     */
    protected string $identifier;

    /**
     * @return non-empty-string
     */
    public function getIdentifier(): string
    {
        return $this->identifier;
    }

    /**
     * @param non-empty-string $identifier
     */
    public function setIdentifier(string $identifier): void
    {
        $this->identifier = $identifier;
    }
}


================================================
FILE: src/Entities/Traits/RefreshTokenTrait.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

use DateTimeImmutable;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;

trait RefreshTokenTrait
{
    protected AccessTokenEntityInterface $accessToken;

    protected DateTimeImmutable $expiryDateTime;

    /**
     * {@inheritdoc}
     */
    public function setAccessToken(AccessTokenEntityInterface $accessToken): void
    {
        $this->accessToken = $accessToken;
    }

    /**
     * {@inheritdoc}
     */
    public function getAccessToken(): AccessTokenEntityInterface
    {
        return $this->accessToken;
    }

    /**
     * Get the token's expiry date time.
     */
    public function getExpiryDateTime(): DateTimeImmutable
    {
        return $this->expiryDateTime;
    }

    /**
     * Set the date time when the token expires.
     */
    public function setExpiryDateTime(DateTimeImmutable $dateTime): void
    {
        $this->expiryDateTime = $dateTime;
    }
}


================================================
FILE: src/Entities/Traits/ScopeTrait.php
================================================
<?php

/**
 * @author    Andrew Millington <andrew@noexceptions.io>
 * @copyright Copyright (c) Andrew Millington
 * @license   http://mit-license.org
 *
 * @link      https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

trait ScopeTrait
{
    /**
     * Serialize the object to the scopes string identifier when using json_encode().
     */
    public function jsonSerialize(): string
    {
        return $this->getIdentifier();
    }

    abstract public function getIdentifier(): string;
}


================================================
FILE: src/Entities/Traits/TokenEntityTrait.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities\Traits;

use DateTimeImmutable;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;

use function array_values;

trait TokenEntityTrait
{
    /**
     * @var ScopeEntityInterface[]
     */
    protected array $scopes = [];

    protected DateTimeImmutable $expiryDateTime;

    /**
     * @var non-empty-string|null
     */
    protected string|null $userIdentifier = null;

    protected ClientEntityInterface $client;

    /**
     * Associate a scope with the token.
     */
    public function addScope(ScopeEntityInterface $scope): void
    {
        $this->scopes[$scope->getIdentifier()] = $scope;
    }

    /**
     * Return an array of scopes associated with the token.
     *
     * @return ScopeEntityInterface[]
     */
    public function getScopes(): array
    {
        return array_values($this->scopes);
    }

    /**
     * Get the token's expiry date time.
     */
    public function getExpiryDateTime(): DateTimeImmutable
    {
        return $this->expiryDateTime;
    }

    /**
     * Set the date time when the token expires.
     */
    public function setExpiryDateTime(DateTimeImmutable $dateTime): void
    {
        $this->expiryDateTime = $dateTime;
    }

    /**
     * Set the identifier of the user associated with the token.
     *
     * @param non-empty-string $identifier The identifier of the user
     */
    public function setUserIdentifier(string $identifier): void
    {
        $this->userIdentifier = $identifier;
    }

    /**
     * Get the token user's identifier.
     *
     * @return non-empty-string|null
     */
    public function getUserIdentifier(): string|null
    {
        return $this->userIdentifier;
    }

    /**
     * Get the client that the token was issued to.
     */
    public function getClient(): ClientEntityInterface
    {
        return $this->client;
    }

    /**
     * Set the client that the token was issued to.
     */
    public function setClient(ClientEntityInterface $client): void
    {
        $this->client = $client;
    }
}


================================================
FILE: src/Entities/UserEntityInterface.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Entities;

interface UserEntityInterface
{
    /**
     * Return the user's identifier.
     *
     * @return non-empty-string
     */
    public function getIdentifier(): string;
}


================================================
FILE: src/EventEmitting/AbstractEvent.php
================================================
<?php

declare(strict_types=1);

namespace League\OAuth2\Server\EventEmitting;

use League\Event\HasEventName;
use Psr\EventDispatcher\StoppableEventInterface;

class AbstractEvent implements StoppableEventInterface, HasEventName
{
    private bool $propagationStopped = false;

    public function __construct(private string $name)
    {
    }

    public function eventName(): string
    {
        return $this->name;
    }

    /**
     * Backwards compatibility method
     *
     * @deprecated use eventName instead
     */
    public function getName(): string
    {
        return $this->name;
    }

    public function isPropagationStopped(): bool
    {
        return $this->propagationStopped;
    }

    public function stopPropagation(): self
    {
        $this->propagationStopped = true;

        return $this;
    }
}


================================================
FILE: src/EventEmitting/EmitterAwareInterface.php
================================================
<?php

declare(strict_types=1);

namespace League\OAuth2\Server\EventEmitting;

interface EmitterAwareInterface
{
    public function getEmitter(): EventEmitter;

    public function setEmitter(EventEmitter $emitter): self;
}


================================================
FILE: src/EventEmitting/EmitterAwarePolyfill.php
================================================
<?php

declare(strict_types=1);

namespace League\OAuth2\Server\EventEmitting;

use League\Event\ListenerRegistry;
use Psr\EventDispatcher\EventDispatcherInterface;

trait EmitterAwarePolyfill
{
    private EventEmitter $emitter;

    public function getEmitter(): EventEmitter
    {
        return $this->emitter ??= new EventEmitter();
    }

    public function setEmitter(EventEmitter $emitter): self
    {
        $this->emitter = $emitter;

        return $this;
    }

    public function getEventDispatcher(): EventDispatcherInterface
    {
        return $this->getEmitter();
    }

    public function getListenerRegistry(): ListenerRegistry
    {
        return $this->getEmitter();
    }
}


================================================
FILE: src/EventEmitting/EventEmitter.php
================================================
<?php

declare(strict_types=1);

namespace League\OAuth2\Server\EventEmitting;

use League\Event\EventDispatcher;
use League\Event\ListenerPriority;

final class EventEmitter extends EventDispatcher
{
    public function addListener(string $event, callable $listener, int $priority = ListenerPriority::NORMAL): self
    {
        $this->subscribeTo($event, $listener, $priority);

        return $this;
    }

    public function emit(object $event): object
    {
        return $this->dispatch($event);
    }
}


================================================
FILE: src/Exception/OAuthServerException.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Exception;

use Exception;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

use function htmlspecialchars;
use function http_build_query;
use function sprintf;

class OAuthServerException extends Exception
{
    /**
     * @var array<string, string>
     */
    private array $payload;

    private ServerRequestInterface $serverRequest;

    /**
     * Throw a new exception.
     */
    final public function __construct(string $message, int $code, private string $errorType, private int $httpStatusCode = 400, private ?string $hint = null, private ?string $redirectUri = null, ?Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->payload = [
            'error'             => $errorType,
            'error_description' => $message,
        ];

        if ($hint !== null) {
            $this->payload['hint'] = $hint;
        }
    }

    /**
     * Returns the current payload.
     *
     * @return array<string, string>
     */
    public function getPayload(): array
    {
        return $this->payload;
    }

    /**
     * Updates the current payload.
     *
     * @param array<string, string> $payload
     */
    public function setPayload(array $payload): void
    {
        $this->payload = $payload;
    }

    /**
     * Set the server request that is responsible for generating the exception
     */
    public function setServerRequest(ServerRequestInterface $serverRequest): void
    {
        $this->serverRequest = $serverRequest;
    }

    /**
     * Unsupported grant type error.
     */
    public static function unsupportedGrantType(): static
    {
        $errorMessage = 'The authorization grant type is not supported by the authorization server.';
        $hint = 'Check that all required parameters have been provided';

        return new static($errorMessage, 2, 'unsupported_grant_type', 400, $hint);
    }

    /**
     * Invalid request error.
     */
    public static function invalidRequest(string $parameter, ?string $hint = null, ?Throwable $previous = null): static
    {
        $errorMessage = 'The request is missing a required parameter, includes an invalid parameter value, ' .
            'includes a parameter more than once, or is otherwise malformed.';
        $hint = ($hint === null) ? sprintf('Check the `%s` parameter', $parameter) : $hint;

        return new static($errorMessage, 3, 'invalid_request', 400, $hint, null, $previous);
    }

    /**
     * Invalid client error.
     */
    public static function invalidClient(ServerRequestInterface $serverRequest): static
    {
        $exception = new static('Client authentication failed', 4, 'invalid_client', 401);

        $exception->setServerRequest($serverRequest);

        return $exception;
    }

    /**
     * Invalid scope error
     */
    public static function invalidScope(string $scope, string|null $redirectUri = null): static
    {
        $errorMessage = 'The requested scope is invalid, unknown, or malformed';

        if ($scope === '') {
            $hint = 'Specify a scope in the request or set a default scope';
        } else {
            $hint = sprintf(
                'Check the `%s` scope',
                htmlspecialchars($scope, ENT_QUOTES, 'UTF-8', false)
            );
        }

        return new static($errorMessage, 5, 'invalid_scope', 400, $hint, $redirectUri);
    }

    /**
     * Invalid credentials error.
     */
    public static function invalidCredentials(): static
    {
        return new static('The user credentials were incorrect.', 6, 'invalid_grant', 400);
    }

    /**
     * Server error.
     *
     * @codeCoverageIgnore
     */
    public static function serverError(string $hint, ?Throwable $previous = null): static
    {
        return new static(
            'The authorization server encountered an unexpected condition which prevented it from fulfilling'
            . ' the request: ' . $hint,
            7,
            'server_error',
            500,
            null,
            null,
            $previous
        );
    }

    /**
     * Invalid refresh token.
     */
    public static function invalidRefreshToken(?string $hint = null, ?Throwable $previous = null): static
    {
        return new static('The refresh token is invalid.', 8, 'invalid_grant', 400, $hint, null, $previous);
    }

    /**
     * Access denied.
     */
    public static function accessDenied(?string $hint = null, ?string $redirectUri = null, ?Throwable $previous = null): static
    {
        return new static(
            'The resource owner or authorization server denied the request.',
            9,
            'access_denied',
            401,
            $hint,
            $redirectUri,
            $previous
        );
    }

    /**
     * Invalid grant.
     */
    public static function invalidGrant(string $hint = ''): static
    {
        return new static(
            'The provided authorization grant (e.g., authorization code, resource owner credentials) or refresh token '
                . 'is invalid, expired, revoked, does not match the redirection URI used in the authorization request, '
                . 'or was issued to another client.',
            10,
            'invalid_grant',
            400,
            $hint
        );
    }

    public function getErrorType(): string
    {
        return $this->errorType;
    }

    /**
     * Expired token error.
     *
     * @param Throwable $previous Previous exception
     *
     * @return static
     */
    public static function expiredToken(?string $hint = null, ?Throwable $previous = null): static
    {
        $errorMessage = 'The `device_code` has expired and the device ' .
                        'authorization session has concluded.';

        return new static($errorMessage, 11, 'expired_token', 400, $hint, null, $previous);
    }

    public static function authorizationPending(string $hint = '', ?Throwable $previous = null): static
    {
        return new static(
            'The authorization request is still pending as the end user ' .
            'hasn\'t yet completed the user interaction steps. The client ' .
            'SHOULD repeat the Access Token Request to the token endpoint',
            12,
            'authorization_pending',
            400,
            $hint,
            null,
            $previous
        );
    }

    /**
     * Slow down error used with the Device Authorization Grant.
     *
     *
     * @return static
     */
    public static function slowDown(string $hint = '', ?Throwable $previous = null): static
    {
        return new static(
            'The authorization request is still pending and polling should ' .
                'continue, but the interval MUST be increased ' .
                'by 5 seconds for this and all subsequent requests.',
            13,
            'slow_down',
            400,
            $hint,
            null,
            $previous
        );
    }

    /**
     * Unauthorized client error.
     */
    public static function unauthorizedClient(?string $hint = null): static
    {
        return new static(
            'The authenticated client is not authorized to use this authorization grant type.',
            14,
            'unauthorized_client',
            400,
            $hint
        );
    }

    /**
     * Generate a HTTP response.
     */
    public function generateHttpResponse(ResponseInterface $response, bool $useFragment = false, int $jsonOptions = 0): ResponseInterface
    {
        $headers = $this->getHttpHeaders();

        $payload = $this->getPayload();

        if ($this->redirectUri !== null) {
            if ($useFragment === true) {
                $this->redirectUri .= (str_contains($this->redirectUri, '#') === false) ? '#' : '&';
            } else {
                $this->redirectUri .= (str_contains($this->redirectUri, '?') === false) ? '?' : '&';
            }

            return $response->withStatus(302)->withHeader('Location', $this->redirectUri . http_build_query($payload));
        }

        foreach ($headers as $header => $content) {
            $response = $response->withHeader($header, $content);
        }

        $jsonEncodedPayload = json_encode($payload, $jsonOptions);

        $responseBody = $jsonEncodedPayload === false ? 'JSON encoding of payload failed' : $jsonEncodedPayload;

        $response->getBody()->write($responseBody);

        return $response->withStatus($this->getHttpStatusCode());
    }

    /**
     * Get all headers that have to be send with the error response.
     *
     * @return array<string, string> Array with header values
     */
    public function getHttpHeaders(): array
    {
        $headers = [
            'Content-type' => 'application/json',
        ];

        // Add "WWW-Authenticate" header
        //
        // RFC 6749, section 5.2.:
        // "If the client attempted to authenticate via the 'Authorization'
        // request header field, the authorization server MUST
        // respond with an HTTP 401 (Unauthorized) status code and
        // include the "WWW-Authenticate" response header field
        // matching the authentication scheme used by the client.
        if ($this->errorType === 'invalid_client' && $this->requestHasAuthorizationHeader()) {
            $authScheme = str_starts_with($this->serverRequest->getHeader('Authorization')[0], 'Bearer') ? 'Bearer' : 'Basic';

            $headers['WWW-Authenticate'] = $authScheme . ' realm="OAuth"';
        }

        return $headers;
    }

    /**
     * Check if the exception has an associated redirect URI.
     *
     * Returns whether the exception includes a redirect, since
     * getHttpStatusCode() doesn't return a 302 when there's a
     * redirect enabled. This helps when you want to override local
     * error pages but want to let redirects through.
     */
    public function hasRedirect(): bool
    {
        return $this->redirectUri !== null;
    }

    /**
     * Returns the Redirect URI used for redirecting.
     */
    public function getRedirectUri(): ?string
    {
        return $this->redirectUri;
    }

    /**
     * Returns the HTTP status code to send when the exceptions is output.
     */
    public function getHttpStatusCode(): int
    {
        return $this->httpStatusCode;
    }

    public function getHint(): ?string
    {
        return $this->hint;
    }

    /**
     * Check if the request has a non-empty 'Authorization' header value.
     *
     * Returns true if the header is present and not an empty string, false
     * otherwise.
     */
    private function requestHasAuthorizationHeader(): bool
    {
        if (!$this->serverRequest->hasHeader('Authorization')) {
            return false;
        }

        $authorizationHeader = $this->serverRequest->getHeader('Authorization');

        // Common .htaccess configurations yield an empty string for the
        // 'Authorization' header when one is not provided by the client.
        // For practical purposes that case should be treated as though the
        // header isn't present.
        // See https://github.com/thephpleague/oauth2-server/issues/1162
        if ($authorizationHeader === [] || $authorizationHeader[0] === '') {
            return false;
        }

        return true;
    }
}


================================================
FILE: src/Exception/UniqueTokenIdentifierConstraintViolationException.php
================================================
<?php

/**
 * @author      Ivan Kurnosov <zerkms@zerkms.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Exception;

class UniqueTokenIdentifierConstraintViolationException extends OAuthServerException
{
    public static function create(): UniqueTokenIdentifierConstraintViolationException
    {
        $errorMessage = 'Could not create unique access token identifier';

        return new static($errorMessage, 100, 'access_token_duplicate', 500);
    }
}


================================================
FILE: src/Grant/AbstractAuthorizeGrant.php
================================================
<?php

/**
 * Abstract authorization grant.
 *
 * @author      Julián Gutiérrez <juliangut@gmail.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Grant;

use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\RequestTypes\AuthorizationRequest;
use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface;

use function http_build_query;

abstract class AbstractAuthorizeGrant extends AbstractGrant
{
    /**
     * @param array<array-key,mixed> $params
     */
    public function makeRedirectUri(string $uri, array $params = [], string $queryDelimiter = '?'): string
    {
        $uri .= str_contains($uri, $queryDelimiter) ? '&' : $queryDelimiter;

        return $uri . http_build_query($params);
    }

    protected function createAuthorizationRequest(): AuthorizationRequestInterface
    {
        return new AuthorizationRequest();
    }

    /**
     * Get the client redirect URI.
     */
    protected function getClientRedirectUri(ClientEntityInterface $client): string
    {
        return is_array($client->getRedirectUri())
            ? $client->getRedirectUri()[0]
            : $client->getRedirectUri();
    }
}


================================================
FILE: src/Grant/AbstractGrant.php
================================================
<?php

/**
 * OAuth 2.0 Abstract grant.
 *
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Grant;

use DateInterval;
use DateTimeImmutable;
use DomainException;
use Error;
use Exception;
use League\OAuth2\Server\CryptKeyInterface;
use League\OAuth2\Server\CryptTrait;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use League\OAuth2\Server\Entities\ScopeEntityInterface;
use League\OAuth2\Server\EventEmitting\EmitterAwarePolyfill;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Exception\UniqueTokenIdentifierConstraintViolationException;
use League\OAuth2\Server\RedirectUriValidators\RedirectUriValidator;
use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use League\OAuth2\Server\Repositories\UserRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface;
use League\OAuth2\Server\ResponseTypes\DeviceCodeResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use TypeError;

use function array_filter;
use function array_key_exists;
use function base64_decode;
use function bin2hex;
use function explode;
use function is_string;
use function random_bytes;
use function substr;
use function trim;

/**
 * Abstract grant class.
 */
abstract class AbstractGrant implements GrantTypeInterface
{
    use EmitterAwarePolyfill;
    use CryptTrait;

    protected const SCOPE_DELIMITER_STRING = ' ';

    protected const MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS = 10;

    protected ClientRepositoryInterface $clientRepository;

    protected AccessTokenRepositoryInterface $accessTokenRepository;

    protected ScopeRepositoryInterface $scopeRepository;

    protected AuthCodeRepositoryInterface $authCodeRepository;

    protected RefreshTokenRepositoryInterface $refreshTokenRepository;

    protected UserRepositoryInterface $userRepository;

    protected DateInterval $refreshTokenTTL;

    protected CryptKeyInterface $privateKey;

    protected string $defaultScope;

    protected bool $revokeRefreshTokens = true;

    public function setClientRepository(ClientRepositoryInterface $clientRepository): void
    {
        $this->clientRepository = $clientRepository;
    }

    public function setAccessTokenRepository(AccessTokenRepositoryInterface $accessTokenRepository): void
    {
        $this->accessTokenRepository = $accessTokenRepository;
    }

    public function setScopeRepository(ScopeRepositoryInterface $scopeRepository): void
    {
        $this->scopeRepository = $scopeRepository;
    }

    public function setRefreshTokenRepository(RefreshTokenRepositoryInterface $refreshTokenRepository): void
    {
        $this->refreshTokenRepository = $refreshTokenRepository;
    }

    public function setAuthCodeRepository(AuthCodeRepositoryInterface $authCodeRepository): void
    {
        $this->authCodeRepository = $authCodeRepository;
    }

    public function setUserRepository(UserRepositoryInterface $userRepository): void
    {
        $this->userRepository = $userRepository;
    }

    /**
     * {@inheritdoc}
     */
    public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void
    {
        $this->refreshTokenTTL = $refreshTokenTTL;
    }

    /**
     * Set the private key
     */
    public function setPrivateKey(CryptKeyInterface $privateKey): void
    {
        $this->privateKey = $privateKey;
    }

    public function setDefaultScope(string $scope): void
    {
        $this->defaultScope = $scope;
    }

    public function revokeRefreshTokens(bool $willRevoke): void
    {
        $this->revokeRefreshTokens = $willRevoke;
    }

    /**
     * Validate the client.
     *
     * @throws OAuthServerException
     */
    protected function validateClient(ServerRequestInterface $request): ClientEntityInterface
    {
        [$clientId, $clientSecret] = $this->getClientCredentials($request);

        $client = $this->getClientEntityOrFail($clientId, $request);

        if ($client->isConfidential()) {
            if ($clientSecret === '') {
                throw OAuthServerException::invalidRequest('client_secret');
            }

            if ($this->clientRepository->validateClient($clientId, $clientSecret, $this->getIdentifier()) === false) {
                $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));

                throw OAuthServerException::invalidClient($request);
            }
        }

        return $client;
    }

    /**
     * Wrapper around ClientRepository::getClientEntity() that ensures we emit
     * an event and throw an exception if the repo doesn't return a client
     * entity.
     *
     * This is a bit of defensive coding because the interface contract
     * doesn't actually enforce non-null returns/exception-on-no-client so
     * getClientEntity might return null. By contrast, this method will
     * always either return a ClientEntityInterface or throw.
     *
     * @throws OAuthServerException
     */
    protected function getClientEntityOrFail(string $clientId, ServerRequestInterface $request): ClientEntityInterface
    {
        $client = $this->clientRepository->getClientEntity($clientId);

        if ($client instanceof ClientEntityInterface === false) {
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
            throw OAuthServerException::invalidClient($request);
        }

        if ($this->supportsGrantType($client, $this->getIdentifier()) === false) {
            throw OAuthServerException::unauthorizedClient();
        }

        return $client;
    }

    /**
     * Returns true if the given client is authorized to use the given grant type.
     */
    protected function supportsGrantType(ClientEntityInterface $client, string $grantType): bool
    {
        return method_exists($client, 'supportsGrantType') === false
            || $client->supportsGrantType($grantType) === true;
    }

    /**
     * Gets the client credentials from the request from the request body or
     * the Http Basic Authorization header
     *
     * @return array{0:non-empty-string,1:string}
     *
     * @throws OAuthServerException
     */
    protected function getClientCredentials(ServerRequestInterface $request): array
    {
        [$basicAuthUser, $basicAuthPassword] = $this->getBasicAuthCredentials($request);

        $clientId = $this->getRequestParameter('client_id', $request, $basicAuthUser);

        if ($clientId === null) {
            throw OAuthServerException::invalidRequest('client_id');
        }

        $clientSecret = $this->getRequestParameter('client_secret', $request, $basicAuthPassword);

        return [$clientId, $clientSecret ?? ''];
    }

    /**
     * Validate redirectUri from the request. If a redirect URI is provided
     * ensure it matches what is pre-registered
     *
     * @throws OAuthServerException
     */
    protected function validateRedirectUri(
        string $redirectUri,
        ClientEntityInterface $client,
        ServerRequestInterface $request
    ): void {
        $validator = new RedirectUriValidator($client->getRedirectUri());

        if (!$validator->validateRedirectUri($redirectUri)) {
            $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request));
            throw OAuthServerException::invalidClient($request);
        }
    }

    /**
     * Validate scopes in the request.
     *
     * @param null|string|string[] $scopes
     *
     * @throws OAuthServerException
     *
     * @return ScopeEntityInterface[]
     */
    public function validateScopes(string|array|null $scopes, ?string $redirectUri = null): array
    {
        if ($scopes === null) {
            $scopes = [];
        } elseif (is_string($scopes)) {
            $scopes = $this->convertScopesQueryStringToArray($scopes);
        }

        $validScopes = [];

        foreach ($scopes as $scopeItem) {
            $scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeItem);

            if ($scope instanceof ScopeEntityInterface === false) {
                throw OAuthServerException::invalidScope($scopeItem, $redirectUri);
            }

            $validScopes[] = $scope;
        }

        return $validScopes;
    }

    /**
     * Converts a scopes query string to an array to easily iterate for validation.
     *
     * @return string[]
     */
    private function convertScopesQueryStringToArray(string $scopes): array
    {
        return array_filter(explode(self::SCOPE_DELIMITER_STRING, trim($scopes)), static fn ($scope) => $scope !== '');
    }

    /**
     * Parse request parameter.
     *
     * @param array<array-key, mixed> $request
     *
     * @return non-empty-string|null
     *
     * @throws OAuthServerException
     */
    private static function parseParam(string $parameter, array $request, ?string $default = null): ?string
    {
        $value = $request[$parameter] ?? '';

        if (is_scalar($value)) {
            $value = trim((string) $value);
        } else {
            throw OAuthServerException::invalidRequest($parameter);
        }

        if ($value === '') {
            $value = $default === null ? null : trim($default);

            if ($value === '') {
                $value = null;
            }
        }

        return $value;
    }

    /**
     * Retrieve request parameter.
     *
     * @return non-empty-string|null
     *
     * @throws OAuthServerException
     */
    protected function getRequestParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
    {
        return self::parseParam($parameter, (array) $request->getParsedBody(), $default);
    }

    /**
     * Retrieve HTTP Basic Auth credentials with the Authorization header
     * of a request. First index of the returned array is the username,
     * second is the password (so list() will work). If the header does
     * not exist, or is otherwise an invalid HTTP Basic header, return
     * [null, null].
     *
     * @return array{0:non-empty-string,1:string}|array{0:null,1:null}
     */
    protected function getBasicAuthCredentials(ServerRequestInterface $request): array
    {
        if (!$request->hasHeader('Authorization')) {
            return [null, null];
        }

        $header = $request->getHeader('Authorization')[0];
        if (stripos($header, 'Basic ') !== 0) {
            return [null, null];
        }

        $decoded = base64_decode(substr($header, 6), true);

        if ($decoded === false) {
            return [null, null];
        }

        if (str_contains($decoded, ':') === false) {
            return [null, null]; // HTTP Basic header without colon isn't valid
        }

        [$username, $password] = explode(':', $decoded, 2);

        if ($username === '') {
            return [null, null];
        }

        return [$username, $password];
    }

    /**
     * Retrieve query string parameter.
     *
     * @return non-empty-string|null
     *
     * @throws OAuthServerException
     */
    protected function getQueryStringParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
    {
        return self::parseParam($parameter, $request->getQueryParams(), $default);
    }

    /**
     * Retrieve cookie parameter.
     *
     * @return non-empty-string|null
     *
     * @throws OAuthServerException
     */
    protected function getCookieParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
    {
        return self::parseParam($parameter, $request->getCookieParams(), $default);
    }

    /**
     * Retrieve server parameter.
     *
     * @return non-empty-string|null
     *
     * @throws OAuthServerException
     */
    protected function getServerParameter(string $parameter, ServerRequestInterface $request, ?string $default = null): ?string
    {
        return self::parseParam($parameter, $request->getServerParams(), $default);
    }

    /**
     * Issue an access token.
     *
     * @param ScopeEntityInterface[] $scopes
     *
     * @throws OAuthServerException
     * @throws UniqueTokenIdentifierConstraintViolationException
     */
    protected function issueAccessToken(
        DateInterval $accessTokenTTL,
        ClientEntityInterface $client,
        string|null $userIdentifier,
        array $scopes = []
    ): AccessTokenEntityInterface {
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;

        $accessToken = $this->accessTokenRepository->getNewToken($client, $scopes, $userIdentifier);
        $accessToken->setExpiryDateTime((new DateTimeImmutable())->add($accessTokenTTL));
        $accessToken->setPrivateKey($this->privateKey);

        while ($maxGenerationAttempts-- > 0) {
            $accessToken->setIdentifier($this->generateUniqueIdentifier());
            try {
                $this->accessTokenRepository->persistNewAccessToken($accessToken);

                return $accessToken;
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
                if ($maxGenerationAttempts === 0) {
                    throw $e;
                }
            }
        }

        // This should never be hit. It is here to work around a PHPStan false error
        return $accessToken;
    }

    /**
     * Issue an auth code.
     *
     * @param non-empty-string       $userIdentifier
     * @param ScopeEntityInterface[] $scopes
     *
     * @throws OAuthServerException
     * @throws UniqueTokenIdentifierConstraintViolationException
     */
    protected function issueAuthCode(
        DateInterval $authCodeTTL,
        ClientEntityInterface $client,
        string $userIdentifier,
        ?string $redirectUri,
        array $scopes = []
    ): AuthCodeEntityInterface {
        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;

        $authCode = $this->authCodeRepository->getNewAuthCode();
        $authCode->setExpiryDateTime((new DateTimeImmutable())->add($authCodeTTL));
        $authCode->setClient($client);
        $authCode->setUserIdentifier($userIdentifier);

        if ($redirectUri !== null) {
            $authCode->setRedirectUri($redirectUri);
        }

        foreach ($scopes as $scope) {
            $authCode->addScope($scope);
        }

        while ($maxGenerationAttempts-- > 0) {
            $authCode->setIdentifier($this->generateUniqueIdentifier());
            try {
                $this->authCodeRepository->persistNewAuthCode($authCode);

                return $authCode;
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
                if ($maxGenerationAttempts === 0) {
                    throw $e;
                }
            }
        }

        // This should never be hit. It is here to work around a PHPStan false error
        return $authCode;
    }

    /**
     * @throws OAuthServerException
     * @throws UniqueTokenIdentifierConstraintViolationException
     */
    protected function issueRefreshToken(AccessTokenEntityInterface $accessToken): ?RefreshTokenEntityInterface
    {
        if ($this->supportsGrantType($accessToken->getClient(), 'refresh_token') === false) {
            return null;
        }

        $refreshToken = $this->refreshTokenRepository->getNewRefreshToken();

        if ($refreshToken === null) {
            return null;
        }

        $refreshToken->setExpiryDateTime((new DateTimeImmutable())->add($this->refreshTokenTTL));
        $refreshToken->setAccessToken($accessToken);

        $maxGenerationAttempts = self::MAX_RANDOM_TOKEN_GENERATION_ATTEMPTS;

        while ($maxGenerationAttempts-- > 0) {
            $refreshToken->setIdentifier($this->generateUniqueIdentifier());
            try {
                $this->refreshTokenRepository->persistNewRefreshToken($refreshToken);

                return $refreshToken;
            } catch (UniqueTokenIdentifierConstraintViolationException $e) {
                if ($maxGenerationAttempts === 0) {
                    throw $e;
                }
            }
        }

        // This should never be hit. It is here to work around a PHPStan false error
        return $refreshToken;
    }

    /**
     * Generate a new unique identifier.
     *
     * @return non-empty-string
     *
     * @throws OAuthServerException
     */
    protected function generateUniqueIdentifier(int $length = 40): string
    {
        try {
            if ($length < 1) {
                throw new DomainException('Length must be a positive integer');
            }

            return bin2hex(random_bytes($length));
            // @codeCoverageIgnoreStart
        } catch (TypeError | Error $e) {
            throw OAuthServerException::serverError('An unexpected error has occurred', $e);
        } catch (Exception $e) {
            // If you get this message, the CSPRNG failed hard.
            throw OAuthServerException::serverError('Could not generate a random string', $e);
        }
        // @codeCoverageIgnoreEnd
    }

    /**
     * {@inheritdoc}
     */
    public function canRespondToAccessTokenRequest(ServerRequestInterface $request): bool
    {
        $requestParameters = (array) $request->getParsedBody();

        return (
            array_key_exists('grant_type', $requestParameters)
            && $requestParameters['grant_type'] === $this->getIdentifier()
        );
    }

    /**
     * {@inheritdoc}
     */
    public function canRespondToAuthorizationRequest(ServerRequestInterface $request): bool
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function validateAuthorizationRequest(ServerRequestInterface $request): AuthorizationRequestInterface
    {
        throw new LogicException('This grant cannot validate an authorization request');
    }

    /**
     * {@inheritdoc}
     */
    public function completeAuthorizationRequest(AuthorizationRequestInterface $authorizationRequest): ResponseTypeInterface
    {
        throw new LogicException('This grant cannot complete an authorization request');
    }

    /**
     * {@inheritdoc}
     */
    public function canRespondToDeviceAuthorizationRequest(ServerRequestInterface $request): bool
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function respondToDeviceAuthorizationRequest(ServerRequestInterface $request): DeviceCodeResponse
    {
        throw new LogicException('This grant cannot validate a device authorization request');
    }

    /**
     * {@inheritdoc}
     */
    public function completeDeviceAuthorizationRequest(string $deviceCode, string $userId, bool $userApproved): void
    {
        throw new LogicException('This grant cannot complete a device authorization request');
    }

    /**
     * {@inheritdoc}
     */
    public function setIntervalVisibility(bool $intervalVisibility): void
    {
        throw new LogicException('This grant does not support the interval parameter');
    }

    /**
     * {@inheritdoc}
     */
    public function getIntervalVisibility(): bool
    {
        return false;
    }

    /**
     * {@inheritdoc}
     */
    public function setIncludeVerificationUriComplete(bool $includeVerificationUriComplete): void
    {
        throw new LogicException('This grant does not support the verification_uri_complete parameter');
    }
}


================================================
FILE: src/Grant/AuthCodeGrant.php
================================================
<?php

/**
 * @author      Alex Bilbie <hello@alexbilbie.com>
 * @copyright   Copyright (c) Alex Bilbie
 * @license     http://mit-license.org/
 *
 * @link        https://github.com/thephpleague/oauth2-server
 */

declare(strict_types=1);

namespace League\OAuth2\Server\Grant;

use DateInterval;
use DateTimeImmutable;
use Exception;
use InvalidArgumentException;
use League\OAuth2\Server\CodeChallengeVerifiers\CodeChallengeVerifierInterface;
use League\OAuth2\Server\CodeChallengeVerifiers\PlainVerifier;
use League\OAuth2\Server\CodeChallengeVerifiers\S256Verifier;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\RequestAccessTokenEvent;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\RequestRefreshTokenEvent;
use League\OAuth2\Server\RequestTypes\AuthorizationRequestInterface;
use League\OAuth2\Server\ResponseTypes\RedirectResponse;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use LogicException;
use Psr\Http\Message\ServerRequestInterface;
use stdClass;

use function array_key_exists;
use function array_keys;
use function array_map;
use function count;
use function hash_algos;
use function implode;
use function in_array;
use function is_array;
use function json_decode;
use function json_encode;
use function preg_match;
use function property_exists;
use function sprintf;
use function time;

class AuthCodeGrant extends AbstractAuthorizeGrant
{
    private bool $requireCodeChallengeForPublicClients = true;

    /**
     * @var CodeChallengeVerifierInterface[]
     */
    private array $codeChallengeVerifiers = [];

    /**
     * @throws Exception
     */
    public function __construct(
        AuthCodeRepositoryInterface $authCodeRepository,
        RefreshTokenRepositoryInterface $refreshTokenRepository,
        private DateInterval $authCodeTTL
    ) {
        $this->setAuthCodeRepository($authCodeRepository);
        $this->setRefreshTokenRepository($refreshTokenRepository);
        $this->refreshTokenTTL = new DateInterval('P1M');

        if (in_array('sha256', hash_algos(), true)) {
            $s256Verifier = new S256Verifier();
            $this->codeChallengeVerifiers[$s256Verifier->getMethod()] = $s256Verifier;
        }

        $plainVerifier = new PlainVerifier();
        $this->codeChallengeVerifiers[$plainVerifier->getMethod()] = $plainVerifier;
    }

    /**
     * Disable the requirement for a code challenge for public clients.
     */
    public function disableRequireCodeChallengeForPublicClients(): void
    {
        $this->requireCodeChallengeForPublicClients = false;
    }

    /**
     * Respond to an access token request.
     *
     * @throws OAuthServerException
     */
    public function respondToAccessTokenRequest(
        ServerRequestInterface $request,
        ResponseTypeInterface $responseType,
        DateInterval $accessTokenTTL
    ): ResponseTypeInterface {
        $client = $this->validateClient($request);

        $encryptedAuthCode = $this->getRequestParameter('code', $request);

        if ($encryptedAuthCode === null) {
            throw OAuthServerException::invalidRequest('code');
        }

        try {
            $authCodePayload = json_decode($this->decrypt($encryptedAuthCode));

            $this->validateAuthorizationCode($authCodePayload, $client, $request);

            $scopes = $this->scopeRepository->finalizeScopes(
                $this->validateScopes($authCodePayload->scopes),
                $this->getIdentifier(),
                $client,
                $authCodePayload->user_id,
                $authCodePayload->auth_code_id
            );
        } catch (InvalidArgumentException $e) {
            throw OAuthServerException::invalidGrant('Cannot validate the provided authorization code');
        } catch (LogicException $e) {
            throw OAuthServerException::invalidRequest('code', 'Issue decrypting the authorization code', $e);
        }

        $codeVerifier = $this->getRequestParameter('code_verifier', $request);

        // If a code challenge isn't present but a code verifier is, reject the request to block PKCE downgrade attack
        if (!isset($authCodePayload->code_challenge) && $codeVerifier !== null) {
            throw OAuthServerException::invalidRequest(
                'code_challenge',
                'code_verifier received when no code_challenge is present'
            );
        }

        if (isset($authCodePayload->code_challenge)) {
            $this->validateCodeChallenge($authCodePayload, $codeVerifier);
        }

        // Issue and persist new access token
        $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes);
        $this->getEmitter()->emit(new RequestAccessTokenEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request, $accessToken));
        $responseType->setAccessToken($accessToken);

        // Issue and persist new refresh token if given
        $refreshToken = $this->issueRefreshToken($accessToken);

        if ($refreshToken !== null) {
            $this->getEmitter()->emit(new RequestRefreshTokenEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request, $refreshToken));
            $responseType->setRefreshToken($refreshToken);
        }

        // Revoke used auth code
        $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id);

        return $responseType;
    }

    private function validateCodeChallenge(object $authCodePayload, ?string $codeVerifier): void
    {
        if ($codeVerifier === null) {
            throw OAuthServerException::invalidRequest('code_verifier');
        }

        // Validate code_verifier according to RFC-7636
        // @see: https://tools.ietf.org/html/rfc7636#section-4.1
        if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) {
            throw OAuthServerException::invalidRequest(
                'code_verifier',
                'Code Verifier must follow the specifications of RFC-7636.'
            );
        }

        if (property_exists($authCodePayload, 'code_challenge_method')) {
            if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) {
                $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method];

                if (
                    !property_exists($authCodePayload, 'code_challenge') ||
                    !isset($authCodePayload->code_challenge) ||
                    $codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false
                ) {
                    throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.');
                }
            } else {
                throw OAuthServerException::serverError(
                    sprintf(
                        'Unsupported code challenge method `%s`',
                        $authCodePayload->code_challenge_method
                    )
                );
            }
        }
    }

    /**
     * Validate the authorization code.
     */
    private function validateAuthorizationCode(
        stdClass $authCodePayload,
        ClientEntityInterface $client,
        ServerRequestInterface $request
    ): void {
        if (!property_exists($authCodePayload, 'auth_code_id')) {
            throw OAuthServerException::invalidRequest('code', 'Authorization code malformed');
        }

        if (time() > $authCodePayload->expire_time) {
            throw OAuthServerException::invalidGrant('Authorization code has expired');
        }

        if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) {
            throw OAuthServerException::invalidGrant('Authorization code has been revoked');
        }

        if ($authCodePayload->client_id !== $client->getIdentifier()) {
            throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client');
        }

        // The redirect URI is required in this request if it was specified
        // in the authorization request
        $redirectUri = $this->getRequestParameter('redirect_uri', $request);
        if ($authCodePayload->redirect_uri !== null && $redirectUri === null) {
          
Download .txt
gitextract_nrzlm6b4/

├── .gitattributes
├── .github/
│   ├── FUNDING.yml
│   ├── dependabot.yml
│   └── workflows/
│       ├── backwards-compatibility.yml
│       ├── coding-standards.yml
│       ├── static-analysis.yml
│       └── tests.yml
├── .gitignore
├── .scrutinizer.yml
├── .styleci.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── examples/
│   ├── README.md
│   ├── composer.json
│   ├── public/
│   │   ├── api.php
│   │   ├── auth_code.php
│   │   ├── client_credentials.php
│   │   ├── device_code.php
│   │   ├── implicit.php
│   │   ├── middleware_use.php
│   │   ├── password.php
│   │   └── refresh_token.php
│   └── src/
│       ├── Entities/
│       │   ├── AccessTokenEntity.php
│       │   ├── AuthCodeEntity.php
│       │   ├── ClientEntity.php
│       │   ├── DeviceCodeEntity.php
│       │   ├── RefreshTokenEntity.php
│       │   ├── ScopeEntity.php
│       │   └── UserEntity.php
│       └── Repositories/
│           ├── AccessTokenRepository.php
│           ├── AuthCodeRepository.php
│           ├── ClientRepository.php
│           ├── DeviceCodeRepository.php
│           ├── RefreshTokenRepository.php
│           ├── ScopeRepository.php
│           └── UserRepository.php
├── phpcs.xml.dist
├── phpstan.neon.dist
├── phpunit.xml.dist
├── src/
│   ├── AuthorizationServer.php
│   ├── AuthorizationValidators/
│   │   ├── AuthorizationValidatorInterface.php
│   │   └── BearerTokenValidator.php
│   ├── CodeChallengeVerifiers/
│   │   ├── CodeChallengeVerifierInterface.php
│   │   ├── PlainVerifier.php
│   │   └── S256Verifier.php
│   ├── CryptKey.php
│   ├── CryptKeyInterface.php
│   ├── CryptTrait.php
│   ├── Entities/
│   │   ├── AccessTokenEntityInterface.php
│   │   ├── AuthCodeEntityInterface.php
│   │   ├── ClientEntityInterface.php
│   │   ├── DeviceCodeEntityInterface.php
│   │   ├── RefreshTokenEntityInterface.php
│   │   ├── ScopeEntityInterface.php
│   │   ├── TokenInterface.php
│   │   ├── Traits/
│   │   │   ├── AccessTokenTrait.php
│   │   │   ├── AuthCodeTrait.php
│   │   │   ├── ClientTrait.php
│   │   │   ├── DeviceCodeTrait.php
│   │   │   ├── EntityTrait.php
│   │   │   ├── RefreshTokenTrait.php
│   │   │   ├── ScopeTrait.php
│   │   │   └── TokenEntityTrait.php
│   │   └── UserEntityInterface.php
│   ├── EventEmitting/
│   │   ├── AbstractEvent.php
│   │   ├── EmitterAwareInterface.php
│   │   ├── EmitterAwarePolyfill.php
│   │   └── EventEmitter.php
│   ├── Exception/
│   │   ├── OAuthServerException.php
│   │   └── UniqueTokenIdentifierConstraintViolationException.php
│   ├── Grant/
│   │   ├── AbstractAuthorizeGrant.php
│   │   ├── AbstractGrant.php
│   │   ├── AuthCodeGrant.php
│   │   ├── ClientCredentialsGrant.php
│   │   ├── DeviceCodeGrant.php
│   │   ├── GrantTypeInterface.php
│   │   ├── ImplicitGrant.php
│   │   ├── PasswordGrant.php
│   │   └── RefreshTokenGrant.php
│   ├── Middleware/
│   │   ├── AuthorizationServerMiddleware.php
│   │   └── ResourceServerMiddleware.php
│   ├── RedirectUriValidators/
│   │   ├── RedirectUriValidator.php
│   │   └── RedirectUriValidatorInterface.php
│   ├── Repositories/
│   │   ├── AccessTokenRepositoryInterface.php
│   │   ├── AuthCodeRepositoryInterface.php
│   │   ├── ClientRepositoryInterface.php
│   │   ├── DeviceCodeRepositoryInterface.php
│   │   ├── RefreshTokenRepositoryInterface.php
│   │   ├── RepositoryInterface.php
│   │   ├── ScopeRepositoryInterface.php
│   │   └── UserRepositoryInterface.php
│   ├── RequestAccessTokenEvent.php
│   ├── RequestEvent.php
│   ├── RequestRefreshTokenEvent.php
│   ├── RequestTypes/
│   │   ├── AuthorizationRequest.php
│   │   └── AuthorizationRequestInterface.php
│   ├── ResourceServer.php
│   └── ResponseTypes/
│       ├── AbstractResponseType.php
│       ├── BearerTokenResponse.php
│       ├── DeviceCodeResponse.php
│       ├── RedirectResponse.php
│       └── ResponseTypeInterface.php
└── tests/
    ├── AuthorizationServerTest.php
    ├── AuthorizationValidators/
    │   └── BearerTokenValidatorTest.php
    ├── CodeChallengeVerifiers/
    │   ├── PlainVerifierTest.php
    │   └── S256VerifierTest.php
    ├── EventEmitting/
    │   └── EmitterAwarePolyfillTest.php
    ├── Exception/
    │   └── OAuthServerExceptionTest.php
    ├── Grant/
    │   ├── AbstractGrantTest.php
    │   ├── AuthCodeGrantTest.php
    │   ├── ClientCredentialsGrantTest.php
    │   ├── DeviceCodeGrantTest.php
    │   ├── ImplicitGrantTest.php
    │   ├── PasswordGrantTest.php
    │   └── RefreshTokenGrantTest.php
    ├── Middleware/
    │   ├── AuthorizationServerMiddlewareTest.php
    │   └── ResourceServerMiddlewareTest.php
    ├── PHPStan/
    │   └── AbstractGrantExtension.php
    ├── RedirectUriValidators/
    │   └── RedirectUriValidatorTest.php
    ├── ResourceServerTest.php
    ├── ResponseTypes/
    │   ├── BearerResponseTypeTest.php
    │   ├── BearerTokenResponseWithParams.php
    │   └── DeviceCodeResponseTypeTest.php
    ├── Stubs/
    │   ├── .gitattributes
    │   ├── AccessTokenEntity.php
    │   ├── AuthCodeEntity.php
    │   ├── ClientEntity.php
    │   ├── CryptTraitStub.php
    │   ├── DeviceCodeEntity.php
    │   ├── GrantType.php
    │   ├── RefreshTokenEntity.php
    │   ├── ScopeEntity.php
    │   ├── StubResponseType.php
    │   ├── UserEntity.php
    │   ├── private.key
    │   ├── private.key.crlf
    │   └── public.key
    └── Utils/
        ├── CryptKeyTest.php
        └── CryptTraitTest.php
Download .txt
SYMBOL INDEX (739 symbols across 110 files)

FILE: examples/src/Entities/AccessTokenEntity.php
  class AccessTokenEntity (line 20) | class AccessTokenEntity implements AccessTokenEntityInterface

FILE: examples/src/Entities/AuthCodeEntity.php
  class AuthCodeEntity (line 20) | class AuthCodeEntity implements AuthCodeEntityInterface

FILE: examples/src/Entities/ClientEntity.php
  class ClientEntity (line 19) | class ClientEntity implements ClientEntityInterface
    method setName (line 24) | public function setName(string $name): void
    method setRedirectUri (line 29) | public function setRedirectUri(string $uri): void
    method setConfidential (line 34) | public function setConfidential(): void

FILE: examples/src/Entities/DeviceCodeEntity.php
  class DeviceCodeEntity (line 20) | class DeviceCodeEntity implements DeviceCodeEntityInterface

FILE: examples/src/Entities/RefreshTokenEntity.php
  class RefreshTokenEntity (line 19) | class RefreshTokenEntity implements RefreshTokenEntityInterface

FILE: examples/src/Entities/ScopeEntity.php
  class ScopeEntity (line 19) | class ScopeEntity implements ScopeEntityInterface

FILE: examples/src/Entities/UserEntity.php
  class UserEntity (line 17) | class UserEntity implements UserEntityInterface
    method getIdentifier (line 22) | public function getIdentifier(): string

FILE: examples/src/Repositories/AccessTokenRepository.php
  class AccessTokenRepository (line 20) | class AccessTokenRepository implements AccessTokenRepositoryInterface
    method persistNewAccessToken (line 25) | public function persistNewAccessToken(AccessTokenEntityInterface $acce...
    method revokeAccessToken (line 33) | public function revokeAccessToken($tokenId): void
    method isAccessTokenRevoked (line 41) | public function isAccessTokenRevoked($tokenId): bool
    method getNewToken (line 49) | public function getNewToken(ClientEntityInterface $clientEntity, array...

FILE: examples/src/Repositories/AuthCodeRepository.php
  class AuthCodeRepository (line 19) | class AuthCodeRepository implements AuthCodeRepositoryInterface
    method persistNewAuthCode (line 24) | public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEn...
    method revokeAuthCode (line 32) | public function revokeAuthCode($codeId): void
    method isAuthCodeRevoked (line 40) | public function isAuthCodeRevoked($codeId): bool
    method getNewAuthCode (line 48) | public function getNewAuthCode(): AuthCodeEntityInterface

FILE: examples/src/Repositories/ClientRepository.php
  class ClientRepository (line 23) | class ClientRepository implements ClientRepositoryInterface
    method getClientEntity (line 31) | public function getClientEntity(string $clientIdentifier): ?ClientEnti...
    method validateClient (line 46) | public function validateClient($clientIdentifier, $clientSecret, $gran...

FILE: examples/src/Repositories/DeviceCodeRepository.php
  class DeviceCodeRepository (line 22) | class DeviceCodeRepository implements DeviceCodeRepositoryInterface
    method getNewDeviceCode (line 27) | public function getNewDeviceCode(): DeviceCodeEntityInterface
    method persistDeviceCode (line 35) | public function persistDeviceCode(DeviceCodeEntityInterface $deviceCod...
    method getDeviceCodeEntityByDeviceCode (line 43) | public function getDeviceCodeEntityByDeviceCode($deviceCode): ?DeviceC...
    method revokeDeviceCode (line 73) | public function revokeDeviceCode($codeId): void
    method isDeviceCodeRevoked (line 81) | public function isDeviceCodeRevoked($codeId): bool

FILE: examples/src/Repositories/RefreshTokenRepository.php
  class RefreshTokenRepository (line 19) | class RefreshTokenRepository implements RefreshTokenRepositoryInterface
    method persistNewRefreshToken (line 24) | public function persistNewRefreshToken(RefreshTokenEntityInterface $re...
    method revokeRefreshToken (line 32) | public function revokeRefreshToken($tokenId): void
    method isRefreshTokenRevoked (line 40) | public function isRefreshTokenRevoked($tokenId): bool
    method getNewRefreshToken (line 48) | public function getNewRefreshToken(): ?RefreshTokenEntityInterface

FILE: examples/src/Repositories/ScopeRepository.php
  class ScopeRepository (line 22) | class ScopeRepository implements ScopeRepositoryInterface
    method getScopeEntityByIdentifier (line 27) | public function getScopeEntityByIdentifier(string $identifier): ?Scope...
    method finalizeScopes (line 51) | public function finalizeScopes(

FILE: examples/src/Repositories/UserRepository.php
  class UserRepository (line 20) | class UserRepository implements UserRepositoryInterface
    method getUserEntityByUserCredentials (line 25) | public function getUserEntityByUserCredentials(

FILE: src/AuthorizationServer.php
  class AuthorizationServer (line 32) | class AuthorizationServer implements EmitterAwareInterface
    method __construct (line 61) | public function __construct(
    method enableGrantType (line 90) | public function enableGrantType(GrantTypeInterface $grantType, DateInt...
    method validateAuthorizationRequest (line 114) | public function validateAuthorizationRequest(ServerRequestInterface $r...
    method completeAuthorizationRequest (line 128) | public function completeAuthorizationRequest(
    method respondToDeviceAuthorizationRequest (line 142) | public function respondToDeviceAuthorizationRequest(ServerRequestInter...
    method completeDeviceAuthorizationRequest (line 158) | public function completeDeviceAuthorizationRequest(string $deviceCode,...
    method respondToAccessTokenRequest (line 169) | public function respondToAccessTokenRequest(ServerRequestInterface $re...
    method getResponseType (line 191) | protected function getResponseType(): ResponseTypeInterface
    method setDefaultScope (line 207) | public function setDefaultScope(string $defaultScope): void
    method revokeRefreshTokens (line 215) | public function revokeRefreshTokens(bool $revokeRefreshTokens): void

FILE: src/AuthorizationValidators/AuthorizationValidatorInterface.php
  type AuthorizationValidatorInterface (line 17) | interface AuthorizationValidatorInterface
    method validateAuthorization (line 23) | public function validateAuthorization(ServerRequestInterface $request)...

FILE: src/AuthorizationValidators/BearerTokenValidator.php
  class BearerTokenValidator (line 37) | class BearerTokenValidator implements AuthorizationValidatorInterface
    method __construct (line 45) | public function __construct(private AccessTokenRepositoryInterface $ac...
    method setPublicKey (line 52) | public function setPublicKey(CryptKeyInterface $key): void
    method initJwtConfiguration (line 62) | private function initJwtConfiguration(): void
    method validateAuthorization (line 90) | public function validateAuthorization(ServerRequestInterface $request)...

FILE: src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php
  type CodeChallengeVerifierInterface (line 15) | interface CodeChallengeVerifierInterface
    method getMethod (line 20) | public function getMethod(): string;
    method verifyCodeChallenge (line 25) | public function verifyCodeChallenge(string $codeVerifier, string $code...

FILE: src/CodeChallengeVerifiers/PlainVerifier.php
  class PlainVerifier (line 17) | class PlainVerifier implements CodeChallengeVerifierInterface
    method getMethod (line 22) | public function getMethod(): string
    method verifyCodeChallenge (line 30) | public function verifyCodeChallenge(string $codeVerifier, string $code...

FILE: src/CodeChallengeVerifiers/S256Verifier.php
  class S256Verifier (line 21) | class S256Verifier implements CodeChallengeVerifierInterface
    method getMethod (line 26) | public function getMethod(): string
    method verifyCodeChallenge (line 34) | public function verifyCodeChallenge(string $codeVerifier, string $code...

FILE: src/CryptKey.php
  class CryptKey (line 33) | class CryptKey implements CryptKeyInterface
    method __construct (line 44) | public function __construct(
    method getKeyContents (line 99) | public function getKeyContents(): string
    method isValidKey (line 107) | private function isValidKey(
    method getKeyPath (line 133) | public function getKeyPath(): string
    method getPassPhrase (line 141) | public function getPassPhrase(): ?string

FILE: src/CryptKeyInterface.php
  type CryptKeyInterface (line 7) | interface CryptKeyInterface
    method getKeyPath (line 12) | public function getKeyPath(): string;
    method getPassPhrase (line 17) | public function getPassPhrase(): ?string;
    method getKeyContents (line 24) | public function getKeyContents(): string;

FILE: src/CryptTrait.php
  type CryptTrait (line 28) | trait CryptTrait
    method encrypt (line 37) | protected function encrypt(string $unencryptedData): string
    method decrypt (line 59) | protected function decrypt(string $encryptedData): string
    method setEncryptionKey (line 87) | public function setEncryptionKey(

FILE: src/Entities/AccessTokenEntityInterface.php
  type AccessTokenEntityInterface (line 17) | interface AccessTokenEntityInterface extends TokenInterface
    method setPrivateKey (line 22) | public function setPrivateKey(CryptKeyInterface $privateKey): void;
    method toString (line 27) | public function toString(): string;

FILE: src/Entities/AuthCodeEntityInterface.php
  type AuthCodeEntityInterface (line 15) | interface AuthCodeEntityInterface extends TokenInterface
    method getRedirectUri (line 17) | public function getRedirectUri(): string|null;
    method setRedirectUri (line 19) | public function setRedirectUri(string $uri): void;

FILE: src/Entities/ClientEntityInterface.php
  type ClientEntityInterface (line 15) | interface ClientEntityInterface
    method getIdentifier (line 22) | public function getIdentifier(): string;
    method getName (line 27) | public function getName(): string;
    method getRedirectUri (line 35) | public function getRedirectUri(): string|array;
    method isConfidential (line 40) | public function isConfidential(): bool;

FILE: src/Entities/DeviceCodeEntityInterface.php
  type DeviceCodeEntityInterface (line 17) | interface DeviceCodeEntityInterface extends TokenInterface
    method getUserCode (line 19) | public function getUserCode(): string;
    method setUserCode (line 21) | public function setUserCode(string $userCode): void;
    method getVerificationUri (line 23) | public function getVerificationUri(): string;
    method setVerificationUri (line 25) | public function setVerificationUri(string $verificationUri): void;
    method getVerificationUriComplete (line 27) | public function getVerificationUriComplete(): string;
    method getLastPolledAt (line 29) | public function getLastPolledAt(): ?DateTimeImmutable;
    method setLastPolledAt (line 31) | public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void;
    method getInterval (line 33) | public function getInterval(): int;
    method setInterval (line 35) | public function setInterval(int $interval): void;
    method getUserApproved (line 37) | public function getUserApproved(): bool;
    method setUserApproved (line 39) | public function setUserApproved(bool $userApproved): void;

FILE: src/Entities/RefreshTokenEntityInterface.php
  type RefreshTokenEntityInterface (line 17) | interface RefreshTokenEntityInterface
    method getIdentifier (line 24) | public function getIdentifier(): string;
    method setIdentifier (line 31) | public function setIdentifier(string $identifier): void;
    method getExpiryDateTime (line 36) | public function getExpiryDateTime(): DateTimeImmutable;
    method setExpiryDateTime (line 41) | public function setExpiryDateTime(DateTimeImmutable $dateTime): void;
    method setAccessToken (line 46) | public function setAccessToken(AccessTokenEntityInterface $accessToken...
    method getAccessToken (line 51) | public function getAccessToken(): AccessTokenEntityInterface;

FILE: src/Entities/ScopeEntityInterface.php
  type ScopeEntityInterface (line 17) | interface ScopeEntityInterface extends JsonSerializable
    method getIdentifier (line 24) | public function getIdentifier(): string;

FILE: src/Entities/TokenInterface.php
  type TokenInterface (line 17) | interface TokenInterface
    method getIdentifier (line 24) | public function getIdentifier(): string;
    method setIdentifier (line 31) | public function setIdentifier(string $identifier): void;
    method getExpiryDateTime (line 36) | public function getExpiryDateTime(): DateTimeImmutable;
    method setExpiryDateTime (line 41) | public function setExpiryDateTime(DateTimeImmutable $dateTime): void;
    method setUserIdentifier (line 48) | public function setUserIdentifier(string $identifier): void;
    method getUserIdentifier (line 55) | public function getUserIdentifier(): string|null;
    method getClient (line 60) | public function getClient(): ClientEntityInterface;
    method setClient (line 65) | public function setClient(ClientEntityInterface $client): void;
    method addScope (line 70) | public function addScope(ScopeEntityInterface $scope): void;
    method getScopes (line 77) | public function getScopes(): array;

FILE: src/Entities/Traits/AccessTokenTrait.php
  type AccessTokenTrait (line 26) | trait AccessTokenTrait
    method setPrivateKey (line 35) | public function setPrivateKey(
    method initJwtConfiguration (line 45) | public function initJwtConfiguration(): void
    method convertToJWT (line 63) | private function convertToJWT(): Token
    method toString (line 81) | public function toString(): string
    method getClient (line 86) | abstract public function getClient(): ClientEntityInterface;
    method getExpiryDateTime (line 88) | abstract public function getExpiryDateTime(): DateTimeImmutable;
    method getUserIdentifier (line 93) | abstract public function getUserIdentifier(): string|null;
    method getScopes (line 98) | abstract public function getScopes(): array;
    method getIdentifier (line 103) | abstract public function getIdentifier(): string;
    method getSubjectIdentifier (line 108) | private function getSubjectIdentifier(): string

FILE: src/Entities/Traits/AuthCodeTrait.php
  type AuthCodeTrait (line 15) | trait AuthCodeTrait
    method getRedirectUri (line 19) | public function getRedirectUri(): string|null
    method setRedirectUri (line 24) | public function setRedirectUri(string $uri): void

FILE: src/Entities/Traits/ClientTrait.php
  type ClientTrait (line 15) | trait ClientTrait
    method getName (line 32) | public function getName(): string
    method getRedirectUri (line 43) | public function getRedirectUri(): string|array
    method isConfidential (line 51) | public function isConfidential(): bool
    method supportsGrantType (line 59) | public function supportsGrantType(string $grantType): bool

FILE: src/Entities/Traits/DeviceCodeTrait.php
  type DeviceCodeTrait (line 19) | trait DeviceCodeTrait
    method getUserCode (line 28) | public function getUserCode(): string
    method setUserCode (line 33) | public function setUserCode(string $userCode): void
    method getVerificationUri (line 38) | public function getVerificationUri(): string
    method setVerificationUri (line 43) | public function setVerificationUri(string $verificationUri): void
    method getVerificationUriComplete (line 48) | public function getVerificationUriComplete(): string
    method getClient (line 53) | abstract public function getClient(): ClientEntityInterface;
    method getExpiryDateTime (line 55) | abstract public function getExpiryDateTime(): DateTimeImmutable;
    method getScopes (line 60) | abstract public function getScopes(): array;
    method getIdentifier (line 65) | abstract public function getIdentifier(): string;
    method getLastPolledAt (line 67) | public function getLastPolledAt(): ?DateTimeImmutable
    method setLastPolledAt (line 72) | public function setLastPolledAt(DateTimeImmutable $lastPolledAt): void
    method getInterval (line 77) | public function getInterval(): int
    method setInterval (line 82) | public function setInterval(int $interval): void
    method getUserApproved (line 87) | public function getUserApproved(): bool
    method setUserApproved (line 92) | public function setUserApproved(bool $userApproved): void
    method getVerificationUriCompleteInAuthResponse (line 97) | public function getVerificationUriCompleteInAuthResponse(): bool
    method setVerificationUriCompleteInAuthResponse (line 102) | public function setVerificationUriCompleteInAuthResponse(bool $include...

FILE: src/Entities/Traits/EntityTrait.php
  type EntityTrait (line 15) | trait EntityTrait
    method getIdentifier (line 25) | public function getIdentifier(): string
    method setIdentifier (line 33) | public function setIdentifier(string $identifier): void

FILE: src/Entities/Traits/RefreshTokenTrait.php
  type RefreshTokenTrait (line 18) | trait RefreshTokenTrait
    method setAccessToken (line 27) | public function setAccessToken(AccessTokenEntityInterface $accessToken...
    method getAccessToken (line 35) | public function getAccessToken(): AccessTokenEntityInterface
    method getExpiryDateTime (line 43) | public function getExpiryDateTime(): DateTimeImmutable
    method setExpiryDateTime (line 51) | public function setExpiryDateTime(DateTimeImmutable $dateTime): void

FILE: src/Entities/Traits/ScopeTrait.php
  type ScopeTrait (line 15) | trait ScopeTrait
    method jsonSerialize (line 20) | public function jsonSerialize(): string
    method getIdentifier (line 25) | abstract public function getIdentifier(): string;

FILE: src/Entities/Traits/TokenEntityTrait.php
  type TokenEntityTrait (line 21) | trait TokenEntityTrait
    method addScope (line 40) | public function addScope(ScopeEntityInterface $scope): void
    method getScopes (line 50) | public function getScopes(): array
    method getExpiryDateTime (line 58) | public function getExpiryDateTime(): DateTimeImmutable
    method setExpiryDateTime (line 66) | public function setExpiryDateTime(DateTimeImmutable $dateTime): void
    method setUserIdentifier (line 76) | public function setUserIdentifier(string $identifier): void
    method getUserIdentifier (line 86) | public function getUserIdentifier(): string|null
    method getClient (line 94) | public function getClient(): ClientEntityInterface
    method setClient (line 102) | public function setClient(ClientEntityInterface $client): void

FILE: src/Entities/UserEntityInterface.php
  type UserEntityInterface (line 15) | interface UserEntityInterface
    method getIdentifier (line 22) | public function getIdentifier(): string;

FILE: src/EventEmitting/AbstractEvent.php
  class AbstractEvent (line 10) | class AbstractEvent implements StoppableEventInterface, HasEventName
    method __construct (line 14) | public function __construct(private string $name)
    method eventName (line 18) | public function eventName(): string
    method getName (line 28) | public function getName(): string
    method isPropagationStopped (line 33) | public function isPropagationStopped(): bool
    method stopPropagation (line 38) | public function stopPropagation(): self

FILE: src/EventEmitting/EmitterAwareInterface.php
  type EmitterAwareInterface (line 7) | interface EmitterAwareInterface
    method getEmitter (line 9) | public function getEmitter(): EventEmitter;
    method setEmitter (line 11) | public function setEmitter(EventEmitter $emitter): self;

FILE: src/EventEmitting/EmitterAwarePolyfill.php
  type EmitterAwarePolyfill (line 10) | trait EmitterAwarePolyfill
    method getEmitter (line 14) | public function getEmitter(): EventEmitter
    method setEmitter (line 19) | public function setEmitter(EventEmitter $emitter): self
    method getEventDispatcher (line 26) | public function getEventDispatcher(): EventDispatcherInterface
    method getListenerRegistry (line 31) | public function getListenerRegistry(): ListenerRegistry

FILE: src/EventEmitting/EventEmitter.php
  class EventEmitter (line 10) | final class EventEmitter extends EventDispatcher
    method addListener (line 12) | public function addListener(string $event, callable $listener, int $pr...
    method emit (line 19) | public function emit(object $event): object

FILE: src/Exception/OAuthServerException.php
  class OAuthServerException (line 24) | class OAuthServerException extends Exception
    method __construct (line 36) | final public function __construct(string $message, int $code, private ...
    method getPayload (line 54) | public function getPayload(): array
    method setPayload (line 64) | public function setPayload(array $payload): void
    method setServerRequest (line 72) | public function setServerRequest(ServerRequestInterface $serverRequest...
    method unsupportedGrantType (line 80) | public static function unsupportedGrantType(): static
    method invalidRequest (line 91) | public static function invalidRequest(string $parameter, ?string $hint...
    method invalidClient (line 103) | public static function invalidClient(ServerRequestInterface $serverReq...
    method invalidScope (line 115) | public static function invalidScope(string $scope, string|null $redire...
    method invalidCredentials (line 134) | public static function invalidCredentials(): static
    method serverError (line 144) | public static function serverError(string $hint, ?Throwable $previous ...
    method invalidRefreshToken (line 161) | public static function invalidRefreshToken(?string $hint = null, ?Thro...
    method accessDenied (line 169) | public static function accessDenied(?string $hint = null, ?string $red...
    method invalidGrant (line 185) | public static function invalidGrant(string $hint = ''): static
    method getErrorType (line 198) | public function getErrorType(): string
    method expiredToken (line 210) | public static function expiredToken(?string $hint = null, ?Throwable $...
    method authorizationPending (line 218) | public static function authorizationPending(string $hint = '', ?Throwa...
    method slowDown (line 239) | public static function slowDown(string $hint = '', ?Throwable $previou...
    method unauthorizedClient (line 257) | public static function unauthorizedClient(?string $hint = null): static
    method generateHttpResponse (line 271) | public function generateHttpResponse(ResponseInterface $response, bool...
    method getHttpHeaders (line 305) | public function getHttpHeaders(): array
    method hasRedirect (line 336) | public function hasRedirect(): bool
    method getRedirectUri (line 344) | public function getRedirectUri(): ?string
    method getHttpStatusCode (line 352) | public function getHttpStatusCode(): int
    method getHint (line 357) | public function getHint(): ?string
    method requestHasAuthorizationHeader (line 368) | private function requestHasAuthorizationHeader(): bool

FILE: src/Exception/UniqueTokenIdentifierConstraintViolationException.php
  class UniqueTokenIdentifierConstraintViolationException (line 15) | class UniqueTokenIdentifierConstraintViolationException extends OAuthSer...
    method create (line 17) | public static function create(): UniqueTokenIdentifierConstraintViolat...

FILE: src/Grant/AbstractAuthorizeGrant.php
  class AbstractAuthorizeGrant (line 23) | abstract class AbstractAuthorizeGrant extends AbstractGrant
    method makeRedirectUri (line 28) | public function makeRedirectUri(string $uri, array $params = [], strin...
    method createAuthorizationRequest (line 35) | protected function createAuthorizationRequest(): AuthorizationRequestI...
    method getClientRedirectUri (line 43) | protected function getClientRedirectUri(ClientEntityInterface $client)...

FILE: src/Grant/AbstractGrant.php
  class AbstractGrant (line 60) | abstract class AbstractGrant implements GrantTypeInterface
    method setClientRepository (line 89) | public function setClientRepository(ClientRepositoryInterface $clientR...
    method setAccessTokenRepository (line 94) | public function setAccessTokenRepository(AccessTokenRepositoryInterfac...
    method setScopeRepository (line 99) | public function setScopeRepository(ScopeRepositoryInterface $scopeRepo...
    method setRefreshTokenRepository (line 104) | public function setRefreshTokenRepository(RefreshTokenRepositoryInterf...
    method setAuthCodeRepository (line 109) | public function setAuthCodeRepository(AuthCodeRepositoryInterface $aut...
    method setUserRepository (line 114) | public function setUserRepository(UserRepositoryInterface $userReposit...
    method setRefreshTokenTTL (line 122) | public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void
    method setPrivateKey (line 130) | public function setPrivateKey(CryptKeyInterface $privateKey): void
    method setDefaultScope (line 135) | public function setDefaultScope(string $scope): void
    method revokeRefreshTokens (line 140) | public function revokeRefreshTokens(bool $willRevoke): void
    method validateClient (line 150) | protected function validateClient(ServerRequestInterface $request): Cl...
    method getClientEntityOrFail (line 183) | protected function getClientEntityOrFail(string $clientId, ServerReque...
    method supportsGrantType (line 202) | protected function supportsGrantType(ClientEntityInterface $client, st...
    method getClientCredentials (line 216) | protected function getClientCredentials(ServerRequestInterface $reques...
    method validateRedirectUri (line 237) | protected function validateRedirectUri(
    method validateScopes (line 259) | public function validateScopes(string|array|null $scopes, ?string $red...
    method convertScopesQueryStringToArray (line 287) | private function convertScopesQueryStringToArray(string $scopes): array
    method parseParam (line 301) | private static function parseParam(string $parameter, array $request, ...
    method getRequestParameter (line 329) | protected function getRequestParameter(string $parameter, ServerReques...
    method getBasicAuthCredentials (line 343) | protected function getBasicAuthCredentials(ServerRequestInterface $req...
    method getQueryStringParameter (line 380) | protected function getQueryStringParameter(string $parameter, ServerRe...
    method getCookieParameter (line 392) | protected function getCookieParameter(string $parameter, ServerRequest...
    method getServerParameter (line 404) | protected function getServerParameter(string $parameter, ServerRequest...
    method issueAccessToken (line 417) | protected function issueAccessToken(
    method issueAuthCode (line 455) | protected function issueAuthCode(
    method issueRefreshToken (line 498) | protected function issueRefreshToken(AccessTokenEntityInterface $acces...
    method generateUniqueIdentifier (line 539) | protected function generateUniqueIdentifier(int $length = 40): string
    method canRespondToAccessTokenRequest (line 560) | public function canRespondToAccessTokenRequest(ServerRequestInterface ...
    method canRespondToAuthorizationRequest (line 573) | public function canRespondToAuthorizationRequest(ServerRequestInterfac...
    method validateAuthorizationRequest (line 581) | public function validateAuthorizationRequest(ServerRequestInterface $r...
    method completeAuthorizationRequest (line 589) | public function completeAuthorizationRequest(AuthorizationRequestInter...
    method canRespondToDeviceAuthorizationRequest (line 597) | public function canRespondToDeviceAuthorizationRequest(ServerRequestIn...
    method respondToDeviceAuthorizationRequest (line 605) | public function respondToDeviceAuthorizationRequest(ServerRequestInter...
    method completeDeviceAuthorizationRequest (line 613) | public function completeDeviceAuthorizationRequest(string $deviceCode,...
    method setIntervalVisibility (line 621) | public function setIntervalVisibility(bool $intervalVisibility): void
    method getIntervalVisibility (line 629) | public function getIntervalVisibility(): bool
    method setIncludeVerificationUriComplete (line 637) | public function setIncludeVerificationUriComplete(bool $includeVerific...

FILE: src/Grant/AuthCodeGrant.php
  class AuthCodeGrant (line 52) | class AuthCodeGrant extends AbstractAuthorizeGrant
    method __construct (line 64) | public function __construct(
    method disableRequireCodeChallengeForPublicClients (line 85) | public function disableRequireCodeChallengeForPublicClients(): void
    method respondToAccessTokenRequest (line 95) | public function respondToAccessTokenRequest(
    method validateCodeChallenge (line 159) | private function validateCodeChallenge(object $authCodePayload, ?strin...
    method validateAuthorizationCode (line 199) | private function validateAuthorizationCode(
    method getIdentifier (line 236) | public function getIdentifier(): string
    method canRespondToAuthorizationRequest (line 244) | public function canRespondToAuthorizationRequest(ServerRequestInterfac...
    method validateAuthorizationRequest (line 256) | public function validateAuthorizationRequest(ServerRequestInterface $r...
    method completeAuthorizationRequest (line 349) | public function completeAuthorizationRequest(AuthorizationRequestInter...

FILE: src/Grant/ClientCredentialsGrant.php
  class ClientCredentialsGrant (line 27) | class ClientCredentialsGrant extends AbstractGrant
    method respondToAccessTokenRequest (line 32) | public function respondToAccessTokenRequest(
    method getIdentifier (line 65) | public function getIdentifier(): string

FILE: src/Grant/DeviceCodeGrant.php
  class DeviceCodeGrant (line 44) | class DeviceCodeGrant extends AbstractGrant
    method __construct (line 51) | public function __construct(
    method canRespondToDeviceAuthorizationRequest (line 69) | public function canRespondToDeviceAuthorizationRequest(ServerRequestIn...
    method respondToDeviceAuthorizationRequest (line 77) | public function respondToDeviceAuthorizationRequest(ServerRequestInter...
    method completeDeviceAuthorizationRequest (line 118) | public function completeDeviceAuthorizationRequest(string $deviceCode,...
    method respondToAccessTokenRequest (line 139) | public function respondToAccessTokenRequest(
    method validateDeviceCode (line 190) | protected function validateDeviceCode(ServerRequestInterface $request,...
    method deviceCodePolledTooSoon (line 223) | private function deviceCodePolledTooSoon(?DateTimeImmutable $lastPoll)...
    method setVerificationUri (line 231) | public function setVerificationUri(string $verificationUri): void
    method getIdentifier (line 239) | public function getIdentifier(): string
    method setDeviceCodeRepository (line 244) | private function setDeviceCodeRepository(DeviceCodeRepositoryInterface...
    method issueDeviceCode (line 257) | protected function issueDeviceCode(
    method generateUserCode (line 299) | protected function generateUserCode(int $length = 8): string
    method setIntervalVisibility (line 320) | public function setIntervalVisibility(bool $intervalVisibility): void
    method getIntervalVisibility (line 325) | public function getIntervalVisibility(): bool
    method setIncludeVerificationUriComplete (line 330) | public function setIncludeVerificationUriComplete(bool $includeVerific...

FILE: src/Grant/GrantTypeInterface.php
  type GrantTypeInterface (line 32) | interface GrantTypeInterface extends EmitterAwareInterface
    method setRefreshTokenTTL (line 37) | public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void;
    method getIdentifier (line 42) | public function getIdentifier(): string;
    method respondToAccessTokenRequest (line 47) | public function respondToAccessTokenRequest(
    method canRespondToAuthorizationRequest (line 56) | public function canRespondToAuthorizationRequest(ServerRequestInterfac...
    method validateAuthorizationRequest (line 65) | public function validateAuthorizationRequest(ServerRequestInterface $r...
    method completeAuthorizationRequest (line 72) | public function completeAuthorizationRequest(AuthorizationRequestInter...
    method canRespondToAccessTokenRequest (line 79) | public function canRespondToAccessTokenRequest(ServerRequestInterface ...
    method canRespondToDeviceAuthorizationRequest (line 84) | public function canRespondToDeviceAuthorizationRequest(ServerRequestIn...
    method respondToDeviceAuthorizationRequest (line 93) | public function respondToDeviceAuthorizationRequest(ServerRequestInter...
    method completeDeviceAuthorizationRequest (line 101) | public function completeDeviceAuthorizationRequest(string $deviceCode,...
    method setClientRepository (line 106) | public function setClientRepository(ClientRepositoryInterface $clientR...
    method setAccessTokenRepository (line 111) | public function setAccessTokenRepository(AccessTokenRepositoryInterfac...
    method setScopeRepository (line 116) | public function setScopeRepository(ScopeRepositoryInterface $scopeRepo...
    method setDefaultScope (line 121) | public function setDefaultScope(string $scope): void;
    method setPrivateKey (line 126) | public function setPrivateKey(CryptKeyInterface $privateKey): void;
    method setEncryptionKey (line 128) | public function setEncryptionKey(Key|string|null $key = null): void;
    method revokeRefreshTokens (line 133) | public function revokeRefreshTokens(bool $willRevoke): void;
    method setIntervalVisibility (line 139) | public function setIntervalVisibility(bool $intervalVisibility): void;
    method getIntervalVisibility (line 145) | public function getIntervalVisibility(): bool;
    method setIncludeVerificationUriComplete (line 152) | public function setIncludeVerificationUriComplete(bool $includeVerific...

FILE: src/Grant/ImplicitGrant.php
  class ImplicitGrant (line 31) | class ImplicitGrant extends AbstractAuthorizeGrant
    method __construct (line 33) | public function __construct(private DateInterval $accessTokenTTL, priv...
    method setRefreshTokenTTL (line 40) | public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void
    method setRefreshTokenRepository (line 48) | public function setRefreshTokenRepository(RefreshTokenRepositoryInterf...
    method canRespondToAccessTokenRequest (line 56) | public function canRespondToAccessTokenRequest(ServerRequestInterface ...
    method getIdentifier (line 64) | public function getIdentifier(): string
    method respondToAccessTokenRequest (line 72) | public function respondToAccessTokenRequest(
    method canRespondToAuthorizationRequest (line 83) | public function canRespondToAuthorizationRequest(ServerRequestInterfac...
    method validateAuthorizationRequest (line 95) | public function validateAuthorizationRequest(ServerRequestInterface $r...
    method completeAuthorizationRequest (line 149) | public function completeAuthorizationRequest(AuthorizationRequestInter...

FILE: src/Grant/PasswordGrant.php
  class PasswordGrant (line 32) | class PasswordGrant extends AbstractGrant
    method __construct (line 34) | public function __construct(
    method respondToAccessTokenRequest (line 47) | public function respondToAccessTokenRequest(
    method validateUser (line 83) | protected function validateUser(ServerRequestInterface $request, Clien...
    method getIdentifier (line 110) | public function getIdentifier(): string

FILE: src/Grant/RefreshTokenGrant.php
  class RefreshTokenGrant (line 35) | class RefreshTokenGrant extends AbstractGrant
    method __construct (line 37) | public function __construct(RefreshTokenRepositoryInterface $refreshTo...
    method respondToAccessTokenRequest (line 47) | public function respondToAccessTokenRequest(
    method validateOldRefreshToken (line 106) | protected function validateOldRefreshToken(ServerRequestInterface $req...
    method getIdentifier (line 138) | public function getIdentifier(): string

FILE: src/Middleware/AuthorizationServerMiddleware.php
  class AuthorizationServerMiddleware (line 20) | class AuthorizationServerMiddleware
    method __construct (line 22) | public function __construct(private AuthorizationServer $server)
    method __invoke (line 26) | public function __invoke(ServerRequestInterface $request, ResponseInte...

FILE: src/Middleware/ResourceServerMiddleware.php
  class ResourceServerMiddleware (line 20) | class ResourceServerMiddleware
    method __construct (line 22) | public function __construct(private ResourceServer $server)
    method __invoke (line 26) | public function __invoke(ServerRequestInterface $request, ResponseInte...

FILE: src/RedirectUriValidators/RedirectUriValidator.php
  class RedirectUriValidator (line 21) | class RedirectUriValidator implements RedirectUriValidatorInterface
    method __construct (line 33) | public function __construct(array|string $allowedRedirectUris)
    method validateRedirectUri (line 47) | public function validateRedirectUri(string $redirectUri): bool
    method isLoopbackUri (line 61) | private function isLoopbackUri(string $redirectUri): bool
    method matchExactUri (line 76) | private function matchExactUri(string $redirectUri): bool
    method matchUriExcludingPort (line 84) | private function matchUriExcludingPort(string $redirectUri): bool
    method parseUrlAndRemovePort (line 100) | private function parseUrlAndRemovePort(string $url): string

FILE: src/RedirectUriValidators/RedirectUriValidatorInterface.php
  type RedirectUriValidatorInterface (line 15) | interface RedirectUriValidatorInterface
    method validateRedirectUri (line 20) | public function validateRedirectUri(string $redirectUri): bool;

FILE: src/Repositories/AccessTokenRepositoryInterface.php
  type AccessTokenRepositoryInterface (line 23) | interface AccessTokenRepositoryInterface extends RepositoryInterface
    method getNewToken (line 30) | public function getNewToken(
    method persistNewAccessToken (line 39) | public function persistNewAccessToken(AccessTokenEntityInterface $acce...
    method revokeAccessToken (line 41) | public function revokeAccessToken(string $tokenId): void;
    method isAccessTokenRevoked (line 43) | public function isAccessTokenRevoked(string $tokenId): bool;

FILE: src/Repositories/AuthCodeRepositoryInterface.php
  type AuthCodeRepositoryInterface (line 21) | interface AuthCodeRepositoryInterface extends RepositoryInterface
    method getNewAuthCode (line 23) | public function getNewAuthCode(): AuthCodeEntityInterface;
    method persistNewAuthCode (line 28) | public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEn...
    method revokeAuthCode (line 30) | public function revokeAuthCode(string $codeId): void;
    method isAuthCodeRevoked (line 32) | public function isAuthCodeRevoked(string $codeId): bool;

FILE: src/Repositories/ClientRepositoryInterface.php
  type ClientRepositoryInterface (line 20) | interface ClientRepositoryInterface extends RepositoryInterface
    method getClientEntity (line 25) | public function getClientEntity(string $clientIdentifier): ?ClientEnti...
    method validateClient (line 30) | public function validateClient(string $clientIdentifier, ?string $clie...

FILE: src/Repositories/DeviceCodeRepositoryInterface.php
  type DeviceCodeRepositoryInterface (line 18) | interface DeviceCodeRepositoryInterface extends RepositoryInterface
    method getNewDeviceCode (line 23) | public function getNewDeviceCode(): DeviceCodeEntityInterface;
    method persistDeviceCode (line 30) | public function persistDeviceCode(DeviceCodeEntityInterface $deviceCod...
    method getDeviceCodeEntityByDeviceCode (line 35) | public function getDeviceCodeEntityByDeviceCode(
    method revokeDeviceCode (line 42) | public function revokeDeviceCode(string $codeId): void;
    method isDeviceCodeRevoked (line 49) | public function isDeviceCodeRevoked(string $codeId): bool;

FILE: src/Repositories/RefreshTokenRepositoryInterface.php
  type RefreshTokenRepositoryInterface (line 21) | interface RefreshTokenRepositoryInterface extends RepositoryInterface
    method getNewRefreshToken (line 23) | public function getNewRefreshToken(): ?RefreshTokenEntityInterface;
    method persistNewRefreshToken (line 28) | public function persistNewRefreshToken(RefreshTokenEntityInterface $re...
    method revokeRefreshToken (line 30) | public function revokeRefreshToken(string $tokenId): void;
    method isRefreshTokenRevoked (line 32) | public function isRefreshTokenRevoked(string $tokenId): bool;

FILE: src/Repositories/RepositoryInterface.php
  type RepositoryInterface (line 18) | interface RepositoryInterface

FILE: src/Repositories/ScopeRepositoryInterface.php
  type ScopeRepositoryInterface (line 21) | interface ScopeRepositoryInterface extends RepositoryInterface
    method getScopeEntityByIdentifier (line 28) | public function getScopeEntityByIdentifier(string $identifier): ?Scope...
    method finalizeScopes (line 38) | public function finalizeScopes(

FILE: src/Repositories/UserRepositoryInterface.php
  type UserRepositoryInterface (line 18) | interface UserRepositoryInterface extends RepositoryInterface
    method getUserEntityByUserCredentials (line 23) | public function getUserEntityByUserCredentials(

FILE: src/RequestAccessTokenEvent.php
  class RequestAccessTokenEvent (line 19) | class RequestAccessTokenEvent extends RequestEvent
    method __construct (line 21) | public function __construct(
    method getAccessToken (line 33) | public function getAccessToken(): AccessTokenEntityInterface

FILE: src/RequestEvent.php
  class RequestEvent (line 18) | class RequestEvent extends AbstractEvent
    method __construct (line 27) | public function __construct(string $name, private ServerRequestInterfa...
    method getRequest (line 35) | public function getRequest(): ServerRequestInterface

FILE: src/RequestRefreshTokenEvent.php
  class RequestRefreshTokenEvent (line 19) | class RequestRefreshTokenEvent extends RequestEvent
    method __construct (line 21) | public function __construct(
    method getRefreshToken (line 33) | public function getRefreshToken(): RefreshTokenEntityInterface

FILE: src/RequestTypes/AuthorizationRequest.php
  class AuthorizationRequest (line 19) | class AuthorizationRequest implements AuthorizationRequestInterface
    method getGrantTypeId (line 68) | public function getGrantTypeId(): string
    method setGrantTypeId (line 73) | public function setGrantTypeId(string $grantTypeId): void
    method getClient (line 78) | public function getClient(): ClientEntityInterface
    method setClient (line 83) | public function setClient(ClientEntityInterface $client): void
    method getUser (line 88) | public function getUser(): ?UserEntityInterface
    method setUser (line 93) | public function setUser(UserEntityInterface $user): void
    method getScopes (line 101) | public function getScopes(): array
    method setScopes (line 109) | public function setScopes(array $scopes): void
    method isAuthorizationApproved (line 114) | public function isAuthorizationApproved(): bool
    method setAuthorizationApproved (line 119) | public function setAuthorizationApproved(bool $authorizationApproved):...
    method getRedirectUri (line 124) | public function getRedirectUri(): ?string
    method setRedirectUri (line 129) | public function setRedirectUri(?string $redirectUri): void
    method getState (line 134) | public function getState(): ?string
    method setState (line 139) | public function setState(string $state): void
    method getCodeChallenge (line 144) | public function getCodeChallenge(): ?string
    method setCodeChallenge (line 149) | public function setCodeChallenge(string $codeChallenge): void
    method getCodeChallengeMethod (line 154) | public function getCodeChallengeMethod(): ?string
    method setCodeChallengeMethod (line 159) | public function setCodeChallengeMethod(string $codeChallengeMethod): void

FILE: src/RequestTypes/AuthorizationRequestInterface.php
  type AuthorizationRequestInterface (line 19) | interface AuthorizationRequestInterface
    method getUser (line 21) | public function getUser(): UserEntityInterface|null;
    method setState (line 23) | public function setState(string $state): void;
    method getClient (line 25) | public function getClient(): ClientEntityInterface;
    method setAuthorizationApproved (line 27) | public function setAuthorizationApproved(bool $authorizationApproved):...
    method setScopes (line 32) | public function setScopes(array $scopes): void;
    method setRedirectUri (line 34) | public function setRedirectUri(?string $redirectUri): void;
    method getRedirectUri (line 36) | public function getRedirectUri(): ?string;
    method getCodeChallengeMethod (line 38) | public function getCodeChallengeMethod(): ?string;
    method setGrantTypeId (line 40) | public function setGrantTypeId(string $grantTypeId): void;
    method setUser (line 42) | public function setUser(UserEntityInterface $user): void;
    method setClient (line 44) | public function setClient(ClientEntityInterface $client): void;
    method setCodeChallenge (line 46) | public function setCodeChallenge(string $codeChallenge): void;
    method isAuthorizationApproved (line 48) | public function isAuthorizationApproved(): bool;
    method getState (line 50) | public function getState(): ?string;
    method getCodeChallenge (line 52) | public function getCodeChallenge(): ?string;
    method setCodeChallengeMethod (line 54) | public function setCodeChallengeMethod(string $codeChallengeMethod): v...
    method getScopes (line 59) | public function getScopes(): array;
    method getGrantTypeId (line 61) | public function getGrantTypeId(): string;

FILE: src/ResourceServer.php
  class ResourceServer (line 21) | class ResourceServer
    method __construct (line 25) | public function __construct(
    method getAuthorizationValidator (line 36) | protected function getAuthorizationValidator(): AuthorizationValidator...
    method validateAuthenticatedRequest (line 54) | public function validateAuthenticatedRequest(ServerRequestInterface $r...

FILE: src/ResponseTypes/AbstractResponseType.php
  class AbstractResponseType (line 23) | abstract class AbstractResponseType implements ResponseTypeInterface
    method setAccessToken (line 33) | public function setAccessToken(
    method setRefreshToken (line 40) | public function setRefreshToken(
    method setPrivateKey (line 47) | public function setPrivateKey(

FILE: src/ResponseTypes/BearerTokenResponse.php
  class BearerTokenResponse (line 26) | class BearerTokenResponse extends AbstractResponseType
    method generateHttpResponse (line 28) | public function generateHttpResponse(ResponseInterface $response): Res...
    method getExtraParams (line 79) | protected function getExtraParams(

FILE: src/ResponseTypes/DeviceCodeResponse.php
  class DeviceCodeResponse (line 24) | class DeviceCodeResponse extends AbstractResponseType
    method generateHttpResponse (line 33) | public function generateHttpResponse(ResponseInterface $response): Res...
    method setDeviceCodeEntity (line 72) | public function setDeviceCodeEntity(DeviceCodeEntityInterface $deviceC...
    method includeVerificationUriComplete (line 77) | public function includeVerificationUriComplete(): void
    method includeInterval (line 82) | public function includeInterval(): void
    method getExtraParams (line 94) | protected function getExtraParams(DeviceCodeEntityInterface $deviceCod...

FILE: src/ResponseTypes/RedirectResponse.php
  class RedirectResponse (line 19) | class RedirectResponse extends AbstractResponseType
    method setRedirectUri (line 23) | public function setRedirectUri(string $redirectUri): void
    method generateHttpResponse (line 28) | public function generateHttpResponse(ResponseInterface $response): Res...

FILE: src/ResponseTypes/ResponseTypeInterface.php
  type ResponseTypeInterface (line 22) | interface ResponseTypeInterface
    method setAccessToken (line 24) | public function setAccessToken(AccessTokenEntityInterface $accessToken...
    method setRefreshToken (line 26) | public function setRefreshToken(RefreshTokenEntityInterface $refreshTo...
    method generateHttpResponse (line 28) | public function generateHttpResponse(ResponseInterface $response): Res...
    method setEncryptionKey (line 30) | public function setEncryptionKey(Key|string|null $key = null): void;

FILE: tests/AuthorizationServerTest.php
  class AuthorizationServerTest (line 40) | class AuthorizationServerTest extends TestCase
    method setUp (line 45) | public function setUp(): void
    method testGrantTypeGetsEnabled (line 53) | public function testGrantTypeGetsEnabled(): void
    method testRespondToRequestInvalidGrantType (line 70) | public function testRespondToRequestInvalidGrantType(): void
    method testRespondToRequest (line 91) | public function testRespondToRequest(): void
    method testGetResponseType (line 129) | public function testGetResponseType(): void
    method testGetResponseTypeExtended (line 147) | public function testGetResponseTypeExtended(): void
    method testMultipleRequestsGetDifferentResponseTypeInstances (line 177) | public function testMultipleRequestsGetDifferentResponseTypeInstances(...
    method testCompleteAuthorizationRequest (line 226) | public function testCompleteAuthorizationRequest(): void
    method testValidateAuthorizationRequest (line 268) | public function testValidateAuthorizationRequest(): void
    method testValidateAuthorizationRequestUnregistered (line 315) | public function testValidateAuthorizationRequestUnregistered(): void

FILE: tests/AuthorizationValidators/BearerTokenValidatorTest.php
  class BearerTokenValidatorTest (line 21) | class BearerTokenValidatorTest extends TestCase
    method testBearerTokenValidatorAcceptsValidToken (line 23) | public function testBearerTokenValidatorAcceptsValidToken(): void
    method testBearerTokenValidatorRejectsExpiredToken (line 50) | public function testBearerTokenValidatorRejectsExpiredToken(): void
    method testBearerTokenValidatorAcceptsExpiredTokenWithinLeeway (line 78) | public function testBearerTokenValidatorAcceptsExpiredTokenWithinLeewa...
    method testBearerTokenValidatorRejectsExpiredTokenBeyondLeeway (line 108) | public function testBearerTokenValidatorRejectsExpiredTokenBeyondLeewa...
    method testBearerTokenValidatorCaseInsensitiveWithBearerHeader (line 139) | public function testBearerTokenValidatorCaseInsensitiveWithBearerHeade...

FILE: tests/CodeChallengeVerifiers/PlainVerifierTest.php
  class PlainVerifierTest (line 10) | class PlainVerifierTest extends TestCase
    method testGetMethod (line 12) | public function testGetMethod(): void
    method testVerifyCodeChallenge (line 19) | public function testVerifyCodeChallenge(): void

FILE: tests/CodeChallengeVerifiers/S256VerifierTest.php
  class S256VerifierTest (line 15) | class S256VerifierTest extends TestCase
    method testGetMethod (line 17) | public function testGetMethod(): void
    method testVerifyCodeChallengeSucceeds (line 24) | public function testVerifyCodeChallengeSucceeds(): void
    method testVerifyCodeChallengeFails (line 32) | public function testVerifyCodeChallengeFails(): void
    method createCodeChallenge (line 40) | private function createCodeChallenge(string $codeVerifier): string

FILE: tests/EventEmitting/EmitterAwarePolyfillTest.php
  class EmitterAwarePolyfillTest (line 11) | class EmitterAwarePolyfillTest extends TestCase
    method testGetEmitter (line 13) | public function testGetEmitter(): void

FILE: tests/Exception/OAuthServerExceptionTest.php
  class OAuthServerExceptionTest (line 17) | class OAuthServerExceptionTest extends TestCase
    method testInvalidClientExceptionSetsAuthenticateHeader (line 19) | public function testInvalidClientExceptionSetsAuthenticateHeader(): void
    method testInvalidClientExceptionSetsBearerAuthenticateHeader (line 36) | public function testInvalidClientExceptionSetsBearerAuthenticateHeader...
    method testInvalidClientExceptionOmitsAuthenticateHeader (line 53) | public function testInvalidClientExceptionOmitsAuthenticateHeader(): void
    method testInvalidClientExceptionOmitsAuthenticateHeaderGivenEmptyAuthorizationHeader (line 69) | public function testInvalidClientExceptionOmitsAuthenticateHeaderGiven...
    method issueInvalidClientException (line 91) | private function issueInvalidClientException(ServerRequestInterface $s...
    method testHasRedirect (line 108) | public function testHasRedirect(): void
    method testDoesNotHaveRedirect (line 115) | public function testDoesNotHaveRedirect(): void
    method testHasPrevious (line 122) | public function testHasPrevious(): void
    method testDoesNotHavePrevious (line 132) | public function testDoesNotHavePrevious(): void
    method testCanGetRedirectionUri (line 139) | public function testCanGetRedirectionUri(): void
    method testInvalidCredentialsIsInvalidGrant (line 146) | public function testInvalidCredentialsIsInvalidGrant(): void

FILE: tests/Grant/AbstractGrantTest.php
  class AbstractGrantTest (line 32) | class AbstractGrantTest extends TestCase
    method testHttpBasicWithPassword (line 34) | public function testHttpBasicWithPassword(): void
    method testHttpBasicNoPassword (line 47) | public function testHttpBasicNoPassword(): void
    method testHttpBasicNotBasic (line 60) | public function testHttpBasicNotBasic(): void
    method testHttpBasicCaseInsensitive (line 73) | public function testHttpBasicCaseInsensitive(): void
    method testHttpBasicNotBase64 (line 86) | public function testHttpBasicNotBase64(): void
    method testHttpBasicNoColon (line 99) | public function testHttpBasicNoColon(): void
    method testGetClientCredentialsClientSecretNotAString (line 112) | public function testGetClientCredentialsClientSecretNotAString(): void
    method testValidateClientPublic (line 144) | public function testValidateClientPublic(): void
    method testValidateClientConfidential (line 171) | public function testValidateClientConfidential(): void
    method testValidateClientMissingClientId (line 200) | public function testValidateClientMissingClientId(): void
    method testValidateClientMissingClientSecret (line 221) | public function testValidateClientMissingClientSecret(): void
    method testValidateClientInvalidClientSecret (line 244) | public function testValidateClientInvalidClientSecret(): void
    method testValidateClientBadClient (line 268) | public function testValidateClientBadClient(): void
    method testCanRespondToRequest (line 292) | public function testCanRespondToRequest(): void
    method testIssueRefreshToken (line 307) | public function testIssueRefreshToken(): void
    method testIssueNullRefreshToken (line 333) | public function testIssueNullRefreshToken(): void
    method testIssueNullRefreshTokenUnauthorizedClient (line 355) | public function testIssueNullRefreshTokenUnauthorizedClient(): void
    method testIssueAccessToken (line 382) | public function testIssueAccessToken(): void
    method testIssueAuthCode (line 408) | public function testIssueAuthCode(): void
    method testGetCookieParameter (line 437) | public function testGetCookieParameter(): void
    method testGetQueryStringParameter (line 454) | public function testGetQueryStringParameter(): void
    method testValidateScopes (line 471) | public function testValidateScopes(): void
    method testValidateScopesBadScope (line 485) | public function testValidateScopesBadScope(): void
    method testGenerateUniqueIdentifier (line 500) | public function testGenerateUniqueIdentifier(): void
    method testCanRespondToAuthorizationRequest (line 512) | public function testCanRespondToAuthorizationRequest(): void
    method testValidateAuthorizationRequest (line 520) | public function testValidateAuthorizationRequest(): void
    method testCompleteAuthorizationRequest (line 531) | public function testCompleteAuthorizationRequest(): void
    method testUnauthorizedClient (line 542) | public function testUnauthorizedClient(): void

FILE: tests/Grant/AuthCodeGrantTest.php
  class AuthCodeGrantTest (line 41) | class AuthCodeGrantTest extends TestCase
    method setUp (line 52) | public function setUp(): void
    method testGetIdentifier (line 57) | public function testGetIdentifier(): void
    method testCanRespondToAuthorizationRequest (line 68) | public function testCanRespondToAuthorizationRequest(): void
    method testValidateAuthorizationRequest (line 93) | public function testValidateAuthorizationRequest(): void
    method testValidateAuthorizationRequestRedirectUriArray (line 133) | public function testValidateAuthorizationRequestRedirectUriArray(): void
    method testValidateAuthorizationRequestWithoutRedirectUri (line 172) | public function testValidateAuthorizationRequestWithoutRedirectUri(): ...
    method testValidateAuthorizationRequestCodeChallenge (line 214) | public function testValidateAuthorizationRequestCodeChallenge(): void
    method testValidateAuthorizationRequestCodeChallengeInvalidLengthTooShort (line 254) | public function testValidateAuthorizationRequestCodeChallengeInvalidLe...
    method testValidateAuthorizationRequestCodeChallengeInvalidLengthTooLong (line 284) | public function testValidateAuthorizationRequestCodeChallengeInvalidLe...
    method testValidateAuthorizationRequestCodeChallengeInvalidCharacters (line 314) | public function testValidateAuthorizationRequestCodeChallengeInvalidCh...
    method testValidateAuthorizationRequestMissingClientId (line 344) | public function testValidateAuthorizationRequestMissingClientId(): void
    method testValidateAuthorizationRequestInvalidClientId (line 365) | public function testValidateAuthorizationRequestInvalidClientId(): void
    method testValidateAuthorizationRequestBadRedirectUriString (line 388) | public function testValidateAuthorizationRequestBadRedirectUriString()...
    method testValidateAuthorizationRequestBadRedirectUriArray (line 414) | public function testValidateAuthorizationRequestBadRedirectUriArray():...
    method testValidateAuthorizationRequestInvalidCodeChallengeMethod (line 440) | public function testValidateAuthorizationRequestInvalidCodeChallengeMe...
    method testValidateAuthorizationRequestInvalidScopes (line 475) | public function testValidateAuthorizationRequestInvalidScopes(): void
    method testCompleteAuthorizationRequest (line 518) | public function testCompleteAuthorizationRequest(): void
    method testCompleteAuthorizationRequestWithMultipleRedirectUrisOnClient (line 543) | public function testCompleteAuthorizationRequestWithMultipleRedirectUr...
    method testCompleteAuthorizationRequestDenied (line 568) | public function testCompleteAuthorizationRequestDenied(): void
    method testRespondToAccessTokenRequest (line 603) | public function testRespondToAccessTokenRequest(): void
    method testRespondToAccessTokenRequestWithDefaultRedirectUri (line 705) | public function testRespondToAccessTokenRequestWithDefaultRedirectUri(...
    method testRespondToAccessTokenRequestUsingHttpBasicAuth (line 778) | public function testRespondToAccessTokenRequestUsingHttpBasicAuth(): void
    method testRespondToAccessTokenRequestForPublicClient (line 844) | public function testRespondToAccessTokenRequestForPublicClient(): void
    method testRespondToAccessTokenRequestNullRefreshToken (line 912) | public function testRespondToAccessTokenRequestNullRefreshToken(): void
    method testRespondToAccessTokenRequestCodeChallengePlain (line 981) | public function testRespondToAccessTokenRequestCodeChallengePlain(): void
    method testRespondToAccessTokenRequestCodeChallengeS256 (line 1058) | public function testRespondToAccessTokenRequestCodeChallengeS256(): void
    method testPKCEDowngradeBlocked (line 1135) | public function testPKCEDowngradeBlocked(): void
    method testRespondToAccessTokenRequestMissingRedirectUri (line 1208) | public function testRespondToAccessTokenRequestMissingRedirectUri(): void
    method testRespondToAccessTokenRequestRedirectUriMismatch (line 1258) | public function testRespondToAccessTokenRequestRedirectUriMismatch(): ...
    method testRejectAccessTokenRequestIfRedirectUriSpecifiedButNotInOriginalAuthCodeRequest (line 1309) | public function testRejectAccessTokenRequestIfRedirectUriSpecifiedButN...
    method testRespondToAccessTokenRequestMissingCode (line 1360) | public function testRespondToAccessTokenRequestMissingCode(): void
    method testRespondToAccessTokenRequestWithRefreshTokenInsteadOfAuthCode (line 1409) | public function testRespondToAccessTokenRequestWithRefreshTokenInstead...
    method testRespondToAccessTokenRequestWithAuthCodeNotAString (line 1461) | public function testRespondToAccessTokenRequestWithAuthCodeNotAString(...
    method testRespondToAccessTokenRequestExpiredCode (line 1499) | public function testRespondToAccessTokenRequestExpiredCode(): void
    method testRespondToAccessTokenRequestRevokedCode (line 1551) | public function testRespondToAccessTokenRequestRevokedCode(): void
    method testRespondToAccessTokenRequestClientMismatch (line 1619) | public function testRespondToAccessTokenRequestClientMismatch(): void
    method testRespondToAccessTokenRequestBadCode (line 1683) | public function testRespondToAccessTokenRequestBadCode(): void
    method testRespondToAccessTokenRequestNoEncryptionKey (line 1739) | public function testRespondToAccessTokenRequestNoEncryptionKey(): void
    method testRespondToAccessTokenRequestBadCodeVerifierPlain (line 1795) | public function testRespondToAccessTokenRequestBadCodeVerifierPlain():...
    method testRespondToAccessTokenRequestBadCodeVerifierS256 (line 1869) | public function testRespondToAccessTokenRequestBadCodeVerifierS256(): ...
    method testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidChars (line 1943) | public function testRespondToAccessTokenRequestMalformedCodeVerifierS2...
    method testRespondToAccessTokenRequestMalformedCodeVerifierS256WithInvalidLength (line 2017) | public function testRespondToAccessTokenRequestMalformedCodeVerifierS2...
    method testRespondToAccessTokenRequestMissingCodeVerifier (line 2091) | public function testRespondToAccessTokenRequestMissingCodeVerifier(): ...
    method testAuthCodeRepositoryUniqueConstraintCheck (line 2164) | public function testAuthCodeRepositoryUniqueConstraintCheck(): void
    method testAuthCodeRepositoryFailToPersist (line 2202) | public function testAuthCodeRepositoryFailToPersist(): void
    method testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoop (line 2231) | public function testAuthCodeRepositoryFailToPersistUniqueNoInfiniteLoo...
    method testRefreshTokenRepositoryUniqueConstraintCheck (line 2259) | public function testRefreshTokenRepositoryUniqueConstraintCheck(): void
    method testRefreshTokenRepositoryFailToPersist (line 2337) | public function testRefreshTokenRepositoryFailToPersist(): void
    method testRefreshTokenRepositoryFailToPersistUniqueNoInfiniteLoop (line 2408) | public function testRefreshTokenRepositoryFailToPersistUniqueNoInfinit...
    method testCompleteAuthorizationRequestNoUser (line 2479) | public function testCompleteAuthorizationRequestNoUser(): void
    method testPublicClientAuthCodeRequestRejectedWhenCodeChallengeRequiredButNotGiven (line 2492) | public function testPublicClientAuthCodeRequestRejectedWhenCodeChallen...
    method testUseValidRedirectUriIfScopeCheckFails (line 2527) | public function testUseValidRedirectUriIfScopeCheckFails(): void

FILE: tests/Grant/ClientCredentialsGrantTest.php
  class ClientCredentialsGrantTest (line 22) | class ClientCredentialsGrantTest extends TestCase
    method testGetIdentifier (line 26) | public function testGetIdentifier(): void
    method testRespondToRequest (line 32) | public function testRespondToRequest(): void

FILE: tests/Grant/DeviceCodeGrantTest.php
  class DeviceCodeGrantTest (line 36) | class DeviceCodeGrantTest extends TestCase
    method testGetIdentifier (line 41) | public function testGetIdentifier(): void
    method testCanRespondToDeviceAuthorizationRequest (line 56) | public function testCanRespondToDeviceAuthorizationRequest(): void
    method testRespondToDeviceAuthorizationRequest (line 73) | public function testRespondToDeviceAuthorizationRequest(): void
    method testRespondToDeviceAuthorizationRequestWithVerificationUriComplete (line 115) | public function testRespondToDeviceAuthorizationRequestWithVerificatio...
    method testValidateDeviceAuthorizationRequestMissingClient (line 161) | public function testValidateDeviceAuthorizationRequestMissingClient():...
    method testValidateDeviceAuthorizationRequestEmptyScope (line 192) | public function testValidateDeviceAuthorizationRequestEmptyScope(): void
    method testValidateDeviceAuthorizationRequestClientMismatch (line 223) | public function testValidateDeviceAuthorizationRequestClientMismatch()...
    method testCompleteDeviceAuthorizationRequest (line 252) | public function testCompleteDeviceAuthorizationRequest(): void
    method testDeviceAuthorizationResponse (line 273) | public function testDeviceAuthorizationResponse(): void
    method testRespondToAccessTokenRequest (line 329) | public function testRespondToAccessTokenRequest(): void
    method testRespondToRequestMissingClient (line 429) | public function testRespondToRequestMissingClient(): void
    method testRespondToRequestMissingDeviceCode (line 459) | public function testRespondToRequestMissingDeviceCode(): void
    method testIssueSlowDownError (line 504) | public function testIssueSlowDownError(): void
    method testIssueAuthorizationPendingError (line 552) | public function testIssueAuthorizationPendingError(): void
    method testIssueExpiredTokenError (line 599) | public function testIssueExpiredTokenError(): void
    method testSettingDeviceCodeIntervalRate (line 646) | public function testSettingDeviceCodeIntervalRate(): void
    method testSettingInternalVisibility (line 692) | public function testSettingInternalVisibility(): void
    method testIssueAccessDeniedError (line 737) | public function testIssueAccessDeniedError(): void

FILE: tests/Grant/ImplicitGrantTest.php
  class ImplicitGrantTest (line 31) | class ImplicitGrantTest extends TestCase
    method setUp (line 38) | public function setUp(): void
    method testGetIdentifier (line 43) | public function testGetIdentifier(): void
    method testCanRespondToAccessTokenRequest (line 49) | public function testCanRespondToAccessTokenRequest(): void
    method testRespondToAccessTokenRequest (line 58) | public function testRespondToAccessTokenRequest(): void
    method testCanRespondToAuthorizationRequest (line 71) | public function testCanRespondToAuthorizationRequest(): void
    method testValidateAuthorizationRequest (line 83) | public function testValidateAuthorizationRequest(): void
    method testValidateAuthorizationRequestRedirectUriArray (line 108) | public function testValidateAuthorizationRequestRedirectUriArray(): void
    method testValidateAuthorizationRequestMissingClientId (line 133) | public function testValidateAuthorizationRequestMissingClientId(): void
    method testValidateAuthorizationRequestInvalidClientId (line 148) | public function testValidateAuthorizationRequestInvalidClientId(): void
    method testValidateAuthorizationRequestBadRedirectUriString (line 167) | public function testValidateAuthorizationRequestBadRedirectUriString()...
    method testValidateAuthorizationRequestBadRedirectUriArray (line 189) | public function testValidateAuthorizationRequestBadRedirectUriArray():...
    method testValidateAuthorizationRequestInvalidScopes (line 211) | public function testValidateAuthorizationRequestInvalidScopes(): void
    method testCompleteAuthorizationRequest (line 249) | public function testCompleteAuthorizationRequest(): void
    method testCompleteAuthorizationRequestDenied (line 295) | public function testCompleteAuthorizationRequestDenied(): void
    method testAccessTokenRepositoryUniqueConstraintCheck (line 333) | public function testAccessTokenRepositoryUniqueConstraintCheck(): void
    method testAccessTokenRepositoryFailToPersist (line 375) | public function testAccessTokenRepositoryFailToPersist(): void
    method testAccessTokenRepositoryFailToPersistUniqueNoInfiniteLoop (line 407) | public function testAccessTokenRepositoryFailToPersistUniqueNoInfinite...
    method testSetRefreshTokenTTL (line 439) | public function testSetRefreshTokenTTL(): void
    method testSetRefreshTokenRepository (line 448) | public function testSetRefreshTokenRepository(): void
    method testCompleteAuthorizationRequestNoUser (line 459) | public function testCompleteAuthorizationRequestNoUser(): void

FILE: tests/Grant/PasswordGrantTest.php
  class PasswordGrantTest (line 29) | class PasswordGrantTest extends TestCase
    method testGetIdentifier (line 33) | public function testGetIdentifier(): void
    method testRespondToRequest (line 42) | public function testRespondToRequest(): void
    method testRespondToRequestNullRefreshToken (line 119) | public function testRespondToRequestNullRefreshToken(): void
    method testRespondToRequestMissingUsername (line 167) | public function testRespondToRequestMissingUsername(): void
    method testRespondToRequestMissingPassword (line 195) | public function testRespondToRequestMissingPassword(): void
    method testRespondToRequestBadCredentials (line 229) | public function testRespondToRequestBadCredentials(): void

FILE: tests/Grant/RefreshTokenGrantTest.php
  class RefreshTokenGrantTest (line 33) | class RefreshTokenGrantTest extends TestCase
    method setUp (line 37) | public function setUp(): void
    method testGetIdentifier (line 42) | public function testGetIdentifier(): void
    method testRespondToRequest (line 50) | public function testRespondToRequest(): void
    method testRespondToRequestNullRefreshToken (line 145) | public function testRespondToRequestNullRefreshToken(): void
    method testRespondToReducedScopes (line 212) | public function testRespondToReducedScopes(): void
    method testRespondToUnexpectedScope (line 278) | public function testRespondToUnexpectedScope(): void
    method testRespondToRequestMissingOldToken (line 340) | public function testRespondToRequestMissingOldToken(): void
    method testRespondToRequestInvalidOldToken (line 372) | public function testRespondToRequestInvalidOldToken(): void
    method testRespondToRequestClientMismatch (line 407) | public function testRespondToRequestClientMismatch(): void
    method testRespondToRequestExpiredToken (line 462) | public function testRespondToRequestExpiredToken(): void
    method testRespondToRequestRevokedToken (line 514) | public function testRespondToRequestRevokedToken(): void
    method testRespondToRequestFinalizeScopes (line 567) | public function testRespondToRequestFinalizeScopes(): void
    method testRevokedRefreshToken (line 650) | public function testRevokedRefreshToken(): void
    method testUnrevokedRefreshToken (line 719) | public function testUnrevokedRefreshToken(): void
    method testRespondToRequestWithIntUserId (line 803) | public function testRespondToRequestWithIntUserId(): void

FILE: tests/Middleware/AuthorizationServerMiddlewareTest.php
  class AuthorizationServerMiddlewareTest (line 27) | class AuthorizationServerMiddlewareTest extends TestCase
    method testValidResponse (line 31) | public function testValidResponse(): void
    method testOAuthErrorResponse (line 78) | public function testOAuthErrorResponse(): void
    method testOAuthErrorResponseRedirectUri (line 113) | public function testOAuthErrorResponseRedirectUri(): void
    method testOAuthErrorResponseRedirectUriFragment (line 125) | public function testOAuthErrorResponseRedirectUriFragment(): void

FILE: tests/Middleware/ResourceServerMiddlewareTest.php
  class ResourceServerMiddlewareTest (line 22) | class ResourceServerMiddlewareTest extends TestCase
    method testValidResponse (line 24) | public function testValidResponse(): void
    method testValidResponseExpiredToken (line 59) | public function testValidResponseExpiredToken(): void
    method testErrorResponse (line 94) | public function testErrorResponse(): void

FILE: tests/PHPStan/AbstractGrantExtension.php
  class AbstractGrantExtension (line 19) | final class AbstractGrantExtension implements DynamicMethodReturnTypeExt...
    method getClass (line 21) | public function getClass(): string
    method isMethodSupported (line 26) | public function isMethodSupported(MethodReflection $methodReflection):...
    method getTypeFromMethodCall (line 35) | public function getTypeFromMethodCall(MethodReflection $methodReflecti...

FILE: tests/RedirectUriValidators/RedirectUriValidatorTest.php
  class RedirectUriValidatorTest (line 10) | class RedirectUriValidatorTest extends TestCase
    method testInvalidNonLoopbackUri (line 12) | public function testInvalidNonLoopbackUri(): void
    method testValidNonLoopbackUri (line 27) | public function testValidNonLoopbackUri(): void
    method testInvalidLoopbackUri (line 42) | public function testInvalidLoopbackUri(): void
    method testValidLoopbackUri (line 54) | public function testValidLoopbackUri(): void
    method testValidIpv6LoopbackUri (line 66) | public function testValidIpv6LoopbackUri(): void
    method testCanValidateUrn (line 78) | public function testCanValidateUrn(): void
    method canValidateCustomSchemeHost (line 88) | public function canValidateCustomSchemeHost(): void
    method canValidateCustomSchemePath (line 98) | public function canValidateCustomSchemePath(): void

FILE: tests/ResourceServerTest.php
  class ResourceServerTest (line 13) | class ResourceServerTest extends TestCase
    method testValidateAuthenticatedRequest (line 15) | public function testValidateAuthenticatedRequest(): void

FILE: tests/ResponseTypes/BearerResponseTypeTest.php
  class BearerResponseTypeTest (line 27) | class BearerResponseTypeTest extends TestCase
    method testGenerateHttpResponse (line 29) | public function testGenerateHttpResponse(): void
    method testGenerateHttpResponseWithExtraParams (line 72) | public function testGenerateHttpResponseWithExtraParams(): void
    method testDetermineAccessTokenInHeaderValidToken (line 118) | public function testDetermineAccessTokenInHeaderValidToken(): void
    method testDetermineAccessTokenInHeaderInvalidJWT (line 161) | public function testDetermineAccessTokenInHeaderInvalidJWT(): void
    method testDetermineAccessTokenInHeaderRevokedToken (line 205) | public function testDetermineAccessTokenInHeaderRevokedToken(): void
    method testDetermineAccessTokenInHeaderInvalidToken (line 250) | public function testDetermineAccessTokenInHeaderInvalidToken(): void
    method testDetermineMissingBearerInHeader (line 273) | public function testDetermineMissingBearerInHeader(): void

FILE: tests/ResponseTypes/BearerTokenResponseWithParams.php
  class BearerTokenResponseWithParams (line 10) | class BearerTokenResponseWithParams extends BearerTokenResponse
    method getExtraParams (line 15) | protected function getExtraParams(AccessTokenEntityInterface $accessTo...

FILE: tests/ResponseTypes/DeviceCodeResponseTypeTest.php
  class DeviceCodeResponseTypeTest (line 21) | class DeviceCodeResponseTypeTest extends TestCase
    method testGenerateHttpResponse (line 23) | public function testGenerateHttpResponse(): void

FILE: tests/Stubs/AccessTokenEntity.php
  class AccessTokenEntity (line 12) | class AccessTokenEntity implements AccessTokenEntityInterface

FILE: tests/Stubs/AuthCodeEntity.php
  class AuthCodeEntity (line 12) | class AuthCodeEntity implements AuthCodeEntityInterface

FILE: tests/Stubs/ClientEntity.php
  class ClientEntity (line 11) | class ClientEntity implements ClientEntityInterface
    method setRedirectUri (line 19) | public function setRedirectUri(string|array $uri): void
    method setConfidential (line 24) | public function setConfidential(): void

FILE: tests/Stubs/CryptTraitStub.php
  class CryptTraitStub (line 13) | class CryptTraitStub
    method __construct (line 17) | public function __construct()
    method getKey (line 22) | public function getKey(): string|Key|null
    method doEncrypt (line 27) | public function doEncrypt(string $unencryptedData): string
    method doDecrypt (line 32) | public function doDecrypt(string $encryptedData): string

FILE: tests/Stubs/DeviceCodeEntity.php
  class DeviceCodeEntity (line 12) | class DeviceCodeEntity implements DeviceCodeEntityInterface

FILE: tests/Stubs/GrantType.php
  class GrantType (line 22) | final class GrantType implements GrantTypeInterface
    method setEmitter (line 26) | public function setEmitter(EventEmitter $emitter): self
    method getEmitter (line 33) | public function getEmitter(): EventEmitter
    method setRefreshTokenTTL (line 38) | public function setRefreshTokenTTL(DateInterval $refreshTokenTTL): void
    method getIdentifier (line 42) | public function getIdentifier(): string
    method respondToAccessTokenRequest (line 47) | public function respondToAccessTokenRequest(
    method canRespondToAuthorizationRequest (line 55) | public function canRespondToAuthorizationRequest(ServerRequestInterfac...
    method validateAuthorizationRequest (line 60) | public function validateAuthorizationRequest(ServerRequestInterface $r...
    method completeAuthorizationRequest (line 68) | public function completeAuthorizationRequest(AuthorizationRequestInter...
    method canRespondToAccessTokenRequest (line 73) | public function canRespondToAccessTokenRequest(ServerRequestInterface ...
    method setClientRepository (line 78) | public function setClientRepository(ClientRepositoryInterface $clientR...
    method setAccessTokenRepository (line 82) | public function setAccessTokenRepository(AccessTokenRepositoryInterfac...
    method setScopeRepository (line 86) | public function setScopeRepository(ScopeRepositoryInterface $scopeRepo...
    method setDefaultScope (line 90) | public function setDefaultScope(string $scope): void
    method setPrivateKey (line 94) | public function setPrivateKey(CryptKeyInterface $privateKey): void
    method setEncryptionKey (line 98) | public function setEncryptionKey(Key|string|null $key = null): void
    method revokeRefreshTokens (line 102) | public function revokeRefreshTokens(bool $willRevoke): void
    method canRespondToDeviceAuthorizationRequest (line 106) | public function canRespondToDeviceAuthorizationRequest(ServerRequestIn...
    method completeDeviceAuthorizationRequest (line 111) | public function completeDeviceAuthorizationRequest(string $deviceCode,...
    method respondToDeviceAuthorizationRequest (line 115) | public function respondToDeviceAuthorizationRequest(ServerRequestInter...
    method setIntervalVisibility (line 120) | public function setIntervalVisibility(bool $intervalVisibility): void
    method getIntervalVisibility (line 124) | public function getIntervalVisibility(): bool
    method setIncludeVerificationUriComplete (line 129) | public function setIncludeVerificationUriComplete(bool $includeVerific...

FILE: tests/Stubs/RefreshTokenEntity.php
  class RefreshTokenEntity (line 11) | class RefreshTokenEntity implements RefreshTokenEntityInterface

FILE: tests/Stubs/ScopeEntity.php
  class ScopeEntity (line 12) | class ScopeEntity implements ScopeEntityInterface
    method jsonSerialize (line 17) | #[ReturnTypeWillChange]

FILE: tests/Stubs/StubResponseType.php
  class StubResponseType (line 15) | class StubResponseType extends AbstractResponseType
    method getAccessToken (line 17) | public function getAccessToken(): AccessTokenEntityInterface
    method getRefreshToken (line 22) | public function getRefreshToken(): RefreshTokenEntityInterface|null
    method setAccessToken (line 27) | public function setAccessToken(AccessTokenEntityInterface $accessToken...
    method setRefreshToken (line 32) | public function setRefreshToken(RefreshTokenEntityInterface $refreshTo...
    method validateAccessToken (line 40) | public function validateAccessToken(ServerRequestInterface $request): ...
    method generateHttpResponse (line 49) | public function generateHttpResponse(ResponseInterface $response): Res...

FILE: tests/Stubs/UserEntity.php
  class UserEntity (line 10) | class UserEntity implements UserEntityInterface
    method __construct (line 14) | public function __construct()

FILE: tests/Utils/CryptKeyTest.php
  class CryptKeyTest (line 20) | class CryptKeyTest extends TestCase
    method testNoFile (line 22) | public function testNoFile(): void
    method testKeyCreation (line 29) | public function testKeyCreation(): void
    method testKeyString (line 38) | public function testKeyString(): void
    method testUnsupportedKeyType (line 67) | public function testUnsupportedKeyType(): void
    method testECKeyType (line 96) | public function testECKeyType(): void
    method testRSAKeyType (line 122) | public function testRSAKeyType(): void
    method generateKeyPath (line 148) | private static function generateKeyPath(string $keyContent): string

FILE: tests/Utils/CryptTraitTest.php
  class CryptTraitTest (line 14) | class CryptTraitTest extends TestCase
    method setUp (line 18) | protected function setUp(): void
    method testEncryptDecryptWithPassword (line 23) | public function testEncryptDecryptWithPassword(): void
    method testEncryptDecryptWithKey (line 30) | public function testEncryptDecryptWithKey(): void
    method encryptDecrypt (line 37) | private function encryptDecrypt(): void
Condensed preview — 143 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (606K chars).
[
  {
    "path": ".gitattributes",
    "chars": 375,
    "preview": "* text=auto\n\n/examples export-ignore\n/tests export-ignore\n/.gitattributes export-ignore\n/.github export-ignore\n/.gitigno"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 19,
    "preview": "github: [sephster]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 218,
    "preview": "version: 2\nupdates:\n- package-ecosystem: composer\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"11:00\"\n  o"
  },
  {
    "path": ".github/workflows/backwards-compatibility.yml",
    "chars": 646,
    "preview": "name: \"Backwards compatibility check\"\n\non:\n  pull_request:\n\njobs:\n  bc-check:\n    name: \"Backwards compatibility check\"\n"
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "chars": 754,
    "preview": "name: Coding Standards\n\non:\n  pull_request:\n  push:\n\njobs:\n  coding-standards:\n    name: Coding Standards\n\n    runs-on: "
  },
  {
    "path": ".github/workflows/static-analysis.yml",
    "chars": 848,
    "preview": "name: Static Analysis\n\non:\n  push:\n  pull_request:\n\njobs:\n  static-analysis:\n    name: Static Analysis\n\n    runs-on: ${{"
  },
  {
    "path": ".github/workflows/tests.yml",
    "chars": 1301,
    "preview": "name: tests\n\non:\n  push:\n  pull_request:\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  tests:\n    strategy:\n      fail-fa"
  },
  {
    "path": ".gitignore",
    "chars": 134,
    "preview": "/vendor\n/composer.lock\nphpunit.xml\n.phpunit.result.cache\n.idea\n/examples/vendor\nexamples/public.key\nexamples/private.key"
  },
  {
    "path": ".scrutinizer.yml",
    "chars": 1100,
    "preview": "build:\n    environment:\n        php:\n            version: 8.3.3\n    nodes:\n        analysis:\n            tests:\n        "
  },
  {
    "path": ".styleci.yml",
    "chars": 955,
    "preview": "preset: psr12\n\nrisky: true\n\nenabled:\n  - blank_line_before_return\n  - fully_qualified_strict_types\n  - hash_to_slash_com"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 38327,
    "preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3230,
    "preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 539,
    "preview": "Thanks for contributing to this project.\n\n\n**Please submit your pull request against the `master` branch only.**\n\n\nPleas"
  },
  {
    "path": "LICENSE",
    "chars": 1062,
    "preview": "MIT License\n\nCopyright (C) Alex Bilbie\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\n"
  },
  {
    "path": "README.md",
    "chars": 5593,
    "preview": "# PHP OAuth 2.0 Server\n\n[![Latest Version](http://img.shields.io/packagist/v/league/oauth2-server.svg?style=flat-square)"
  },
  {
    "path": "composer.json",
    "chars": 2402,
    "preview": "{\n  \"name\": \"league/oauth2-server\",\n  \"description\": \"A lightweight and powerful OAuth 2.0 authorization and resource se"
  },
  {
    "path": "examples/README.md",
    "chars": 2944,
    "preview": "# Example implementations (via [`Slim 3`](https://github.com/slimphp/Slim/tree/3.x))\n\n## Installation\n\n0. Run `composer "
  },
  {
    "path": "examples/composer.json",
    "chars": 439,
    "preview": "{\n    \"require\": {\n        \"slim/slim\": \"^3.12.3\"\n    },\n    \"require-dev\": {\n        \"league/event\": \"^3.0\",\n        \"l"
  },
  {
    "path": "examples/public/api.php",
    "chars": 2288,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\ninclude __DIR__ . '/../vendor/autoload.php';\n\nuse League\\OAuth2\\Server\\Middleware\\Resou"
  },
  {
    "path": "examples/public/auth_code.php",
    "chars": 3964,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/public/client_credentials.php",
    "chars": 2788,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/public/device_code.php",
    "chars": 3904,
    "preview": "<?php\n\n/**\n * @author    Andrew Millington <andrew@noexceptions.io>\n * @copyright Copyright (c) Alex Bilbie\n * @license "
  },
  {
    "path": "examples/public/implicit.php",
    "chars": 2899,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/public/middleware_use.php",
    "chars": 3639,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/public/password.php",
    "chars": 2765,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\ninclude __DIR__ . '/../vendor/autoload.php';\n\nuse League\\OAuth2\\Server\\AuthorizationSer"
  },
  {
    "path": "examples/public/refresh_token.php",
    "chars": 2543,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Entities/AccessTokenEntity.php",
    "chars": 656,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Entities/AuthCodeEntity.php",
    "chars": 641,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Entities/ClientEntity.php",
    "chars": 833,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Entities/DeviceCodeEntity.php",
    "chars": 641,
    "preview": "<?php\n\n/**\n * @author Andrew Millington <andrew@noexceptions.io>\n * @copyright Copyright (c) Alex Bilbie\n * @license htt"
  },
  {
    "path": "examples/src/Entities/RefreshTokenEntity.php",
    "chars": 576,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Entities/ScopeEntity.php",
    "chars": 541,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Entities/UserEntity.php",
    "chars": 519,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Repositories/AccessTokenRepository.php",
    "chars": 1664,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Repositories/AuthCodeRepository.php",
    "chars": 1199,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Repositories/ClientRepository.php",
    "chars": 1851,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Repositories/DeviceCodeRepository.php",
    "chars": 2354,
    "preview": "<?php\n\n/**\n * @author    Andrew Millington <andrew@noexceptions.io>\n * @copyright Copyright (c) Alex Bilbie\n * @license "
  },
  {
    "path": "examples/src/Repositories/RefreshTokenRepository.php",
    "chars": 1266,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Repositories/ScopeRepository.php",
    "chars": 1696,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "examples/src/Repositories/UserRepository.php",
    "chars": 926,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "phpcs.xml.dist",
    "chars": 1498,
    "preview": "<?xml version=\"1.0\"?>\n<ruleset\n    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n    xsi:noNamespaceSchemaLocati"
  },
  {
    "path": "phpstan.neon.dist",
    "chars": 240,
    "preview": "parameters:\n    level: 8\n    paths:\n        - src\n        - tests\n    ignoreErrors:\n        - \n            message: '#De"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 572,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNam"
  },
  {
    "path": "src/AuthorizationServer.php",
    "chars": 7108,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/AuthorizationValidators/AuthorizationValidatorInterface.php",
    "chars": 632,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/AuthorizationValidators/BearerTokenValidator.php",
    "chars": 4567,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/CodeChallengeVerifiers/CodeChallengeVerifierInterface.php",
    "chars": 576,
    "preview": "<?php\n\n/**\n * @author      Lukáš Unger <lookymsc@gmail.com>\n * @copyright   Copyright (c) Lukáš Unger\n * @license     ht"
  },
  {
    "path": "src/CodeChallengeVerifiers/PlainVerifier.php",
    "chars": 729,
    "preview": "<?php\n\n/**\n * @author      Lukáš Unger <lookymsc@gmail.com>\n * @copyright   Copyright (c) Lukáš Unger\n * @license     ht"
  },
  {
    "path": "src/CodeChallengeVerifiers/S256Verifier.php",
    "chars": 916,
    "preview": "<?php\n\n/**\n * @author      Lukáš Unger <lookymsc@gmail.com>\n * @copyright   Copyright (c) Lukáš Unger\n * @license     ht"
  },
  {
    "path": "src/CryptKey.php",
    "chars": 4024,
    "preview": "<?php\n\n/**\n * Cryptography key holder.\n *\n * @author      Julián Gutiérrez <juliangut@gmail.com>\n * @copyright   Copyrig"
  },
  {
    "path": "src/CryptKeyInterface.php",
    "chars": 407,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace League\\OAuth2\\Server;\n\ninterface CryptKeyInterface\n{\n    /**\n     * Retrieve "
  },
  {
    "path": "src/CryptTrait.php",
    "chars": 2771,
    "preview": "<?php\n\n/**\n * Encrypt/decrypt with encryptionKey.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   "
  },
  {
    "path": "src/Entities/AccessTokenEntityInterface.php",
    "chars": 656,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/AuthCodeEntityInterface.php",
    "chars": 450,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/ClientEntityInterface.php",
    "chars": 1087,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/DeviceCodeEntityInterface.php",
    "chars": 995,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/RefreshTokenEntityInterface.php",
    "chars": 1242,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/ScopeEntityInterface.php",
    "chars": 500,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/TokenInterface.php",
    "chars": 1790,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/Traits/AccessTokenTrait.php",
    "chars": 3083,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/Traits/AuthCodeTrait.php",
    "chars": 556,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/Traits/ClientTrait.php",
    "chars": 1249,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/Traits/DeviceCodeTrait.php",
    "chars": 2598,
    "preview": "<?php\n\n/**\n * @author      Andrew Millington <andrew@noexceptions.io>\n * @copyright   Copyright (c) Alex Bilbie\n * @lice"
  },
  {
    "path": "src/Entities/Traits/EntityTrait.php",
    "chars": 702,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/Traits/RefreshTokenTrait.php",
    "chars": 1194,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/Traits/ScopeTrait.php",
    "chars": 567,
    "preview": "<?php\n\n/**\n * @author    Andrew Millington <andrew@noexceptions.io>\n * @copyright Copyright (c) Andrew Millington\n * @li"
  },
  {
    "path": "src/Entities/Traits/TokenEntityTrait.php",
    "chars": 2363,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Entities/UserEntityInterface.php",
    "chars": 453,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/EventEmitting/AbstractEvent.php",
    "chars": 835,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace League\\OAuth2\\Server\\EventEmitting;\n\nuse League\\Event\\HasEventName;\nuse Psr\\E"
  },
  {
    "path": "src/EventEmitting/EmitterAwareInterface.php",
    "chars": 226,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace League\\OAuth2\\Server\\EventEmitting;\n\ninterface EmitterAwareInterface\n{\n    pu"
  },
  {
    "path": "src/EventEmitting/EmitterAwarePolyfill.php",
    "chars": 702,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace League\\OAuth2\\Server\\EventEmitting;\n\nuse League\\Event\\ListenerRegistry;\nuse P"
  },
  {
    "path": "src/EventEmitting/EventEmitter.php",
    "chars": 512,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace League\\OAuth2\\Server\\EventEmitting;\n\nuse League\\Event\\EventDispatcher;\nuse Le"
  },
  {
    "path": "src/Exception/OAuthServerException.php",
    "chars": 11649,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Exception/UniqueTokenIdentifierConstraintViolationException.php",
    "chars": 624,
    "preview": "<?php\n\n/**\n * @author      Ivan Kurnosov <zerkms@zerkms.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     h"
  },
  {
    "path": "src/Grant/AbstractAuthorizeGrant.php",
    "chars": 1331,
    "preview": "<?php\n\n/**\n * Abstract authorization grant.\n *\n * @author      Julián Gutiérrez <juliangut@gmail.com>\n * @copyright   Co"
  },
  {
    "path": "src/Grant/AbstractGrant.php",
    "chars": 20218,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Abstract grant.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright "
  },
  {
    "path": "src/Grant/AuthCodeGrant.php",
    "chars": 16014,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Grant/ClientCredentialsGrant.php",
    "chars": 2043,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Client credentials grant.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   "
  },
  {
    "path": "src/Grant/DeviceCodeGrant.php",
    "chars": 11266,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Device Code grant.\n *\n * @author      Andrew Millington <andrew@noexceptions.io>\n * @copyright  "
  },
  {
    "path": "src/Grant/GrantTypeInterface.php",
    "chars": 5649,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Grant type interface.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copy"
  },
  {
    "path": "src/Grant/ImplicitGrant.php",
    "chars": 7104,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Grant/PasswordGrant.php",
    "chars": 3749,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Password grant.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright "
  },
  {
    "path": "src/Grant/RefreshTokenGrant.php",
    "chars": 4912,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Refresh token grant.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyr"
  },
  {
    "path": "src/Middleware/AuthorizationServerMiddleware.php",
    "chars": 1085,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Middleware/ResourceServerMiddleware.php",
    "chars": 1059,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/RedirectUriValidators/RedirectUriValidator.php",
    "chars": 2741,
    "preview": "<?php\n\n/**\n * @author      Sebastiano Degan <sebdeg87@gmail.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license  "
  },
  {
    "path": "src/RedirectUriValidators/RedirectUriValidatorInterface.php",
    "chars": 461,
    "preview": "<?php\n\n/**\n * @author      Sebastiano Degan <sebdeg87@gmail.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license  "
  },
  {
    "path": "src/Repositories/AccessTokenRepositoryInterface.php",
    "chars": 1243,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/AuthCodeRepositoryInterface.php",
    "chars": 895,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/ClientRepositoryInterface.php",
    "chars": 736,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/DeviceCodeRepositoryInterface.php",
    "chars": 1381,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/RefreshTokenRepositoryInterface.php",
    "chars": 930,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/RepositoryInterface.php",
    "chars": 353,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/ScopeRepositoryInterface.php",
    "chars": 1227,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/Repositories/UserRepositoryInterface.php",
    "chars": 711,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/RequestAccessTokenEvent.php",
    "chars": 858,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/RequestEvent.php",
    "chars": 1059,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/RequestRefreshTokenEvent.php",
    "chars": 865,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/RequestTypes/AuthorizationRequest.php",
    "chars": 3532,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/RequestTypes/AuthorizationRequestInterface.php",
    "chars": 1662,
    "preview": "<?php\n\n/**\n * @author      Patrick Rodacker <dev@rodacker.de>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/ResourceServer.php",
    "chars": 1905,
    "preview": "<?php\n\n/**\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyright (c) Alex Bilbie\n * @license     "
  },
  {
    "path": "src/ResponseTypes/AbstractResponseType.php",
    "chars": 1323,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Abstract Response Type.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Co"
  },
  {
    "path": "src/ResponseTypes/BearerTokenResponse.php",
    "chars": 2856,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Bearer Token Response.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Cop"
  },
  {
    "path": "src/ResponseTypes/DeviceCodeResponse.php",
    "chars": 2904,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Bearer Token Response.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Cop"
  },
  {
    "path": "src/ResponseTypes/RedirectResponse.php",
    "chars": 759,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Redirect Response.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   Copyrig"
  },
  {
    "path": "src/ResponseTypes/ResponseTypeInterface.php",
    "chars": 888,
    "preview": "<?php\n\n/**\n * OAuth 2.0 Response Type Interface.\n *\n * @author      Alex Bilbie <hello@alexbilbie.com>\n * @copyright   C"
  },
  {
    "path": "tests/AuthorizationServerTest.php",
    "chars": 13520,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests;\n\nuse DateInterval;\nuse Defuse\\Crypto\\Key;\nuse Laminas\\Diactoros\\"
  },
  {
    "path": "tests/AuthorizationValidators/BearerTokenValidatorTest.php",
    "chars": 8085,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\AuthorizationValidators;\n\nuse DateInterval;\nuse DateTimeImmutable"
  },
  {
    "path": "tests/CodeChallengeVerifiers/PlainVerifierTest.php",
    "chars": 630,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\CodeChallengeVerifiers;\n\nuse League\\OAuth2\\Server\\CodeChallengeVe"
  },
  {
    "path": "tests/CodeChallengeVerifiers/S256VerifierTest.php",
    "chars": 1152,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\CodeChallengeVerifiers;\n\nuse League\\OAuth2\\Server\\CodeChallengeVe"
  },
  {
    "path": "tests/EventEmitting/EmitterAwarePolyfillTest.php",
    "chars": 1674,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\EventEmitting;\n\nuse League\\OAuth2\\Server\\EventEmitting\\EmitterAwa"
  },
  {
    "path": "tests/Exception/OAuthServerExceptionTest.php",
    "chars": 5058,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Exception;\n\nuse Exception;\nuse Laminas\\Diactoros\\Response;\nuse La"
  },
  {
    "path": "tests/Grant/AbstractGrantTest.php",
    "chars": 22713,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\ServerRequest;\nus"
  },
  {
    "path": "tests/Grant/AuthCodeGrantTest.php",
    "chars": 108611,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\Response;\nuse Lam"
  },
  {
    "path": "tests/Grant/ClientCredentialsGrantTest.php",
    "chars": 3310,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\ServerRequest;\nus"
  },
  {
    "path": "tests/Grant/DeviceCodeGrantTest.php",
    "chars": 34286,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse DateTimeImmutable;\nuse Laminas\\Diac"
  },
  {
    "path": "tests/Grant/ImplicitGrantTest.php",
    "chars": 18680,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\ServerRequest;\nus"
  },
  {
    "path": "tests/Grant/PasswordGrantTest.php",
    "chars": 12042,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\ServerRequest;\nus"
  },
  {
    "path": "tests/Grant/RefreshTokenGrantTest.php",
    "chars": 37054,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Grant;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\Response;\nuse Lam"
  },
  {
    "path": "tests/Middleware/AuthorizationServerMiddlewareTest.php",
    "chars": 5117,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Middleware;\n\nuse DateInterval;\nuse Laminas\\Diactoros\\Response;\nus"
  },
  {
    "path": "tests/Middleware/ResourceServerMiddlewareTest.php",
    "chars": 3805,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Middleware;\n\nuse DateInterval;\nuse DateTimeImmutable;\nuse Laminas"
  },
  {
    "path": "tests/PHPStan/AbstractGrantExtension.php",
    "chars": 1223,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\PHPStan;\n\nuse League\\OAuth2\\Server\\Grant\\AbstractGrant;\nuse PhpPa"
  },
  {
    "path": "tests/RedirectUriValidators/RedirectUriValidatorTest.php",
    "chars": 3279,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\RedirectUriValidators;\n\nuse League\\OAuth2\\Server\\RedirectUriValid"
  },
  {
    "path": "tests/ResourceServerTest.php",
    "chars": 847,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests;\n\nuse Laminas\\Diactoros\\ServerRequestFactory;\nuse League\\OAuth2\\S"
  },
  {
    "path": "tests/ResponseTypes/BearerResponseTypeTest.php",
    "chars": 13041,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\ResponseTypes;\n\nuse DateInterval;\nuse DateTimeImmutable;\nuse Lami"
  },
  {
    "path": "tests/ResponseTypes/BearerTokenResponseWithParams.php",
    "chars": 486,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\ResponseTypes;\n\nuse League\\OAuth2\\Server\\Entities\\AccessTokenEnti"
  },
  {
    "path": "tests/ResponseTypes/DeviceCodeResponseTypeTest.php",
    "chars": 2231,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\ResponseTypes;\n\nuse DateInterval;\nuse DateTimeImmutable;\nuse Lami"
  },
  {
    "path": "tests/Stubs/.gitattributes",
    "chars": 31,
    "preview": "private.key.crlf text eol=crlf\n"
  },
  {
    "path": "tests/Stubs/AccessTokenEntity.php",
    "chars": 437,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\AccessTokenEntityInterf"
  },
  {
    "path": "tests/Stubs/AuthCodeEntity.php",
    "chars": 422,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\AuthCodeEntityInterface"
  },
  {
    "path": "tests/Stubs/ClientEntity.php",
    "chars": 581,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\ClientEntityInterface;\n"
  },
  {
    "path": "tests/Stubs/CryptTraitStub.php",
    "chars": 680,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse Defuse\\Crypto\\Key;\nuse League\\OAuth2\\Server\\CryptTrai"
  },
  {
    "path": "tests/Stubs/DeviceCodeEntity.php",
    "chars": 432,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\DeviceCodeEntityInterfa"
  },
  {
    "path": "tests/Stubs/GrantType.php",
    "chars": 3571,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse DateInterval;\nuse Defuse\\Crypto\\Key;\nuse League\\OAuth"
  },
  {
    "path": "tests/Stubs/RefreshTokenEntity.php",
    "chars": 357,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\RefreshTokenEntityInter"
  },
  {
    "path": "tests/Stubs/ScopeEntity.php",
    "chars": 472,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\ScopeEntityInterface;\nu"
  },
  {
    "path": "tests/Stubs/StubResponseType.php",
    "chars": 1509,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse Laminas\\Diactoros\\Response;\nuse League\\OAuth2\\Server\\"
  },
  {
    "path": "tests/Stubs/UserEntity.php",
    "chars": 330,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Stubs;\n\nuse League\\OAuth2\\Server\\Entities\\Traits\\EntityTrait;\nuse"
  },
  {
    "path": "tests/Stubs/private.key",
    "chars": 1679,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAwVBWI/czetM28h+oUxi3iHpchFUIvVVsTaPRfq/WypSTpL5P\nkwXxAgY14B3rBC6NOpVSmyI"
  },
  {
    "path": "tests/Stubs/private.key.crlf",
    "chars": 1706,
    "preview": "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEpAIBAAKCAQEAtHYxRBYATiiyDFs3pEhFg6Ei/UiQEmolTaQyQK810xHY23+X\r\n4elLl6HP1J09mefmJ3ZdI"
  },
  {
    "path": "tests/Stubs/public.key",
    "chars": 451,
    "preview": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwVBWI/czetM28h+oUxi3\niHpchFUIvVVsTaPRfq/WypSTpL5P"
  },
  {
    "path": "tests/Utils/CryptKeyTest.php",
    "chars": 4228,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Utils;\n\nuse League\\OAuth2\\Server\\CryptKey;\nuse LogicException;\nus"
  },
  {
    "path": "tests/Utils/CryptTraitTest.php",
    "chars": 1073,
    "preview": "<?php\n\ndeclare(strict_types=1);\n\nnamespace LeagueTests\\Utils;\n\nuse Defuse\\Crypto\\Key;\nuse LeagueTests\\Stubs\\CryptTraitSt"
  }
]

About this extraction

This page contains the full source code of the thephpleague/oauth2-server GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 143 files (564.1 KB), approximately 135.2k tokens, and a symbol index with 739 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!