1
votes

i was learning OAuth2 in order to login an account with Google+ , Facebook, etc. on a web site.

I have followed a tutorial about it, and i have an issue when i clicked on my button "Connect with Google+" : Image1

In the tutorial, he created a User.php but i had already one with FOS User.

Here is my code :

User.php :

    <?php
// src/AppBundle/Entity/User.php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;


/**
 * @ORM\Entity
 * @ORM\Table(name="fos_user")
 */
class User extends BaseUser implements UserInterface
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string")
     */
    protected $nom;

    /**
     * @ORM\Column(type="string")
     */
    protected $prenom;

    /**
     * @ORM\Column(type="string")
     */
    protected $nationalite;

    /**
     * @ORM\Column(type="datetime")
     */
    protected $birthday;

    /**
     * @ORM\Column(type="string")
     */
    protected $sexe;

    /**
     * @ORM\OneToOne(targetEntity="App\Entity\AnnonceColocation", mappedBy="User", cascade={"persist", "remove"})
     */
    private $annonceColocation;

    /**
     * @ORM\OneToOne(targetEntity="App\Entity\AnnonceColocataire", mappedBy="User", cascade={"persist", "remove"})
     */
    private $annonceColocataire;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Message", mappedBy="expediteur", orphanRemoval=true)
     */
    private $sendMessage;

    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Message", mappedBy="destinataire", orphanRemoval=true)
     */
    private $receivedMessage;

    public function __construct()
    {
        parent::__construct();
        $this->sendMessage = new ArrayCollection();
        $this->receivedMessage = new ArrayCollection();
        // your own logic
    }

    /**
     * @return mixed
     */
    public function getNom()
    {
        return $this->nom;
    }

    /**
     * @param mixed $nom
     */
    public function setNom($nom)
    {
        $this->nom = $nom;
    }

    /**
     * @return mixed
     */
    public function getPrenom()
    {
        return $this->prenom;
    }

    /**
     * @param mixed $prenom
     */
    public function setPrenom($prenom)
    {
        $this->prenom = $prenom;
    }

    public function getNationalite()
    {
        return $this->nationalite;
    }

    /**
     * @param mixed $nationalite
     */
    public function setNationalite($nationalite)
    {
        $this->nationalite = $nationalite;
    }

    public function getBirthday()
    {
        return $this->birthday;
    }

    /**
     * @param mixed $birthday
     */
    public function setBirthday($birthday)
    {
        $this->birthday = $birthday;
    }

    public function getSexe()
    {
        return $this->sexe;
    }

    /**
     * @param mixed $sexe
     */
    public function setSexe($sexe)
    {
        $this->sexe = $sexe;
    }

    public function getPicture()
    {
        return $this->picture;
    }

    /**
     * @param mixed $picture
     */
    public function setPicture($picture)
    {
        $this->picture = $picture;
    }

    public function getAnnonceColocation(): ?AnnonceColocation
    {
        return $this->annonceColocation;
    }

    public function setAnnonceColocation(AnnonceColocation $annonceColocation): self
    {
        $this->annonceColocation = $annonceColocation;

        // set (or unset) the owning side of the relation if necessary
        $newUser = $annonceColocation === null ? null : $this;
        if ($newUser !== $annonceColocation->getUser()) {
            $annonceColocation->setUser($newUser);
        }

        return $this;
    }

    public function getAnnonceColocataire(): ?AnnonceColocataire
    {
        return $this->annonceColocataire;
    }

    public function setAnnonceColocataire(?AnnonceColocataire $annonceColocataire): self
    {
        $this->annonceColocataire = $annonceColocataire;

        // set (or unset) the owning side of the relation if necessary
        $newUser = $annonceColocataire === null ? null : $this;
        if ($newUser !== $annonceColocataire->getUser()) {
            $annonceColocataire->setUser($newUser);
        }

        return $this;
    }

    /**
     * @return Collection|Message[]
     */
    public function getSendMessage(): Collection
    {
        return $this->sendMessage;
    }

    public function addSendMessage(Message $sendMessage): self
    {
        if (!$this->sendMessage->contains($sendMessage)) {
            $this->sendMessage[] = $sendMessage;
            $sendMessage->setExpediteur($this);
        }

        return $this;
    }

    public function removeSendMessage(Message $sendMessage): self
    {
        if ($this->sendMessage->contains($sendMessage)) {
            $this->sendMessage->removeElement($sendMessage);
            // set the owning side to null (unless already changed)
            if ($sendMessage->getExpediteur() === $this) {
                $sendMessage->setExpediteur(null);
            }
        }

        return $this;
    }

    /**
     * @return Collection|Message[]
     */
    public function getReceivedMessage(): Collection
    {
        return $this->receivedMessage;
    }

    public function addReceivedMessage(Message $receivedMessage): self
    {
        if (!$this->receivedMessage->contains($receivedMessage)) {
            $this->receivedMessage[] = $receivedMessage;
            $receivedMessage->setDestinataire($this);
        }

        return $this;
    }

    public function removeReceivedMessage(Message $receivedMessage): self
    {
        if ($this->receivedMessage->contains($receivedMessage)) {
            $this->receivedMessage->removeElement($receivedMessage);
            // set the owning side to null (unless already changed)
            if ($receivedMessage->getDestinataire() === $this) {
                $receivedMessage->setDestinataire(null);
            }
        }

        return $this;
    }

    public function getRoles()
    {
        return array('ROLE_USER');
    }

    /**
     * Returns the password used to authenticate the user.
     *
     * This should be the encoded password. On authentication, a plain-text
     * password will be salted, encoded, and then compared to this value.
     *
     * @return string The password
     */
    public function getPassword()
    {
        return null;
    }

    /**
     * Returns the salt that was originally used to encode the password.
     *
     * This can return null if the password was not encoded using a salt.
     *
     * @return string|null The salt
     */
    public function getSalt()
    {
        return null;
    }

    /**
     * Returns the username used to authenticate the user.
     *
     * @return string The username
     */
    public function getUsername()
    {
        return $this->email;
    }

    /**
     * Removes sensitive data from the user.
     *
     * This is important if, at any given point, sensitive information like
     * the plain-text password is stored on this object.
     */
    public function eraseCredentials()
    {
        return null;
    }
}

UserProvider.php :

<?php
/**
 * Created by IntelliJ IDEA.
 * User: mert
 * Date: 12/18/17
 * Time: 12:58 PM
 */

namespace App\Security;


use Doctrine\ORM\EntityManagerInterface;
use App\Entity\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserProvider implements UserProviderInterface
{
    private $entityManager;

    /**
     * UserProvider constructor.
     * @param EntityManagerInterface $entityManager
     * @internal param Client $httpClient
     * @internal param UserOptionService $userOptionService
     * @internal param ProjectService $projectService
     * @internal param SessionService $sessionService
     * @internal param Session $session
     * @internal param UserOptionService $userOptionsService
     */
    public function __construct(EntityManagerInterface $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    /**
     * Loads the user for the given username.
     *
     * This method must throw UsernameNotFoundException if the user is not
     * found.
     *
     * @param string $username The username
     *
     * @return UserInterface
     *
     * @throws \Doctrine\ORM\NonUniqueResultException
     */
    public function loadUserByUsername($username)
    {
        return $this->entityManager->createQueryBuilder('u')
            ->where('u.email = :email')
            ->setParameter('email', $username)
            ->getQuery()
            ->getOneOrNullResult();
    }

    /**
     * Refreshes the user.
     *
     * It is up to the implementation to decide if the user data should be
     * totally reloaded (e.g. from the database), or if the UserInterface
     * object can just be merged into some internal array of users / identity
     * map.
     *
     * @param UserInterface $user
     * @return UserInterface
     *
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(
                sprintf('Instances of "%s" are not supported.', get_class($user))
            );
        }
        return $user;
    }

    /**
     * Whether this provider supports the given user class.
     *
     * @param string $class
     *
     * @return bool
     */
    public function supportsClass($class)
    {
        return $class === 'App\Security\User';
    }
}

GoogleController.php :

    <?php

namespace App\Controller;


use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

class GoogleController extends AbstractController
{
/**
 * Link to this controller to start the "connect" process
 *
 * @Route("/connect/google", name="connect_google")
 * @param ClientRegistry $clientRegistry
 * @return \Symfony\Component\HttpFoundation\RedirectResponse
 */
public function connectAction(ClientRegistry $clientRegistry)
{
    return $clientRegistry
        ->getClient('google')
        ->redirect();
}

/**
 * Facebook redirects to back here afterwards
 *
 * @Route("/connect/google/check", name="connect_google_check")
 * @param Request $request
 * @return JsonResponse|\Symfony\Component\HttpFoundation\RedirectResponse
 */
public function connectCheckAction(Request $request)
{
    if (!$this->getUser()) {
        return new JsonResponse(array('status' => false, 'message' => "User not found!"));
    } else {
        return $this->redirectToRoute('default');
    }

}

}

and my GoogleAuthenticator.php :

<?php

namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use KnpU\OAuth2ClientBundle\Client\ClientRegistry;
use KnpU\OAuth2ClientBundle\Security\Authenticator\SocialAuthenticator;
use League\OAuth2\Client\Provider\GoogleUser;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;


/**
 * Created by IntelliJ IDEA.
 * User: mert
 * Date: 12/18/17
 * Time: 12:00 PM
 */
class GoogleAuthenticator extends SocialAuthenticator
{
    private $clientRegistry;
    private $em;
    private $router;

    public function __construct(ClientRegistry $clientRegistry, EntityManagerInterface $em, RouterInterface $router)
    {
        $this->clientRegistry = $clientRegistry;
        $this->em = $em;
        $this->router = $router;
    }

    public function supports(Request $request)
    {
        return $request->getPathInfo() == '/connect/google/check' && $request->isMethod('GET');
    }

    public function getCredentials(Request $request)
    {
        return $this->fetchAccessToken($this->getGoogleClient());
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        /** @var GoogleUser $googleUser */
        $googleUser = $this->getGoogleClient()
            ->fetchUserFromToken($credentials);

        $email = $googleUser->getEmail();

        $user = $this->em->getRepository('App:User')
            ->findOneBy(['email' => $email]);
        if (!$user) {
            $user = new User();
            $user->setEmail($googleUser->getEmail());
            $user->setFullname($googleUser->getName());
            $user->setCreatedAt(new \DateTime(date('Y-m-d H:i:s')));
            $this->em->persist($user);
            $this->em->flush();
        }

        return $user;
    }

    /**
     * @return \KnpU\OAuth2ClientBundle\Client\OAuth2Client
     */
    private function getGoogleClient()
    {
        return $this->clientRegistry
            ->getClient('google');
    }

    /**
     * Returns a response that directs the user to authenticate.
     *
     * This is called when an anonymous request accesses a resource that
     * requires authentication. The job of this method is to return some
     * response that "helps" the user start into the authentication process.
     *
     * Examples:
     *  A) For a form login, you might redirect to the login page
     *      return new RedirectResponse('/login');
     *  B) For an API token authentication system, you return a 401 response
     *      return new Response('Auth header required', 401);
     *
     * @param Request $request The request that resulted in an AuthenticationException
     * @param \Symfony\Component\Security\Core\Exception\AuthenticationException $authException The exception that started the authentication process
     *
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function start(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $authException = null)
    {
        return new RedirectResponse('/login');
    }

    /**
     * Called when authentication executed, but failed (e.g. wrong username password).
     *
     * This should return the Response sent back to the user, like a
     * RedirectResponse to the login page or a 403 response.
     *
     * If you return null, the request will continue, but the user will
     * not be authenticated. This is probably not what you want to do.
     *
     * @param Request $request
     * @param \Symfony\Component\Security\Core\Exception\AuthenticationException $exception
     *
     * @return \Symfony\Component\HttpFoundation\Response|null
     */
    public function onAuthenticationFailure(Request $request, \Symfony\Component\Security\Core\Exception\AuthenticationException $exception)
    {
        // TODO: Implement onAuthenticationFailure() method.
    }

    /**
     * Called when authentication executed and was successful!
     *
     * This should return the Response sent back to the user, like a
     * RedirectResponse to the last page they visited.
     *
     * 

If you return null, the current request will continue, and the user * will be authenticated. This makes sense, for example, with an API. * * @param Request $request * @param \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token * @param string $providerKey The provider (i.e. firewall) key * * @return void */ public function onAuthenticationSuccess(Request $request, \Symfony\Component\Security\Core\Authentication\Token\TokenInterface $token, $providerKey) { // TODO: Implement onAuthenticationSuccess() method. } }

Hope some of you could help on this. Thanks a lot.

1

1 Answers

0
votes

Since Google plus doesn't exist anymore you need to change the Google API for the login. Check the official Google Oauth2.0 for Google APIs (https://developers.google.com/identity/protocols/googlescopes). For this you need to change the scope inside the config.yml file. Instead of "https://www.googleapis.com/auth/plus.login" you can use "https://www.googleapis.com/auth/cloud-platform" or you can use scope: "openid". That at least works for me.