Full Code of ADmad/cakephp-jwt-auth for AI

master abcf56a13c2e cached
15 files
31.0 KB
8.3k tokens
25 symbols
1 requests
Download .txt
Repository: ADmad/cakephp-jwt-auth
Branch: master
Commit: abcf56a13c2e
Files: 15
Total size: 31.0 KB

Directory structure:
gitextract_afge8016/

├── .editorconfig
├── .gitattributes
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── composer.json
├── phpstan.neon
├── phpunit.xml.dist
├── psalm.xml
├── src/
│   └── Auth/
│       └── JwtAuthenticate.php
└── tests/
    ├── Fixture/
    │   ├── GroupsFixture.php
    │   └── UsersFixture.php
    ├── TestCase/
    │   └── Auth/
    │       └── JwtAuthenticateTest.php
    └── bootstrap.php

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

================================================
FILE: .editorconfig
================================================
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.yml]
indent_size = 2

[*.neon]
indent_style = tab


================================================
FILE: .gitattributes
================================================
# Remove files for archives generated using `git archive`
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
phpunit.xml export-ignore
.travis.yml export-ignore
tests export-ignore
.phpstan.neon export-ignore
.psalm.xml export-ignore
.github export-ignore


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on: [push, pull_request]

jobs:
  testsuite:
    runs-on: ubuntu-18.04
    strategy:
      fail-fast: false
      matrix:
        php-version: ['7.4', '8.0']
        composer-opts: ['']
        include:
          - php-version: '7.2'
            composer-opts: '--prefer-lowest'

    steps:
    - uses: actions/checkout@v1
      with:
        fetch-depth: 1

    - name: Setup PHP
      uses: shivammathur/setup-php@v2
      with:
        php-version: ${{ matrix.php-version }}
        extension-csv: mbstring, intl
        coverage: pcov

    - name: Composer install
      run: |
        composer update ${{ matrix.composer-opts }}

    - name: Run PHPUnit
      run: |
        if [[ ${{ matrix.php-version }} == '7.4' ]]; then
          vendor/bin/phpunit --coverage-clover=coverage.xml
        else
          vendor/bin/phpunit
        fi

    - name: Code Coverage Report
      if: matrix.php-version == '7.4'
      uses: codecov/codecov-action@v1

  cs-stan:
      name: Coding Standard & Static Analysis
      runs-on: ubuntu-18.04

      steps:
      - uses: actions/checkout@v1
        with:
          fetch-depth: 1

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: '7.4'
          extension-csv: mbstring, intl
          coverage: none
          tools: cs2pr, psalm, phpstan

      - name: Composer Install
        run: composer require cakephp/cakephp-codesniffer:^4.1

      - name: Run phpcs
        run: vendor/bin/phpcs --report=checkstyle -q --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/ | cs2pr

      - name: Run psalm
        if: success() || failure()
        run: psalm --output-format=github

      - name: Run phpstan
        if: success() || failure()
        run: phpstan analyse


================================================
FILE: .gitignore
================================================
/composer.lock
/phpunit.xml
/vendor
.phpunit.result.cache


================================================
FILE: LICENSE.txt
================================================
The MIT License (MIT)

Copyright (c) 2015-Present ADmad

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
================================================
# CakePHP JWT Authenticate plugin

[![Build Status](https://img.shields.io/github/workflow/status/ADmad/cakephp-jwt-auth/CI/master?style=flat-square)](https://github.com/ADmad/cakephp-jwt-auth/actions?query=workflow%3ACI+branch%3Amaster)
[![Coverage Status](https://img.shields.io/codecov/c/github/ADmad/cakephp-jwt-auth.svg?style=flat-square)](https://codecov.io/github/ADmad/cakephp-jwt-auth)
[![Total Downloads](https://img.shields.io/packagist/dt/ADmad/cakephp-jwt-auth.svg?style=flat-square)](https://packagist.org/packages/ADmad/cakephp-jwt-auth)
[![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.txt)

Plugin containing AuthComponent's authenticate class for authenticating using
[JSON Web Tokens](http://jwt.io/). You can read about JSON Web Token
specification in detail [here](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-27).

## Installation

```sh
composer require admad/cakephp-jwt-auth
```

## Usage

Load the plugin using Cake's console:

```sh
./bin/cake plugin load ADmad/JwtAuth
```

## Configuration:

Setup `AuthComponent`:

```php
    // In your controller, for e.g. src/Api/AppController.php
    public function initialize(): void
    {
        parent::initialize();

        $this->loadComponent('Auth', [
            'storage' => 'Memory',
            'authenticate' => [
                'ADmad/JwtAuth.Jwt' => [
                    'userModel' => 'Users',
                    'fields' => [
                        'username' => 'id'
                    ],

                    'parameter' => 'token',

                    // Boolean indicating whether the "sub" claim of JWT payload
                    // should be used to query the Users model and get user info.
                    // If set to `false` JWT's payload is directly returned.
                    'queryDatasource' => true,
                ]
            ],

            'unauthorizedRedirect' => false,
            'checkAuthIn' => 'Controller.initialize',

            // If you don't have a login action in your application, set
            // 'loginAction' to empty string to prevent getting a MissingRouteException.
            'loginAction' => '',
        ]);
    }
```

## Working

The authentication class checks for the token in two locations:

- `HTTP_AUTHORIZATION` environment variable:

  It first checks if token is passed using `Authorization` request header.
  The value should be of form `Bearer <token>`. The `Authorization` header name
  and token prefix `Bearer` can be customized using options `header` and `prefix`
  respectively.

- The query string variable specified using `parameter` config:

  Next it checks if the token is present in query string. The default variable
  name is `token` and can be customzied by using the `parameter` config shown
  above.

### Known Issue
  Some servers don't populate `$_SERVER['HTTP_AUTHORIZATION']` when
  `Authorization` header is set. So it's up to you to ensure that either
  `$_SERVER['HTTP_AUTHORIZATION']` or `$_ENV['HTTP_AUTHORIZATION']` is set.

  For e.g. for apache you could use the following:

  ```
  RewriteEngine On
  RewriteCond %{HTTP:Authorization} ^(.*)
  RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
  ```

  or

  ```
  SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
  ```

## Token Generation

You can use `\Firebase\JWT\JWT::encode()` of the [firebase/php-jwt](https://github.com/firebase/php-jwt)
lib, which this plugin depends on, to generate tokens.

**The payload must have the "sub" (subject) claim whose value is used to query the
Users model and find record matching the "id" field.**

Ideally you should also specify the token expiry time using `exp` claim.

You can set the `queryDatasource` option to `false` to directly return the token's
payload as user info without querying datasource for matching user record.

## Further reading

For an end to end usage example check out [this](http://www.bravo-kernel.com/2015/04/how-to-add-jwt-authentication-to-a-cakephp-3-rest-api/) blog post by Bravo Kernel.


================================================
FILE: composer.json
================================================
{
    "name": "admad/cakephp-jwt-auth",
    "type": "cakephp-plugin",
    "description": "CakePHP plugin for authenticating using JSON Web Tokens",
    "keywords": [
        "cakephp",
        "authenticate",
        "authentication",
        "jwt"
    ],
    "homepage": "http://github.com/ADmad/cakephp-jwt-auth",
    "authors": [
        {
            "name":"ADmad",
            "role":"Author",
            "homepage":"https://github.com/ADmad"
        }
    ],
    "license": "MIT",
    "support": {
        "source":"https://github.com/ADmad/cakephp-jwt-auth",
        "issues":"https://github.com/ADmad/cakephp-jwt-auth/issues"
    },
    "require": {
        "cakephp/cakephp": "^4.0",
        "firebase/php-jwt": "^5.0"
    },
    "require-dev": {
        "phpunit/phpunit": "~8.5.0"
    },
    "autoload": {
        "psr-4": {
            "ADmad\\JwtAuth\\": "src"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "ADmad\\JwtAuth\\Test\\": "tests"
        }
    }
}


================================================
FILE: phpstan.neon
================================================
parameters:
	level: 6
	checkMissingIterableValueType: false
	paths:
		- src/


================================================
FILE: phpunit.xml.dist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
    colors="true"
    processIsolation="false"
    stopOnFailure="false"
    bootstrap="./tests/bootstrap.php"
    >
    <php>
        <ini name="memory_limit" value="-1"/>
    </php>

    <!-- Add any additional test suites you want to run here -->
    <testsuites>
        <testsuite name="CakePHP JWT Auth Tests">
            <directory>./tests/TestCase</directory>
        </testsuite>
        <!-- Add plugin test suites here. -->
    </testsuites>

    <!-- Setup a listener for fixtures -->
    <listeners>
        <listener
        class="\Cake\TestSuite\Fixture\FixtureInjector"
        file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
            <arguments>
                <object class="\Cake\TestSuite\Fixture\FixtureManager" />
            </arguments>
        </listener>
    </listeners>

    <filter>
        <whitelist>
            <directory suffix=".php">./src/</directory>
        </whitelist>
    </filter>
</phpunit>


================================================
FILE: psalm.xml
================================================
<?xml version="1.0"?>
<psalm
    errorLevel="2"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
    <projectFiles>
        <directory name="src" />
        <ignoreFiles>
            <directory name="vendor" />
        </ignoreFiles>
    </projectFiles>
    <issueHandlers>
        <PropertyNotSetInConstructor errorLevel="suppress"/>
    </issueHandlers>
</psalm>


================================================
FILE: src/Auth/JwtAuthenticate.php
================================================
<?php
declare(strict_types=1);

namespace ADmad\JwtAuth\Auth;

use Cake\Auth\BaseAuthenticate;
use Cake\Controller\ComponentRegistry;
use Cake\Core\Configure;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\Utility\Security;
use Exception;
use Firebase\JWT\JWT;

/**
 * An authentication adapter for authenticating using JSON Web Tokens.
 *
 * ```
 *  $this->Auth->config('authenticate', [
 *      'ADmad/JwtAuth.Jwt' => [
 *          'parameter' => 'token',
 *          'userModel' => 'Users',
 *          'fields' => [
 *              'username' => 'id'
 *          ],
 *      ]
 *  ]);
 * ```
 *
 * @copyright 2015-Present ADmad
 * @license MIT
 * @see http://jwt.io
 * @see http://tools.ietf.org/html/draft-ietf-oauth-json-web-token
 */
class JwtAuthenticate extends BaseAuthenticate
{
    /**
     * Parsed token.
     *
     * @var string|null
     */
    protected $_token;

    /**
     * Payload data.
     *
     * @var object|null
     */
    protected $_payload;

    /**
     * Exception.
     *
     * @var \Throwable|null
     */
    protected $_error;

    /**
     * Constructor.
     *
     * Settings for this object.
     *
     * - `cookie` - Cookie name to check. Defaults to `false`.
     * - `header` - Header name to check. Defaults to `'authorization'`.
     * - `prefix` - Token prefix. Defaults to `'bearer'`.
     * - `parameter` - The url parameter name of the token. Defaults to `token`.
     *   First $_SERVER['HTTP_AUTHORIZATION'] is checked for token value.
     *   Its value should be of form "Bearer <token>". If empty this query string
     *   paramater is checked.
     * - `allowedAlgs` - List of supported verification algorithms.
     *   Defaults to ['HS256']. See API of JWT::decode() for more info.
     * - `queryDatasource` - Boolean indicating whether the `sub` claim of JWT
     *   token should be used to query the user model and get user record. If
     *   set to `false` JWT's payload is directly retured. Defaults to `true`.
     * - `userModel` - The model name of users, defaults to `Users`.
     * - `fields` - Key `username` denotes the identifier field for fetching user
     *   record. The `sub` claim of JWT must contain identifier value.
     *   Defaults to ['username' => 'id'].
     * - `finder` - Finder method.
     * - `unauthenticatedException` - Fully namespaced exception name. Exception to
     *   throw if authentication fails. Set to false to do nothing.
     *   Defaults to '\Cake\Http\Exception\UnauthorizedException'.
     * - `key` - The key, or map of keys used to decode JWT. If not set, value
     *   of Security::salt() will be used.
     *
     * @param \Cake\Controller\ComponentRegistry $registry The Component registry
     *   used on this request.
     * @param array $config Array of config to use.
     */
    public function __construct(ComponentRegistry $registry, array $config)
    {
        $defaultConfig = [
            'cookie' => false,
            'header' => 'authorization',
            'prefix' => 'bearer',
            'parameter' => 'token',
            'queryDatasource' => true,
            'fields' => ['username' => 'id'],
            'unauthenticatedException' => UnauthorizedException::class,
            'key' => null,
        ];

        $this->setConfig($defaultConfig);

        if (empty($config['allowedAlgs'])) {
            $config['allowedAlgs'] = ['HS256'];
        }

        parent::__construct($registry, $config);
    }

    /**
     * Get user record based on info available in JWT.
     *
     * @param \Cake\Http\ServerRequest $request The request object.
     * @param \Cake\Http\Response $response Response object.
     * @return false|array User record array or false on failure.
     */
    public function authenticate(ServerRequest $request, Response $response)
    {
        return $this->getUser($request);
    }

    /**
     * Get user record based on info available in JWT.
     *
     * @param \Cake\Http\ServerRequest $request Request object.
     * @return false|array User record array or false on failure.
     */
    public function getUser(ServerRequest $request)
    {
        $payload = $this->getPayload($request);

        if (empty($payload)) {
            return false;
        }

        if (!$this->_config['queryDatasource']) {
            return json_decode(json_encode($payload), true);
        }

        if (!isset($payload->sub)) {
            return false;
        }

        $user = $this->_findUser((string)$payload->sub);
        if (!$user) {
            return false;
        }

        unset($user[$this->_config['fields']['password']]);

        return $user;
    }

    /**
     * Get payload data.
     *
     * @param \Cake\Http\ServerRequest|null $request Request instance or null
     * @return object|null Payload object on success, null on failurec
     */
    public function getPayload(?ServerRequest $request = null)
    {
        if (!$request) {
            return $this->_payload;
        }

        $payload = null;

        $token = $this->getToken($request);
        if ($token) {
            $payload = $this->_decode($token);
        }

        return $this->_payload = $payload;
    }

    /**
     * Get token from header or query string.
     *
     * @param \Cake\Http\ServerRequest|null $request Request object.
     * @return string|null Token string if found else null.
     */
    public function getToken(?ServerRequest $request = null)
    {
        $config = $this->_config;

        if ($request === null) {
            return $this->_token;
        }

        $header = $request->getHeaderLine($config['header']);
        if ($header && stripos($header, $config['prefix']) === 0) {
            return $this->_token = str_ireplace($config['prefix'] . ' ', '', $header);
        }

        if (!empty($this->_config['cookie'])) {
            $token = $request->getCookie($this->_config['cookie']);
            if ($token !== null) {
                /** @psalm-suppress PossiblyInvalidCast */
                $token = (string)$token;
            }

            return $this->_token = $token;
        }

        if (!empty($this->_config['parameter'])) {
            $token = $request->getQuery($this->_config['parameter']);
            if ($token !== null) {
                /** @psalm-suppress PossiblyInvalidCast */
                $token = (string)$token;
            }

            return $this->_token = $token;
        }

        return $this->_token;
    }

    /**
     * Decode JWT token.
     *
     * @param string $token JWT token to decode.
     * @return object|null The JWT's payload as a PHP object, null on failure.
     */
    protected function _decode(string $token)
    {
        $config = $this->_config;
        try {
            $payload = JWT::decode(
                $token,
                $config['key'] ?: Security::getSalt(),
                $config['allowedAlgs']
            );

            return $payload;
        } catch (Exception $e) {
            if (Configure::read('debug')) {
                throw $e;
            }
            $this->_error = $e;
        }

        return null;
    }

    /**
     * Handles an unauthenticated access attempt. Depending on value of config
     * `unauthenticatedException` either throws the specified exception or returns
     * null.
     *
     * @param \Cake\Http\ServerRequest $request A request object.
     * @param \Cake\Http\Response $response A response object.
     * @throws \Cake\Http\Exception\UnauthorizedException Or any other
     *   configured exception.
     * @return void
     */
    public function unauthenticated(ServerRequest $request, Response $response)
    {
        if (!$this->_config['unauthenticatedException']) {
            return;
        }

        $message = $this->_error
            ? $this->_error->getMessage()
            : $this->_registry->get('Auth')->getConfig('authError');

        /** @var \Throwable $exception */
        $exception = new $this->_config['unauthenticatedException']($message);
        throw $exception;
    }
}


================================================
FILE: tests/Fixture/GroupsFixture.php
================================================
<?php
namespace ADmad\JwtAuth\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

/**
 * Class GroupsFixture.
 */
class GroupsFixture extends TestFixture
{
    /**
     * fields property.
     *
     * @var array
     */
    public $fields = [
        'id' => ['type' => 'integer'],
        'title' => ['type' => 'string'],
        '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
    ];

    /**
     * records property.
     *
     * @var array
     */
    public $records = [
        ['title' => 'admin'],
        ['title' => 'moderator'],
    ];
}


================================================
FILE: tests/Fixture/UsersFixture.php
================================================
<?php
namespace ADmad\JwtAuth\Test\Fixture;

use Cake\TestSuite\Fixture\TestFixture;

class UsersFixture extends TestFixture
{
    /**
     * fields property.
     *
     * @var array
     */
    public $fields = [
        'id' => ['type' => 'integer'],
        'group_id' => ['type' => 'integer', 'null' => false],
        'user_name' => ['type' => 'string', 'null' => false],
        'email' => ['type' => 'string', 'null' => false],
        'password' => ['type' => 'string', 'null' => false],
        'created' => 'datetime',
        'updated' => 'datetime',
        '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]],
    ];

    /**
     * records property.
     *
     * @var array
     */
    public $records = [
        [
            'group_id' => 1, 'user_name' => 'admad',
            'email' => 'admad@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
            'created' => '2014-03-17 01:18:23', 'updated' => '2014-03-17 01:20:31',
        ],
        [
            'group_id' => 2, 'user_name' => 'mark',
            'email' => 'mark@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
            'created' => '2014-03-17 01:16:23', 'updated' => '2014-03-17 01:18:31',
        ],
        [
            'group_id' => 2, 'user_name' => 'jose',
            'email' => 'jose@example.com', 'password' => '5f4dcc3b5aa765d61d8327deb882cf99',
            'created' => '2014-03-17 01:20:23', 'updated' => '2014-03-17 01:22:31',
        ],
    ];
}


================================================
FILE: tests/TestCase/Auth/JwtAuthenticateTest.php
================================================
<?php
declare(strict_types=1);

namespace ADmad\JwtAuth\Auth\Test\TestCase\Auth;

use ADmad\JwtAuth\Auth\JwtAuthenticate;
use Cake\Controller\ComponentRegistry;
use Cake\Core\Configure;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Http\Response;
use Cake\Http\ServerRequest;
use Cake\I18n\FrozenTime;
use Cake\I18n\Time;
use Cake\TestSuite\TestCase;
use Cake\Utility\Security;
use DomainException;
use Firebase\JWT\JWT;

/**
 * Test case for JwtAuthentication.
 */
class JwtAuthenticateTest extends TestCase
{
    protected $fixtures = [
        'plugin.ADmad\JwtAuth.Users',
        'plugin.ADmad\JwtAuth.Groups',
    ];

    /**
     * setup.
     *
     * @return void
     */
    public function setUp(): void
    {
        parent::setUp();

        Security::setSalt('secret-key');

        $this->Registry = new ComponentRegistry();
        $this->auth = new JwtAuthenticate($this->Registry, [
            'userModel' => 'Users',
        ]);
        $this->Registry->Auth = $this->auth;

        $this->token = JWT::encode(['sub' => 1], Security::getSalt());

        $this->response = $this->getMockBuilder(Response::class)
            ->getMock();
    }

    /**
     * testConfig.
     *
     * @return void
     */
    public function testConfig()
    {
        $auth = new JwtAuthenticate($this->Registry, []);
        $this->assertEquals(['HS256'], $auth->getConfig('allowedAlgs'));

        $auth = new JwtAuthenticate($this->Registry, [
            'allowedAlgs' => ['RS256'],
        ]);
        $this->assertEquals(['RS256'], $auth->getConfig('allowedAlgs'));
    }

    /**
     * test authenticate token as query parameter.
     *
     * @return void
     */
    public function testAuthenticateTokenParameter()
    {
        $request = new ServerRequest();

        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);

        $expected = [
            'id' => 1,
            'group_id' => 1,
            'user_name' => 'admad',
            'email' => 'admad@example.com',
            'created' => new FrozenTime('2014-03-17 01:18:23'),
            'updated' => new FrozenTime('2014-03-17 01:20:31'),
        ];
        $request = new ServerRequest(['url' => 'posts/index?token=' . $this->token]);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);

        $this->auth->setConfig('parameter', 'tokenname');
        $request = new ServerRequest(['url' => 'posts/index?tokenname=' . $this->token]);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);

        $request = new ServerRequest(['url' => 'posts/index?wrongtoken=' . $this->token]);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);
    }

    /**
     * test authenticate token as request header.
     *
     * @return void
     */
    public function testAuthenticateTokenHeader()
    {
        $request = new ServerRequest();

        $expected = [
            'id' => 1,
            'group_id' => 1,
            'user_name' => 'admad',
            'email' => 'admad@example.com',
            'created' => new FrozenTime('2014-03-17 01:18:23'),
            'updated' => new FrozenTime('2014-03-17 01:20:31'),
        ];
        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $this->token);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);

        $request = $request->withEnv('HTTP_AUTHORIZATION', 'WrongBearer ' . $this->token);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);

        $this->expectException('UnexpectedValueException');
        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer foobar');
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);
    }

    /**
     * test authenticate no token present in header "parameter" option disabled.
     *
     * @return void
     */
    public function testAuthenticateNoHeaderWithParameterDisabled()
    {
        $request = new ServerRequest();

        $this->auth = new JwtAuthenticate($this->Registry, [
            'userModel' => 'Users',
            'parameter' => false,
        ]);

        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);

        $request = new ServerRequest(['url' => 'posts/index?token=' . $this->token]);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);
    }

    /**
     * test returning payload without querying database.
     *
     * @return void
     */
    public function testQueryDatasourceFalse()
    {
        $expected = [
                'id' => 99,
                'username' => 'ADmad',
                'group' => ['name' => 'admin'],
        ];
        $token = JWT::encode($expected, Security::getSalt());
        $this->auth->setConfig('queryDatasource', false);

        $request = new ServerRequest();
        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $token);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);

        $request = new ServerRequest(['url' => 'posts/index?token=' . $token]);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);
    }

    /**
     * test for valid token but no matching user found in db.
     *
     * @return void
     */
    public function testWithValidTokenButNoUserInDb()
    {
        $token = JWT::encode(['id' => 4], Security::getSalt());

        $request = new ServerRequest();
        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $token);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);

        $request = new ServerRequest(['url' => 'posts/index?token=' . $token]);
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);
    }

    /**
     * Test that authenticated() always returns false.
     *
     * @return void
     */
    public function testAuthenticated()
    {
        $this->assertFalse($this->auth->authenticate(new ServerRequest(), $this->response));
    }

    /**
     * test that with debug off for invalid token exception from JWT::decode()
     * is re-thrown.
     */
    public function testExceptionForInvalidToken()
    {
        $this->expectException(DomainException::class);

        $request = new ServerRequest();
        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer this.is.invalid');

        $this->auth->getUser($request, $this->response);
    }

    /**
     * testUnauthenticated
     */
    public function testUnauthenticated()
    {
        $this->Registry->Auth->setConfig('authError', 'Auth error');

        $exceptionClass = UnauthorizedException::class;
        if (!class_exists(UnauthorizedException::class)) {
            $exceptionClass = 'Cake\Network\Exception\UnauthorizedException';
        }
        $this->expectException($exceptionClass);
        $this->expectExceptionMessage('Auth error');

        $this->auth->unauthenticated(new ServerRequest(), $this->response);
    }

    /**
     * test unauthenticated() doesn't throw exception is config `unauthenticatedException`
     * is set to falsey value.
     */
    public function testUnauthenticatedNoException()
    {
        $this->auth->setConfig('unauthenticatedException', false);
        $this->assertNull($this->auth->unauthenticated(new ServerRequest(), $this->response));
    }

    /**
     * test that getUser() returns false instead of throwing exception with
     * invalid token when debug is off.
     *
     * @return void
     */
    public function testWithInvalidToken()
    {
        Configure::write('debug', false);
        $request = new ServerRequest();

        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer this.is.invalid');
        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);
    }

    /**
     * test using custom key for decoding jwt.
     *
     * @return void
     */
    public function testCustomKey()
    {
        $key = 'my-custom-key';
        $auth = new JwtAuthenticate($this->Registry, [
            'key' => $key,
            'queryDatasource' => false,
        ]);

        $payload = ['sub' => 100];
        $token = JWT::encode($payload, $key);

        $request = new ServerRequest();
        $request = $request->withEnv('HTTP_AUTHORIZATION', 'Bearer ' . $token);
        $result = $auth->getUser($request, $this->response);
        $this->assertEquals($payload, $result);

        $request = new ServerRequest(['url' => '/posts/index?token=' . $token]);
        $result = $auth->getUser($request, $this->response);
        $this->assertEquals($payload, $result);
    }

    /**
     * test authenticate token as cookie.
     *
     * @return void
     */
    public function testAuthenticateCookie()
    {
        $request = new ServerRequest();

        $this->auth = new JwtAuthenticate($this->Registry, [
            'userModel' => 'Users',
            'cookie' => 'jwt',
        ]);

        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);

        $expected = [
            'id' => 1,
            'group_id' => 1,
            'user_name' => 'admad',
            'email' => 'admad@example.com',
            'created' => new Time('2014-03-17 01:18:23'),
            'updated' => new Time('2014-03-17 01:20:31'),
        ];

        $request = new ServerRequest([
            'url' => 'posts/index',
            'cookies' => ['jwt' => $this->token],
        ]);

        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);

        $this->auth->setConfig('cookie', 'tokenname');
        $request = new ServerRequest([
            'url' => 'posts/index',
            'cookies' => ['tokenname' => $this->token],
        ]);

        $result = $this->auth->getUser($request, $this->response);
        $this->assertEquals($expected, $result);

        $request = new ServerRequest([
            'url' => 'posts/index',
            'cookies' => ['wrongtoken' => $this->token],
        ]);

        $result = $this->auth->getUser($request, $this->response);
        $this->assertFalse($result);
    }
}


================================================
FILE: tests/bootstrap.php
================================================
<?php
declare(strict_types=1);

/*
 * Test suite bootstrap
 *
 * This function is used to find the location of CakePHP whether CakePHP
 * has been installed as a dependency of the plugin, or the plugin is itself
 * installed as a dependency of an application.
 */
$findRoot = function ($root) {
    do {
        $lastRoot = $root;
        $root = dirname($root);
        if (is_dir($root . '/vendor/cakephp/cakephp')) {
            return $root;
        }
    } while ($root !== $lastRoot);
    throw new Exception('Cannot find the root of the application, unable to run tests');
};
$root = $findRoot(__FILE__);
unset($findRoot);
chdir($root);
require $root . '/vendor/cakephp/cakephp/tests/bootstrap.php';
Download .txt
gitextract_afge8016/

├── .editorconfig
├── .gitattributes
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── LICENSE.txt
├── README.md
├── composer.json
├── phpstan.neon
├── phpunit.xml.dist
├── psalm.xml
├── src/
│   └── Auth/
│       └── JwtAuthenticate.php
└── tests/
    ├── Fixture/
    │   ├── GroupsFixture.php
    │   └── UsersFixture.php
    ├── TestCase/
    │   └── Auth/
    │       └── JwtAuthenticateTest.php
    └── bootstrap.php
Download .txt
SYMBOL INDEX (25 symbols across 4 files)

FILE: src/Auth/JwtAuthenticate.php
  class JwtAuthenticate (line 36) | class JwtAuthenticate extends BaseAuthenticate
    method __construct (line 91) | public function __construct(ComponentRegistry $registry, array $config)
    method authenticate (line 120) | public function authenticate(ServerRequest $request, Response $response)
    method getUser (line 131) | public function getUser(ServerRequest $request)
    method getPayload (line 163) | public function getPayload(?ServerRequest $request = null)
    method getToken (line 185) | public function getToken(?ServerRequest $request = null)
    method _decode (line 227) | protected function _decode(string $token)
    method unauthenticated (line 259) | public function unauthenticated(ServerRequest $request, Response $resp...

FILE: tests/Fixture/GroupsFixture.php
  class GroupsFixture (line 9) | class GroupsFixture extends TestFixture

FILE: tests/Fixture/UsersFixture.php
  class UsersFixture (line 6) | class UsersFixture extends TestFixture

FILE: tests/TestCase/Auth/JwtAuthenticateTest.php
  class JwtAuthenticateTest (line 22) | class JwtAuthenticateTest extends TestCase
    method setUp (line 34) | public function setUp(): void
    method testConfig (line 57) | public function testConfig()
    method testAuthenticateTokenParameter (line 73) | public function testAuthenticateTokenParameter()
    method testAuthenticateTokenHeader (line 107) | public function testAuthenticateTokenHeader()
    method testAuthenticateNoHeaderWithParameterDisabled (line 138) | public function testAuthenticateNoHeaderWithParameterDisabled()
    method testQueryDatasourceFalse (line 160) | public function testQueryDatasourceFalse()
    method testWithValidTokenButNoUserInDb (line 185) | public function testWithValidTokenButNoUserInDb()
    method testAuthenticated (line 204) | public function testAuthenticated()
    method testExceptionForInvalidToken (line 213) | public function testExceptionForInvalidToken()
    method testUnauthenticated (line 226) | public function testUnauthenticated()
    method testUnauthenticatedNoException (line 244) | public function testUnauthenticatedNoException()
    method testWithInvalidToken (line 256) | public function testWithInvalidToken()
    method testCustomKey (line 271) | public function testCustomKey()
    method testAuthenticateCookie (line 297) | public function testAuthenticateCookie()
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
  {
    "path": ".editorconfig",
    "chars": 309,
    "preview": "; This file is for unifying the coding style for different editors and IDEs.\n; More information at http://editorconfig.o"
  },
  {
    "path": ".gitattributes",
    "chars": 287,
    "preview": "# Remove files for archives generated using `git archive`\n.editorconfig export-ignore\n.gitattributes export-ignore\n.giti"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1788,
    "preview": "name: CI\n\non: [push, pull_request]\n\njobs:\n  testsuite:\n    runs-on: ubuntu-18.04\n    strategy:\n      fail-fast: false\n  "
  },
  {
    "path": ".gitignore",
    "chars": 58,
    "preview": "/composer.lock\n/phpunit.xml\n/vendor\n.phpunit.result.cache\n"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1080,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015-Present ADmad\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "README.md",
    "chars": 4051,
    "preview": "# CakePHP JWT Authenticate plugin\n\n[![Build Status](https://img.shields.io/github/workflow/status/ADmad/cakephp-jwt-auth"
  },
  {
    "path": "composer.json",
    "chars": 998,
    "preview": "{\n    \"name\": \"admad/cakephp-jwt-auth\",\n    \"type\": \"cakephp-plugin\",\n    \"description\": \"CakePHP plugin for authenticat"
  },
  {
    "path": "phpstan.neon",
    "chars": 77,
    "preview": "parameters:\n\tlevel: 6\n\tcheckMissingIterableValueType: false\n\tpaths:\n\t\t- src/\n"
  },
  {
    "path": "phpunit.xml.dist",
    "chars": 1015,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit\n    colors=\"true\"\n    processIsolation=\"false\"\n    stopOnFailure=\"false\""
  },
  {
    "path": "psalm.xml",
    "chars": 546,
    "preview": "<?xml version=\"1.0\"?>\n<psalm\n    errorLevel=\"2\"\n    resolveFromConfigFile=\"true\"\n    xmlns:xsi=\"http://www.w3.org/2001/X"
  },
  {
    "path": "src/Auth/JwtAuthenticate.php",
    "chars": 8117,
    "preview": "<?php\ndeclare(strict_types=1);\n\nnamespace ADmad\\JwtAuth\\Auth;\n\nuse Cake\\Auth\\BaseAuthenticate;\nuse Cake\\Controller\\Compo"
  },
  {
    "path": "tests/Fixture/GroupsFixture.php",
    "chars": 584,
    "preview": "<?php\nnamespace ADmad\\JwtAuth\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\n/**\n * Class GroupsFixture.\n */\ncl"
  },
  {
    "path": "tests/Fixture/UsersFixture.php",
    "chars": 1507,
    "preview": "<?php\nnamespace ADmad\\JwtAuth\\Test\\Fixture;\n\nuse Cake\\TestSuite\\Fixture\\TestFixture;\n\nclass UsersFixture extends TestFix"
  },
  {
    "path": "tests/TestCase/Auth/JwtAuthenticateTest.php",
    "chars": 10589,
    "preview": "<?php\ndeclare(strict_types=1);\n\nnamespace ADmad\\JwtAuth\\Auth\\Test\\TestCase\\Auth;\n\nuse ADmad\\JwtAuth\\Auth\\JwtAuthenticate"
  },
  {
    "path": "tests/bootstrap.php",
    "chars": 707,
    "preview": "<?php\ndeclare(strict_types=1);\n\n/*\n * Test suite bootstrap\n *\n * This function is used to find the location of CakePHP w"
  }
]

About this extraction

This page contains the full source code of the ADmad/cakephp-jwt-auth GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (31.0 KB), approximately 8.3k tokens, and a symbol index with 25 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!