CakePHP 4 JWT Authenticator for REST API

Are there any JWT authenticator example? I don’t understand documentation clearly.

4 Likes

I need also an example! I’m in you opinion that the documentation is not clear enough! I hope you get an answer! :slight_smile:

3 Likes

Someone please help us…on JWT

1 Like

You follow the getting started by attaching the middleware and return a configured service in Application::getAuthenticationService. Configure JWT with JWT Authenticator and the JWT Subject identifier.

Here’s an example:

  1. Attach Middleware
  2. Implement getAuthentiationService
  3. Configuration
2 Likes

@Schlaefer I’ve implemented the same way you mentioned. Authentication and token generation is working fine but it doesn’t return user data when accessing it using $this->Authentication->getIdentity();.

Is there anything special we have to do in order to set user data?

$id = $this->Authentication->getIdentityData(‘id’);

In order get authenticated user’s data, you just have to set returnPayload option to false in your src/Application.php file where you’ve load JWT authenticator.

The reason behind is clearly mentioned in documentation, if we set returnPayload option to true it will return the token payload directly without going through the JWT Subject Identifier set in Application.php file.

Please find this project as a proof of concept.

1 Like

I need help! (FIXED)

CakePHP 4.2.3

I implemented the code as above, but it still returns an error message and asks for authorization on methods other than login and register.

I’m trying via postman to send a get request with a token query, or as a header as written in the documentation, but I’m out of luck.

Here is my code:

Application.php

public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
    $service = new AuthenticationService();

    $fields = [
        IdentifierInterface::CREDENTIAL_USERNAME => 'email',
        IdentifierInterface::CREDENTIAL_PASSWORD => 'password',
    ];

    // $service->loadAuthenticator('Authentication.Token', [
    //     'queryParam' => 'token',
    //     'header' => 'Authorization',
    //     'tokenPrefix' => 'Bearer',
    // ]);

    // Load the authenticators.
    $service->loadAuthenticator('Authentication.Jwt', [
        'secretKey' => Security::getSalt(),
        'returnPayload' => false,
        'queryParam' => 'token',
        // 'header' => 'Authorization',
        // 'tokenPrefix' => 'Bearer',
        ]);
    $service->loadAuthenticator('Authentication.Form', [
        'fields' => $fields,
        ]);

        // Load identifiers
    $service->loadIdentifier('Authentication.JwtSubject');
    $service->loadIdentifier('Authentication.Password', [
        'returnPayload' => false,
        'fields' => $fields,
    ]);

    return $service;
}

Users class

class UsersController extends AppController
{
    /**
     * @inheritDoc
     */
    public function beforeFilter(\Cake\Event\EventInterface $event)
    {
        parent::beforeFilter($event);
        // Configure the login action to not require authentication, preventing
        // the infinite redirect loop issue
        $this->Authentication->addUnauthenticatedActions([
            'login',
            'register',
            ]);
    }

/**
 * Index method
 *
 * @return \Cake\Http\Response|null|void Renders view
 */
public function index()
{
    $this->paginate = [
        'contain' => ['Companies'],
    ];
    $users = $this->paginate($this->Users);
    $this->set('users', $users);
    $this->viewBuilder()->setOption('serialize', ['users']);
}

/**
 * Users Login
 *
 * @return \Cake\Http\Response|null|void
 */
public function login()
{
    $this->request->allowMethod(['get', 'post']);

    $result = $this->Authentication->getResult();
    if ($result->isValid()) {
        $user = $result->getData();
        $payload = [
            'sub' => $user->id,
            'exp' => time() + 60,
        ];
        $json = [
            'token' => JWT::encode($payload, Security::getSalt(), 'HS256'),
        ];
    } else {
        $this->response = $this->response->withStatus(401);
        $json = [];
    }
    $this->set(compact('json'));
    $this->viewBuilder()->setOption('serialize', 'json');
}

/**
 * Register method
 *
 * @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise.
 */
public function register()
{
    $user = $this->Users->newEmptyEntity();
    $message = null;
    if ($this->getRequest()->is(['post', 'put'])) {
        $user = $this->Users->patchEntity($user, $this->request->getData());
        if ($this->Users->save($user)) {
            $message = __('The user has been saved.');
        } else {
            $message = __('The user could not be saved. Please, try again.');
        }
    }
    $this->set(compact('user', 'message'));
    $this->viewBuilder()->setOption('serialize', ['user', 'message']);
}

I successfully register a new user, also the login returns the token, but when I visit for example http://localhost/users.json?token= TOKEN_HERE

{
"message": "Authentication is required to continue",
"url": "/users.json?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.........",
"code": 401,
"file": "/app/vendor/cakephp/authentication/src/Controller/Component/AuthenticationComponent.php",
"line": 177
}

Fixed

login action:

'exp' => time() + 604800,

and

 $service->loadAuthenticator('Authentication.Jwt', [
        'secretKey' => Security::getSalt(),
        'queryParam' => 'token',
        'header' => 'Authorization',
        'tokenPrefix' => 'Bearer', // postman sends a tokenPrefix Bearer with the first capital letter
        'returnPayload' => false,
    ]);

FWIW there are plugins available for this too: