CakePHP 3 JWT-Auth gives 401 Unauthorized error - cakephp

I'm using CakePHP 3.6 and JWT Auth to enable token-based authentication in my application and frontend is written in Angular 6.
My login controller is like
<?php
namespace App\Controller\Api;
use Cake\Event\Event;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Utility\Security;
use Crud\Controller\Component\CrudComponent;
use Firebase\JWT\JWT;
class UsersController extends AppController
{
public function initialize()
{
parent::initialize();
$this->Auth->allow(['add', 'token']);
}
public function token()
{
$user = $this->Auth->identify();
if (!$user) {
throw new UnauthorizedException('Invalid username or password');
}
$this->set([
'success' => true,
'data' => [
'token_type' => 'Bearer',
'expires_in' => 604800,
'token' => JWT::encode([
'sub' => $user['id'],
// 'exp' => time() + 604800
],
Security::getSalt())
],
'_serialize' => ['success', 'data']
]);
}
}
AppController.php contents
namespace App\Controller\Api;
<?php
use Cake\Controller\Controller;
class AppController extends Controller
{
use \Crud\Controller\ControllerTrait;
public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Crud.Crud', [
'actions' => [
'Crud.Index',
'Crud.View',
'Crud.Add',
'Crud.Edit',
'Crud.Delete'
],
'listeners' => [
'Crud.Api',
'Crud.ApiPagination'
]
]);
$this->loadComponent('Auth', [
'storage' => 'Memory',
'authenticate' => [
'Form' => [
'fields' => [
'username' => 'email',
'password' => 'password'
],
'finder' => 'auth'
],
'ADmad/JwtAuth.Jwt' => [
'parameter' => 'token',
'userModel' => 'Users',
'finder' => 'auth',
'fields' => [
'username' => 'id'
],
'queryDatasource' => true
]
],
'unauthorizedRedirect' => false,
'checkAuthIn' => 'Controller.initialize'
]);
}
}
On sending request from the angular application to generate token works fine and following response is received.
But when using the token to send the request to other endpoints giving an error
401: Unauthorized access
The request/response header has token
What I tried?
I tried with disabling exp while generating an access token.
tried with disabling debug in CakePHP application.
It is working great when CakePHP server application is run locally.

in your .htaccess try this rule (if mod_rewrite is activated) :
SetEnvIf Authorization "(.*)" HTTP_AUTHORIZATION=$1
With the Bitnami stack of LAMP (on EC2 AWS instance for example), the php-fdm module filter the header of every requests, and the "authorization" header is screwed up.
With this line, you can force to create a $_HTTP variable with the original Authorization header.
Regards

Check in cakephp code if you are receiving the AUTHORIZATION in headers.

Related

Cakephp authentication plugin how can I implement login in admin prefix?

I'm trying to implement cakephp authentication plugin in admin prefix
Route I have written for admin prefix
$routes->prefix('admin', function (RouteBuilder $routes) {
$routes->connect('/',['controller'=>'AdminUsers','action'=>'login']);
$routes->fallbacks(DashedRoute::class);
});
In application.php , I have followed everything That mentioned in authentication documentation
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => Router::url('/admin'),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'userModel' => 'AdminUsers',
'loginUrl' => Router::url('/admin'),
]);
return $authenticationService;
}
I have changed users model to AdminUsers for database table admin_users
Now in admin/appController.php
I have loadComponent in initialize method
$this->loadComponent('Authentication.Authentication');
In before filter method I have added
$this->Authentication->allowUnauthenticated(['login']);
Now after submit login form I am getting error
Table class for alias Users could not be found.
In getAuthenticationService method I have changed model Users to AdminUsers. Why it's going for Users model rather then AdminUsers model ?
My Table/AdminUsersTable.php table class look likes
<?php
declare(strict_types=1);
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AdminUsersTable extends Table
{
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('admin_users');
--------
After ndm comment I am able to change default model in application.php , But how can I change it in Admin/AppController.php ? Code that I have tried.
public function beforeFilter(EventInterface $event)
{
$service = new AuthenticationService();
$service->loadIdentifier('Authentication.Password', [
'resolver' => [
'className' => 'Authentication.Orm',
'userModel' => 'AdminUsers',
],
]);
$this->Authentication->allowUnauthenticated(['login','signup']);
$this->viewBuilder()->setLayout('admin');
}

cakedc/users autologin after registration

I'm using the cakephp 4.2.6 with the cakedc/users plugin. I'd like to automatically login the user after registration without the need to first activate the account by clicking on the link in e-mail.
My idea is to creating an Event-Listener and listening to the Users.Global.afterRegister Event, but then I don't know how to login the user with the new Authentication.
this is what I ended up with and it seems to work so far:
<?php
namespace App\Event;
use Authentication\AuthenticationService;
use Authentication\AuthenticationServiceInterface;
use Authentication\AuthenticationServiceProviderInterface;
use Authentication\Middleware\AuthenticationMiddleware;
use Psr\Http\Message\ServerRequestInterface;
use Cake\Event\EventListenerInterface;
use Cake\Datasource\FactoryLocator;
use Cake\Log\Log;
class UserEventListener implements EventListenerInterface
{
public function implementedEvents(): array
{
return [
'Users.Global.afterRegister' => 'autoLogin',
];
}
public function autoLogin($event)
{
$user = $usersTable->get($event->getData('user')->id);
$request = $event->getSubject()->getRequest();
$response = $event->getSubject()->getResponse();
$authenticationService = $this->getAuthenticationService($request);
$authenticationService->persistIdentity($request, $response, $user);
}
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$authenticationService = new AuthenticationService([
'unauthenticatedRedirect' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
'queryParam' => 'redirect',
]);
// Load identifiers, ensure we check email and password fields
$authenticationService->loadIdentifier('Authentication.Password', [
'fields' => [
'username' => 'email',
'password' => 'password',
]
]);
// Load the authenticators, you want session first
$authenticationService->loadAuthenticator('Authentication.Session');
// Configure form data check to pick email and password
$authenticationService->loadAuthenticator('Authentication.Form', [
'fields' => [
'username' => 'email',
'password' => 'password',
],
'loginUrl' => \Cake\Routing\Router::url([
'controller' => 'Users',
'action' => 'login',
'plugin' => null,
'prefix' => null
]),
]);
return $authenticationService;
}
}
There is a similar use case covered in the plugin documentation https://github.com/CakeDC/users/blob/master/Docs/Documentation/Events.md
Your approach looks correct to me, using the afterRegister event to create the listener in a controller where you have access to the Authenticatication component,
// note this example is handling auto-login after the user validated the email sent (click the validation token link sent to his email address)
EventManager::instance()->on(
\CakeDC\Users\Plugin::EVENT_AFTER_EMAIL_TOKEN_VALIDATION,
function($event){
$users = $this->getTableLocator()->get('Users');
$user = $users->get($event->getData('user')->id);
$this->Authentication->setIdentity($user);
}
);
or using the request to retrieve the authentication service and set the identity like it's done here https://github.com/cakephp/authentication/blob/master/src/Controller/Component/AuthenticationComponent.php#L273

CakePHP - CakeDC Plugin Permission Not Working

I have been using the following plugin (https://github.com/CakeDC/users) for CakePHP, but I can't figure out how to get the permissions working for it. I have followed all instructions, but it seems authorize does not get used at all. Wondering if anyone has any tips on how to make it work. Here is my setup:
bootstrap.php
Configure::write('Users.config', ['users']);
Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]);
AppController.php initialize function
$this->loadComponent('CakeDC/Users.UsersAuth');
config/users.php
$config = [
'Auth' => [
'authError' => 'Did you really think you are allowed to see that?'
]
];
return $config;
config/permissions.php
return [
'Users.SimpleRbac.permissions' => [
[
'role' => '*',
'controller' => 'Pages',
'action' => ['display'],
'allowed' => true
], [
'role' => '*',
'controller' => 'Taxes',
'action' => ['*'],
'allowed' => true
], [
'role' => '*',
'prefix' => 'v1',
'controller' => '*',
'action' => '*',
'allowed' => true
]
]
];
return $config;
Frankly it seems a CakePHP configuration issue, but I am not able to find where that problem is coming from. I say that because even though debug shows the correct file loaded to authorize, it does not get called.
Please ensure you are returning the $config variable in the users.php file and you are initializing the plugin correctly as indicated here https://github.com/CakeDC/users/blob/master/Docs/Documentation/Configuration.md
Configure::write('Users.config', ['users']);
Plugin::load('CakeDC/Users', ['routes' => true, 'bootstrap' => true]);
I've created a test environment here with your provided Auth configuration and it works correctly https://ide.c9.io/steinkel/users-so-42523209
https://nimbus.everhelper.me/client/notes/share/790695/girguwv9x7rttdvu5c4x
Thanks,

Legacy Password Hasher Not Working Migrating CodeIgniter2 to Cake 3

I have a legacy CRM application that I am migrating from Code Igniter 2.x to Cake 3. I am attempting to implement a Legacy Password Hasher and then move everything to the Cake 3 password hasher.
I have been unable to get authentication to work. As you'll see I've had to deviate from various cake defaults, in particular all tables were created as singular names. Not sure if that's causing any issues. It's been an ordeal. If it matters, Admin exists as a plugin.
AppController
namespace Admin\Controller;
use App\Controller\AppController as BaseController;
class AppController extends BaseController
{
public function initialize() {
parent::initialize();
$this->loadComponent('Auth', [
'loginAction' => '/admin/login', // routes to /admin/user/login
'authenticate' => [
'Form' => [
'passwordHasher' => [
'className' => 'Legacy',
],
'fields' => [
'username' => ['email_address','handle'],
'password' => 'password',
],
'userModel' => 'User',
],
],
'storage' => 'Session'
]);
}
}
UserController
if( $this->request->is('post') ){
$this->loadModel('User');
$me = $this->User->find()->where([
'type IN ' => ['superuser','administrator','user','publisher'],
'status IN ' => ['active','suspended'],
'OR' => [
'email_address' => $this->request->data('username'), 'handle' => $this->request->data('username')
]
])->first();
if( $me->status == 'active' ){
$user = $this->Auth->identify();
if( $user ){
$this->Auth->setUser($user);
if( $this->Auth->authenticationProvider()->needsPasswordRehash() ){
$user = $this->User->get($this->Auth->user('id'));
$user->password = $this->request->data('password');
$this->User->save($user);
}
switch($user->type){
case 'superuser':
$url = '/admin/dashboard/super';
break;
case 'admin':
$url = '/admin/dashboard/';
break;
case 'publisher':
$url = '/admin/dashboard/publisher';
break;
case 'property':
$url = '/admin/dashboard/property';
break;
}
return $this->redirect($url);
}
}
$this->Flash->error(__('Username or password is incorrect'), [
'key' => 'auth'
]);
}
LegacyPasswordHasher
namespace App\Auth;
use Cake\Auth\AbstractPasswordHasher;
class LegacyPasswordHasher extends AbstractPasswordHasher
{
public function hash($password){
$salt_length = 16;
$salt = substr($password, 0, $salt_length);
return $salt . sha1($salt . $password);
}
public function check($password, $hashedPassword){
return $this->hash($password) === $hashedPassword;
}
}
The legacy password hasher does not even seem to be called. I did a work around to force authentication by doing my own check and then setting the auth user data. Still after that when rehashing the password using Cakes DefaultPasswordHasher authentication failed.
I figured this out. There were a few issues here. One, I needed to change the configuration to this:
$this->loadComponent('Auth', [
'loginAction' => '/admin/login',
'authenticate' => [
'Form' => [
'finder' => 'auth',
'passwordHasher' => [
'className' => 'Legacy',
],
'fields' => [
'username' => 'email_address',
'password' => 'password',
],
'userModel' => 'User',
],
],
'storage' => 'Session'
]);
Next, the field I posted needed to be email_address and not username. Last, there was a bug in my implementation of Code Igniters password hasher. It ended up needing to look like this:
public function hash($password){
$salt_length = 16;
$salt = substr($this->salt, 0, $salt_length);
return $salt . sha1($salt . $password);
}
public function check($password, $hashedPassword){
$this->salt = $hashedPassword;
return $this->hash($password) === $hashedPassword;
}
I also needed a custom finder query added to UserTable:
public function findAuth(\Cake\ORM\Query $query, array $options)
{
$query
->select(['id', 'email_address','handle','password'])
->where(['User.status' => 'active']);
return $query;
}
To handle users being able to login with a username or email_address I will just override the the posted value if its not an email address by looking up the user by handle prior to auth doing its thing.
I hope this helps someone.

CakePHP 3 - Login Error - Call to a member function identify on boolean

Hello I'm using CakePHP 3 to simple setup a site which some pages of it need user to login first.
It was fine when I put the loadComponent('Auth', blablabla) code in initialize() of AppController.php.
src\Controller\AdminController.php
...
public function login() {
if ($this->request->is('post')) {
$admin = $this->Auth->identify();
if ($admin) {
$this->Auth->setUser($admin);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
...
src\Controller\AppController.php
...
public function initialize() {
parent::initialize();
$this->loadComponent('RequestHandler');
$this->loadComponent('Flash');
$this->loadComponent('Auth', [
'Authenticate' => [
'Form' => [
'userModel' => 'Admin',
'Fields' => [
'username' => 'email',
'password' => 'password'
]
]
],
'loginAction' => [
'controller' => 'Admin',
'action' => 'login',
]
]);
$this->Auth->allow(['display']);
}
...
At this point, I needed to login in order to view all other pages of the site.
But I tried to put this same authentication setup in another controller called JustController, and after I logged in, a fetal error stated
Call to a member function identify() on boolean
has been shown.
It should be possible to setup authentication in other controllers so that the site can have more than 1 set of login system instead of covering whole site by setting up in AppController, doesn't it?
Thank you.

Resources