I have a project with SonataAdminBundle for admin side and FosUserBundle for user management.
I override user management part in act to customize user entity, user login form ecc.
In my case override files are in src/Application/UserBundle.
I had some difficulties to set properly HWIOAuthBundle, so i wrote what i did, and I hope it can help someone.
Step 1: Setting up bundles
Step 2: Setting configuration: app/config.yml
hwi_oauth:
connect:
account_connector: wannaplay_user_provider
firewall_names: [main]
resource_owners:
facebook:
type: facebook
client_id: 123456789101112
client_secret: 8db9a0as2cfdde6863a9e0698273159
scope: "email"
options:
display: popup #dialog is optimized for popup window
fosub:
# try 30 times to check if a username is available (foo, foo1, foo2 etc)
username_iterations: 30
# mapping between resource owners (see below) and properties
properties:
facebook: facebook_id
services:
wannaplay_user_provider:
class: Application\Sonata\UserBundle\Security\Core\User\FOSUBUserProvider
#this is the place where the properties are passed to the UserProvider - see config.yml
arguments: [@fos_user.user_manager,{facebook: facebook_id}]
Step 3: Setting security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
SONATA:
- ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT # if you are using acl then this line must be commented
providers:
fos_userbundle:
id: fos_user.user_manager
firewalls:
# -> custom firewall for the admin area of the URL
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: null
logout:
path: /admin/logout
anonymous: true
# -> end custom configuration
# default login area for standard users
# This firewall is used to handle the public login area
# This part is handled by the FOS User Bundle
main:
pattern: .*
context: user
oauth:
resource_owners:
facebook: /login_facebook
login_path: /login
use_forward: false
failure_path: null
oauth_user_provider:
#it's the custom user provider. It will be registered as service
service: wannaplay_user_provider
form_login:
provider: fos_userbundle
login_path: /login
use_forward: false
check_path: /login_check
failure_path: null
always_use_default_target_path : true
default_target_path: /event/list
use_referer : true
logout:
path: /logout
target: /login
anonymous: true
access_control:
# URL of FOSUserBundle which need to be available to anonymous users
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
# Admin login page needs to be access without credential
- { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
# Secured part of the site
# This config requires being logged for the whole site and having the admin role for the admin part.
# Change these rules to adapt them to your needs
- { path: ^/admin/, role: [ROLE_ADMIN, ROLE_SONATA_ADMIN] }
- { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: [ IS_AUTHENTICATED_FULLY ] }
Step 4: Update routing
admin:
resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
prefix: /admin
_sonata_admin:
resource: .
type: sonata_admin
prefix: /admin
sonata_user_security:
resource: "@SonataUserBundle/Resources/config/routing/sonata_security_1.xml"
sonata_user_resetting:
resource: "@SonataUserBundle/Resources/config/routing/sonata_resetting_1.xml"
prefix: /resetting
sonata_user_profile:
resource: "@SonataUserBundle/Resources/config/routing/sonata_profile_1.xml"
prefix: /profile
sonata_user_register:
resource: "@SonataUserBundle/Resources/config/routing/sonata_registration_1.xml"
prefix: /register
sonata_user_change_password:
resource: "@SonataUserBundle/Resources/config/routing/sonata_change_password_1.xml"
prefix: /profile
sonata_user:
resource: '@SonataUserBundle/Resources/config/routing/admin_security.xml'
prefix: /admin
fos_message:
resource: "@FOSMessageBundle/Resources/config/routing.xml"
prefix: /massages
fos_oauth_server_token:
resource: "@FOSOAuthServerBundle/Resources/config/routing/token.xml"
fos_oauth_server_authorize:
resource: "@FOSOAuthServerBundle/Resources/config/routing/authorize.xml"
hwi_oauth_redirect:
resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
prefix: /connect
facebook_login:
path: /login_facebook
Step 5: Customize user provider
User provider is registered as service, extended from FOSUBUserProvider. This is the one that actually does the User registration in YOUR database with data from PROVIDERS (Facebook, Google, etc.) and in responsible for connecting already logged in users with accounts from PROVIDERS. It does this by overwriting 2 functions (connect(UserInterface $user, UserResponseInterface $response) and loadUserByOAuthUserResponse(UserResponseInterface $response)). See code below.
//Application\Sonata\UserBundle\Security\Core\User\FOSUBUserProvider.php
<?php
namespace Application\Sonata\UserBundle\Security\Core\User;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\User\UserInterface;
class FOSUBUserProvider extends BaseClass
{
/**
* {@inheritDoc}
*/
public function connect(UserInterface $user, UserResponseInterface $response)
{
$property = $this->getProperty($response);
$username = $response->getUsername();
//on connect - get the access token and the user ID
$service = $response->getResourceOwner()->getName();
$setter = 'set'.ucfirst($service);
$setter_id = $setter.'Id';
$setter_token = $setter.'AccessToken';
//we "disconnect" previously connected users
if (null !== $previousUser = $this->userManager->findUserBy(array($property => $username))) {
$previousUser->$setter_id(null);
$previousUser->$setter_token(null);
$this->userManager->updateUser($previousUser);
}
//we connect current user
$user->$setter_id($username);
$user->$setter_token($response->getAccessToken());
$this->userManager->updateUser($user);
}
/**
* {@inheritdoc}
*/
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$username = $response->getUsername();
$user = $this->userManager->findUserBy(array($this->getProperty($response) => $username));
//If user not exist create user and return it
if (null === $user) {
$service = $response->getResourceOwner()->getName();
$setter = 'set'.ucfirst($service);
$setter_id = $setter.'Id';
$setter_token = $setter.'AccessToken';
// create new user here
$user = $this->userManager->createUser();
$user->$setter_id($username);
$user->$setter_token($response->getAccessToken());
$user->setUsername( $response->getRealName());
$user->setEmail($response->getEmail());
$user->setPassword($username);
$user->setEnabled(true);
$this->userManager->updateUser($user);
return $user;
}
//if user exists - go with the HWIOAuth way
$user = parent::loadUserByOAuthUserResponse($response);
$serviceName = $response->getResourceOwner()->getName();
$setter = 'set' . ucfirst($serviceName) . 'AccessToken';
//update access token
$user->$setter($response->getAccessToken());
return $user;
}
}
Step 5: Register user provider as service
services:
wannaplay_user_provider:
class: Application\Sonata\UserBundle\Security\Core\User\FOSUBUserProvider
#this is the place where the properties are passed to the UserProvider - see config.yml
arguments: [@fos_user.user_manager,{facebook: facebook_id}]
Step 6: Update your User Entity
We add facebook_id and facebook_access_token field in entity and into table of DB
<?php
namespace Application\Sonata\UserBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use FOS\MessageBundle\Model\ParticipantInterface;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser implements ParticipantInterface
{
/**
* @var integer $id
*/
protected $id;
.......................
.......................
/** @ORM\Column(name="facebook_id", type="string", length=255, nullable=true) */
protected $facebook_id;
/** @ORM\Column(name="facebook_access_token", type="string", length=255, nullable=true) */
protected $facebook_access_token;
.......................
.......................
public function getFacebookId()
{
return $this->facebook_id;
}
public function setFacebookId($facebookId)
{
$this->facebook_id = $facebookId;
}
public function getFacebookAccessToken()
{
return $this->facebook_access_token;
}
public function setFacebookAccessToken($facebookAccessToken)
{
$this->facebook_access_token = $facebookAccessToken;
}
.......................
.......................
I use Sonata Admin so I must override the mapping entity file for add correctly new fields. In my case I just added two lines inside the entity object:
//src/Application/Sonata/UserBundle/Resources/config/doctrine/User.orm.xml
<entity name="Application\Sonata\UserBundle\Entity\User" table="fos_user">
.......
<field name="facebook_id" type="string" column="facebook_id" length="255" nullable="true"/>
<field name="facebook_access_token" type="string" column="facebook_access_token" length="255" nullable="true"/>
</entity>
Step 7: Customize login form – Adding the Facebook Login Button
//src/Application/Sonata/UserBundle/Resources/view/Security/base_login.html.twig <div id="fb-root"></div> <script> window.fbAsyncInit = function() { // init the FB JS SDK FB.init({ appId : '12345678910', // App ID from the app dashboard channelUrl : '//yourdomain.com/channel.html', // Channel file for x-domain comms status : true, // Check Facebook Login status xfbml : true // Look for social plugins on the page }); }; // Load the SDK asynchronously (function(d, s, id){ var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) {return;} js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/en_US/all.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); function fb_login() { FB.getLoginStatus(function(response) { if (response.status === 'connected') { // connected alert('Already connected, redirect to login page to create token.'); document.location = "{{ url("hwi_oauth_service_redirect", {service: "facebook"}) }}"; } else { // not_authorized FB.login(function(response) { if (response.authResponse) { document.location = "{{ url("hwi_oauth_service_redirect", {service: "facebook"}) }}"; } else { alert('Cancelled.'); } }, {scope: 'email'}); } }); } ................. ................. <form action="{{ path("fos_user_security_check") }}" method="post" role="form" class="form col-md-12 center-block"> ................. {% render(controller('ApplicationSonataUserBundle:Connect:connect')) %} ................ </form>
Extra
I used « render controller » for show facebook button. But in this moment button is just a link, so i want customize link for show properly the facebook connect button. So i must override controller and view.
Step 8: Customize Connect Controller
// src/Application/Sonata/UserBundle/Controller/ConnectController
<?php
namespace Application\Sonata\UserBundle\Controller;
.............
class ConnectController extends Controller
{
/**
* Action that handles the login 'form'. If connecting is enabled the
* user will be redirected to the appropriate login urls or registration forms.
*
* @param Request $request
*
* @return Response
*/
public function connectAction(Request $request)
{
.......................
return $this->render('@ApplicationSonataUser/Security/connect_login.html.twig', array(
'error' => $error,
));
}
...........................
}
Step 9: Customize view
{# src/Application/Sonata/UserBundle/Resurces/views/Security #}
{% extends 'HWIOAuthBundle::layout.html.twig' %}
{% block hwi_oauth_content %}
{% if error is defined and error %}
<span>{{ error }}</span>
{% endif %}
{% for owner in hwi_oauth_resource_owners() %}
{% if owner == 'facebook' %}
<a class="btn btn-primary btn-lg btn-block" role="button" href="{{ hwi_oauth_login_url(owner) }}">
<i class="fa fa-facebook-official" aria-hidden="true"></i> {{ 'Continue with Facebook' | trans({}, 'HWIOAuthBundle') }}
</a> <br/>
{% else %}
<a href="{{ hwi_oauth_login_url(owner) }}">{{ owner | trans({}, 'HWIOAuthBundle') }}</a> <br/>
{% endif %}
{% endfor %}
{% endblock hwi_oauth_content %}
Final result:

References:
- https://github.com/hwi/HWIOAuthBundle
- http://symfony.com/doc/current/bundles/FOSUserBundle/index.html
- https://gist.github.com/danvbe/4476697
- https://www.supinfo.com/articles/single/2426-symfony-27-une-authentification-avec-oauth2
- http://rkueny.fr/developpement-web/symfony2-fosuserbundle-et-connexion-oauth-facebook-twitter-google-github-etc
- http://rkueny.fr/developpement-web/integrer-facilement-facebook-connect-et-twitter-connect-avec-fosuserbundle-pour-symfony-2-1
Comments