Loctarogar
Loctarogar

Reputation: 620

How to setup HWIOAuthBundle with Symfony4.3?

I have project on SF4.3 and i don't use FOSUserBundle. How to setup HWIOAuthBundle? Configuration that i have now:

security.yaml

main:
            anonymous: ~
            oauth:
                resource_owners:
                    facebook:           "/login/check-facebook"
                    google:             "/login/check-google"
                    #my_custom_provider: "/login/check-custom"
                    #my_github:          "/login/check-github"
                login_path:        /login
                use_forward:       false
                failure_path:      /login
                provider: users
                oauth_user_provider:
                    service: my.oauth_aware.user_provider.service

hwi_oauth.yaml

hwi_oauth:
    # list of names of the firewalls in which this bundle is active, this setting MUST be set
    firewall_names: [main]

    # https://github.com/hwi/HWIOAuthBundle/blob/master/Resources/doc/2-configuring_resource_owners.md
    resource_owners:
        facebook:
            type:                facebook
            client_id:           '%env(FB_ID)%'
            client_secret:       '%env(FB_SECRET)%'
            scope:               "email"
            options:
                display: popup
                csrf: true
        google:
            type:                google
            client_id:           '%env(G_ID)%'
            client_secret:       '%env(G_SECRET)%'
            scope:               "email"

and in security.yaml

my.oauth_aware.user_provider.service:
    class: HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider
    arguments:
        - '@fos_user.user_manager'
        - ['@fos_user.user_manager', google: google ]

if i don't use FOSUser for users provider in security.yaml has to be different, how to configure provider for my User?

Upvotes: 2

Views: 3361

Answers (2)

Frank B
Frank B

Reputation: 3697

If you want to use your custom User class (eg App\Entity\User) the solution is to make your own User Provider. If you want users to be able to authenticate with a traditional login form and also with HWIOAuthBundle then you could start to make your own User provider as described here. Of course you have to add some code inside the two methods before it works (see example below) and do not forget to setup the providers section in security.yaml like so:

    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            id: App\Security\UserProvider

Then if you can authenticate with your traditional login form through your newly created User Provider and it all works well you can start to integrate HWIOAuthBundle. Start to direct the oauth_user_provider setting to your own User Provider class. Your main firewall could look like this:

    firewalls:
        main:
            anonymous: true
            oauth:
                resource_owners:
                    facebook: "/login/check-facebook"
                oauth_user_provider:
                    service: App\Security\UserProvider       # HERE YOU GO!
                login_path: /login
                use_forward:       false
                failure_path:      /login
            form_login:
                login_path: /login
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            logout:
                path: app_logout

By the way your App\Security\UserProvider class should be auto-wired in the service container. If not you have to add the service manually to your service.yaml.

If you now try to login with a resource owner (eg Facebook or Google) from HWIOAuthBundle then you will get an error because your User Provider class must implement HWI's OAuthAwareUserProviderInterface. A quick look into that interface source file will learn that you have to add one method to your User Provider: loadUserByOAuthUserResponse(UserResponseInterface $response). So let your User Provider class implement HWI OAuthAwareUserProviderInterface and add the method to the class. It is pretty straightforward.

Here comes the full User Provider that i wrote for this test case:

<?php

namespace App\Security;

use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Doctrine\ORM\EntityManagerInterface;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface;
use App\Entity\User;

class UserProvider implements UserProviderInterface, OAuthAwareUserProviderInterface
{
    private $em;
    private $property = 'email';

    public function __construct(EntityManagerInterface $em) {
        $this->em = $em;
    }

    /**
     * @return UserInterface
     */
    public function loadUserByUsername($username)
    {
        $repository = $this->em->getRepository(User::class);

        if (null !== $this->property) {
            $user = $repository->findOneBy([$this->property => $username]);
        } else {
            if (!$repository instanceof UserLoaderInterface) {
                throw new \InvalidArgumentException(sprintf('You must either make the "%s" entity Doctrine Repository ("%s") implement "Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface" or set the "property" option in the corresponding entity provider configuration.', $this->classOrAlias, \get_class($repository)));
            }
            $user = $repository->loadUserByUsername($username);
        }

        if (null === $user) {
            throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username));
        }

        return $user;
    }

    /**
     * @return UserInterface
     */
    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', User::class));
        }

        $repository = $this->em->getRepository(User::class);

        if ($repository instanceof UserProviderInterface) {
            $refreshedUser = $repository->refreshUser($user);
        } else {
            $refreshedUser = $repository->find($user->getId());
            if (null === $refreshedUser) {
                throw new UsernameNotFoundException(sprintf('User with id %s not found', json_encode($user->getId())));
            }
        }

        return $refreshedUser;
    }

    /**
     * @return UserInterface
     */
    public function loadUserByOAuthUserResponse(UserResponseInterface $response)
    {
        return $this->loadUserByUsername($response->getEmail());
    }

    /**
     * Tells Symfony to use this provider for this User class.
     */
    public function supportsClass($class)
    {
        return User::class === $class;
    }
}

This User Provider is still not ready. For example it will throw an exception if a unknown facebook/google/... user tries to login. You have to extend this example to your needs and thoroughly test it!

Upvotes: 1

Loctarogar
Loctarogar

Reputation: 620

Ok, i made my own provider:

class OAuthUserProvider extends BaseClass {

public $entityManager;

public $userRepository;

public function __construct(
    UserManagerInterface $userManager,
    array $properties,
    UserRepository $userRepository,
    EntityManagerInterface $entityManager
) {
    parent::__construct($userManager, $properties);
    $this->userRepository = $userRepository;
    $this->entityManager = $entityManager;
}

/**
 * {@inheritdoc}
 * @throws \Doctrine\ORM\NonUniqueResultException
 */
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $socialID = $response->getUsername();
    $user = $this->userRepository->findByGoogleId($socialID);
    $email = $response->getEmail();
    //check if the user already has the corresponding social account
    if (null === $user) {
        //check if the user has a normal account
        $user = $this->userRepository->findUserByEmail($email);

        if (null === $user || !$user instanceof UserInterface) {
            //if the user does not have a normal account, set it up:
            $user = new User();
            $user->setEmail($email);
            $user->setPlainPassword(md5(uniqid('', true)));
            $user->setActive(true);
        }
        //then set its corresponding social id
        $service = $response->getResourceOwner()->getName();
        switch ($service) {
            case 'google':
                $user->setGoogleID($socialID);
                break;
            case 'facebook':
                $user->setFacebookID($socialID);
                break;
        }
        $em = $this->entityManager;
        $em->persist($user);
        $em->flush();
        //$this->userManager->updateUser($user);
    } else {
        //and then login the user
        $checker = new UserChecker();
        $checker->checkPreAuth($user);
    }

    return $user;
}
}

in my services.yaml:

app.provider.oauth:
        class: App\Security\Providers\OAuthUserProvider
        arguments: ['@fos_user.user_manager',{google: googleID, facebook: facebook}]

Upvotes: 3

Related Questions