Hi,
I'm trying to build a symfony headless drupal application, i made a custom module to handle login, logout and registration process,
this is my custom module:
<?php
/<strong>
* @file
* Contains \Drupal\remoteuser\Controller\RemoteUserController
*/
namespace Drupal\remoteuser\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Entity;
use Drupal\user\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
class RemoteUserController extends ControllerBase
{
public function login() {
$form_state = (new FormState())->setValues($_POST);
\Drupal::formBuilder()->submitForm('\Drupal\user\Form\UserLoginForm', $form_state);
// Check for errors from the from
if ($errors = $form_state->getErrors()) {
// Return errors to notify the client.
return new JsonResponse( array( 'error' => $errors ) );
}
else {
// Return new user session to client.
$uid = \Drupal::service('user.auth')->authenticate($_POST['name'], $_POST['pass']);
$session_manager = \Drupal::service('session_manager');
$session_id = $session_manager->getId();
return new JsonResponse( array( 'uid' => $uid, 'session_id' => $session_id ) );
}
}
public function register() {
// Validate the e-mail address first.
if (!\Drupal::service('email.validator')->isValid($_POST['mail'])) {
return new JsonResponse( array( 'error' => 'Invalid e-mail address' ) );
}
// Create password if it was not provided.
$password = $_POST['pass'] ? $_POST['pass'] : user_password();
/</strong> @var \Drupal\user\Entity\User $user */
$user = entity_create('user', array(
'name' => $_POST['name'],
'mail' => $_POST['mail'],
'pass' => $password,
));
// Validate the object.
$errors = $user->validate();
if ($errors->count() > 0) {
// Return errors to notify the client.
return new JsonResponse( array( 'error' => $errors->__toString() ) );
} else {
// Save new user
$user->save();
// Return new user credentials
return new JsonResponse( array( 'user' => $user->toArray() ) );
}
}
public function logout() {
user_logout();
return new JsonResponse(null);
}
public function session() {
$uid = \Drupal::currentUser()->id();
$session_manager = \Drupal::service('session_manager');
$session_id = $session_manager->getId();
// $permissions = $perms = array_keys(\Drupal::service('user.permissions')->getPermissions());
return new JsonResponse( array( 'uid' => $uid, 'session_id' => $session_id ) );
}
}
?>
and this is the routing yml file:
remoteuser.user_login:
path: '/api/user/login'
defaults:
_title: 'Remote User Login API'
_controller: '\Drupal\remoteuser\Controller\RemoteUserController::login'
requirements:
_permission: 'access content'
remoteuser.user_register:
path: '/api/user/register'
defaults:
_title: 'Remote User Register API'
_controller: '\Drupal\remoteuser\Controller\RemoteUserController::register'
requirements:
_permission: 'access content'
remoteuser.user_session:
path: '/api/user/session'
defaults:
_title: 'Remote User Session API'
_controller: '\Drupal\remoteuser\Controller\RemoteUserController::session'
requirements:
_permission: 'access content'
remoteuser.user_logout:
path: '/api/user/logout'
defaults:
_title: 'Remote User Logout API'
_controller: '\Drupal\remoteuser\Controller\RemoteUserController::logout'
requirements:
_permission: 'access content'
the problem is with the login process, when i send a POST request to /api/user/login, it returns successfully a json response with uid and session_id
{"uid":"2","session_id":"s4tfYEYGGDZ7MJqcR1f8sSde4sTwcfZNua5WS5s8nOc"}
but when i check the user login statusby sending a GET request to /api/user/session, it returns:
{"uid":0,"session_id":"eBkwCGKv8qdetpPT42uCiSKqf8WS6UkLTuyNsk4hxLk"}
this problem is happening only when i execute the requests inside the application in symfony with curl, when i execute the same process in Postman, /api/user/session returns successfully the same result from /api/user/login
{"uid":"2","session_id":"s4tfYEYGGDZ7MJqcR1f8sSde4sTwcfZNua5WS5s8nOc"}
base url of symfony: http://app.local/web/
and for drupal, the url is : http://app.local/headless
can you help figure out what's the problem with my code please ?
Comments
Don't use UserLoginForm for that
As a proof of concept I did something very similar. I don't use login form at all. The key part is calling ugly static user_login_finalize($account) function which will make sure that current session is set up correctly.
<?php
use Drupal\Core\Controller\ControllerBase;
use Drupal\user\UserAuthInterface;
use Drupal\user\UserStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class UserController extends ControllerBase {
protected $userAuth;
protected $userStorage;
protected $session;
/**
* @inheritDoc
*/
public function __construct(UserAuthInterface $userAuth, UserStorageInterface $userStorage, SessionInterface $session) {
$this->userAuth = $userAuth;
$this->userStorage = $userStorage;
$this->session = $session;
}
/**
* @inheritDoc
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('user.auth'),
$container->get('entity.manager')->getStorage('user'),
$container->get('session')
);
}
/**
* Authenticate user.
*
* @param string $username
* @param string $password
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function login($username, $password){
// TODO: Implement flood protection the same way as Drupal\user\Form\UserLoginForm does
$uid = $this->userAuth->authenticate($username, $password);
if ($uid) {
$account = $this->userStorage->load($uid);
// Damn legacy Drupal code!
// This line is needed to set up current session for newly logged in user.
user_login_finalize($account);
$data = [
'uid' => $uid,
'username' => $username,
'sessionName' => $this->session->getName(),
'sessionId' => $this->session->getId(),
'result' => true,
];
} else {
$data = ['result' => false];
}
$response = new JsonResponse($data);
return $response;
}
/**
* Destroy user session. Works only for authenticated users.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
*/
public function logout() {
// Damn legacy Drupal code!
user_logout();
$response = new JsonResponse(['result' => true]);
return $response;
}
}
?>
Pass a session id properly
I just realised I forgot to answer this:
This might be happening because you're not providing a session cookie correctly. First of all you need to know what is the session name, then you assign a session id for it. Session name is mangled string and you can get it from session service by calling
getName()
function.And more over, you have to provide session id inside a cookie, so making manual curl requests make sure you set cookie header properly.
Thanks for your the code
Thanks for your the code example, I need to implement something similar in an EmberJS application, if everything works I'll make a blog post about this.