I'm using HWIOAuthBundle to let an user login with Oauth, I've created a custom user provider which creates an user in case it doesn't exist:
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$attr = $response->getResponse();
switch($response->getResourceOwner()->getName()) {
case 'google':
if(!$user = $this->userRepository->findOneByGoogleId($attr['id'])) {
if(($user = $this->userRepository->findOneByEmail($attr['email'])) && $attr['verified_email']) {
$user->setGoogleId($attr['id']);
if(!$user->getFirstname()) {
$user->setFirstname($attr['given_name']);
}
if(!$user->getLastname()) {
$user->setLastname($attr['family_name']);
}
$user->setGoogleName($attr['name']);
}else{
$user = new User();
$user->setUsername($this->userRepository->createUsernameByEmail($attr['email']));
$user->setEmail($attr['email']);
$user->setFirstname($attr['given_name']);
$user->setLastname($attr['family_name']);
$user->setPassword('');
$user->setIsActive(true);
$user->setGoogleId($attr['id']);
$user->setGoogleName($attr['name']);
$user->addGroup($this->groupRepository->findOneByRole('ROLE_USER'));
$this->entityManager->persist($user);
}
}
break;
case 'facebook':
if(!$user = $this->userRepository->findOneByFacebookId($attr['id'])) {
if(($user = $this->userRepository->findOneByEmail($attr['email'])) && $attr['verified']) {
$user->setFacebookId($attr['id']);
if(!$user->getFirstname()) {
$user->setFirstname($attr['first_name']);
}
if(!$user->getLastname()) {
$user->setLastname($attr['last_name']);
}
$user->setFacebookUsername($attr['username']);
}else{
$user = new User();
$user->setUsername($this->userRepository->createUsernameByEmail($attr['email']));
$user->setEmail($attr['email']);
$user->setFirstname($attr['first_name']);
$user->setLastname($attr['last_name']);
$user->setPassword('');
$user->setIsActive(true);
$user->setFacebookId($attr['id']);
$user->setFacebookUsername($attr['username']);
$user->addGroup($this->groupRepository->findOneByRole('ROLE_USER'));
$this->entityManager->persist($user);
}
}
break;
}
$this->entityManager->flush();
if (null === $user) {
throw new AccountNotLinkedException(sprintf("User '%s' not found.", $attr['email']));
}
return $user;
}
The problem is that twitter for example doesn't give the email or i want some additional fields to be included before a new user is created. Is there a way to redirect an user to a "complete registration" form before creating it?
I've tried to add a request listener, that on each request, if the user is logged, checks if the email is there and if it doesn't it redirects to the complete_registration page, but it will redirect also if the user goes to the homepage, to logout or anything else, I want to redirect him only if he tries to access some user restricted pages.
Or better, don't create it until he gives all the required informations.
I've found the solution by myself, I've manually created a new exception:
<?php
namespace Acme\UserBundle\Exception;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use HWI\Bundle\OAuthBundle\Security\Core\Exception\OAuthAwareExceptionInterface;
/**
* IncompleteUserException is thrown when the user isn't fully registered (e.g.: missing some informations).
*
* @author Alessandro Tagliapietra http://www.alexnetwork.it/
*/
class IncompleteUserException extends AuthenticationException implements OAuthAwareExceptionInterface
{
private $user;
private $accessToken;
private $resourceOwnerName;
/**
* {@inheritdoc}
*/
public function setAccessToken($accessToken)
{
$this->accessToken = $accessToken;
}
/**
* {@inheritdoc}
*/
public function getAccessToken()
{
return $this->accessToken;
}
/**
* {@inheritdoc}
*/
public function getResourceOwnerName()
{
return $this->resourceOwnerName;
}
/**
* {@inheritdoc}
*/
public function setResourceOwnerName($resourceOwnerName)
{
$this->resourceOwnerName = $resourceOwnerName;
}
public function setUser($user)
{
$this->user = $user;
}
public function getUser($user)
{
return $this->user;
}
public function serialize()
{
return serialize(array(
$this->user,
$this->accessToken,
$this->resourceOwnerName,
parent::serialize(),
));
}
public function unserialize($str)
{
list(
$this->user,
$this->accessToken,
$this->resourceOwnerName,
$parentData
) = unserialize($str);
parent::unserialize($parentData);
}
}
In this way, in the custom Oauth user provider when i check if an user exist or I create a new user i check if the required fields are missing:
if (!$user->getEmail()) {
$e = new IncompleteUserException("Your account doesn't has a mail set");
$e->setUser($user);
throw $e;
}
In that case the user will be redirected to the login form, with that exception in session, so in the login page I do:
if($error instanceof IncompleteUserException) {
$session->set(SecurityContext::AUTHENTICATION_ERROR, $error);
return $this->redirect($this->generateUrl('register_complete'));
}
And it will be redirected to a form with the $user in the exception so it can ask only for the missing information and then login the user.