Developed with love by KnpLabs Hire us for your project!
41

SensioLabsConnectBundle

by symfonycorp

SensioLabsConnectBundle

About

This is the official bundle of the SensioLabs Connect SDK.

Installation

Step 1: Install SensioLabsConnectBundle using Composer

$ composer require sensiolabs/connect-bundle

Step 2: Enable the bundle

<?php

// app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new SensioLabs\Bundle\ConnectBundle\SensioLabsConnectBundle(),
        // ...
    );
}

Step 3: Configure your config.yml file

# app/config/config.yml
sensio_labs_connect:
    app_id:     Your app id
    app_secret: Your app secret
    scope:      Your app scope # SCOPE_EMAIL SCOPE_PUBLIC

Usage

Use SensioLabsConnect to authenticate your user

Step 1: Configure the security

Note: If you want to persist your users, read the Cookbooks section.

If you don't want to persist your users, you can use ConnectInMemoryUserProvider:

# app/config/security.yml
security:
    providers:
        sensiolabs_connect:
            connect_memory: ~
    firewalls:
        dev: { pattern:  "^/(_(profiler|wdt)|css|images|js)/",  security: false }
        secured_area:
            pattern:    ^/
            sensiolabs_connect:
                check_path: oauth_callback
                login_path: sensiolabs_connect_new_session
                failure_path: homepage # need to be adapted to your config, see step 5
                remember_me: false
                provider: sensiolabs_connect
            anonymous: true

You can also load specific roles for some users:

# app/config/security.yml
security:
    providers:
        sensiolabs_connect:
            connect_memory:
                users:
                    90f28e69-9ce9-4a42-8b0e-e8c7fcc27713: "ROLE_CONNECT_USER ROLE_ADMIN"

Note: The username is the user uuid.

Step 2: Configure the routing

Import the default routing

# app/config/routing.yml
_sensiolabs_connect:
    resource: "@SensioLabsConnectBundle/Resources/config/routing.xml"

Step 3: Add some link to your templates

You can generate a link to the SensioLabs Connect login page:

<a href="{{ url('sensiolabs_connect_new_session') }}">Connect</a>

You can also specify the target URL after connection:

<a href="{{ url('sensiolabs_connect_new_session') }}?target=XXX">Connect</a>

Step 4: Play with the user

The API user is available through the security token:

$user = $this->container->get('security.context')->getToken()->getApiUser();

You can also get access to the API root object:

$accessToken = $this->container->get('security.context')->getToken()->getAccessToken();

$api = $this->get('sensiolabs_connect.api');
$api->setAccessToken($accessToken);

$root = $api->getRoot();
$user = $root->getCurrentUser();

If you use the built-in security component, you can access to the root api
directly:

$api = $this->get('sensiolabs_connect.api');
$user = $api->getRoot()->getCurrentUser();

Step 5: Handling Failures

Note: this feature requires sensiolabs/connect v3.0.0

Several errors can occur during the OAuth dance, for example the user can
deny your application or the scope you defined in config.yml can be different
from what you selected while creating your application on SensioLabsConnect.
Theses failures need to be handled.

Since sensiolabs/connect v3.0.0, failures handling is restored to the default
Symfony failure handling.

Therefore, if an error occurred, the error is stored in the session (with a
fallback on query attributes) and the user is redirected to the route/path
specificed in failure_path node of the sensiolabs_connect section of your
firewall in security.yml.

Warning: You need to specifiy failure_path. If you don't, the user
will be redirected back to login_path, meaning that will launch the
SensioLabsConnect authentication and redirect the user to SensioLabsConnect
which can lead to a redirection loop.

This means you need to fetch the authentication error if there is one and display
it in the view. This is similar to what you do for a typical login form on
Symfony (here we assume you have a homepage route pointing to the
AppBundle:Default:homepage controller):

// src/AppBundle/Controller/DefaultController.php

namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\SecurityContextInterface;

class DefaultController extends Controller
{
    public function homepageAction(Request $request)
    {
        $session = $request->getSession();

        // get the authentication error if there is one
        if ($request->attributes->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
            $error = $request->attributes->get(
                SecurityContextInterface::AUTHENTICATION_ERROR
            );
        } elseif (null !== $session && $session->has(SecurityContextInterface::AUTHENTICATION_ERROR)) {
            $error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
            $session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);
        } else {
            $error = '';
        }

        return $this->render('default/homepage.html.twig', array('error' => $error));
    }
}

And then adapt your twig template:

{# app/Resources/views/default/homepage.html.twig #}

{% if app.user %}
    Congrats! You are authenticated with SensioLabsConnect
{% elseif error %}
    {{ error.messageKey | trans(error.messageData, 'security') }}
{% else %}
    <a href="{{ url('sensiolabs_connect_new_session') }}">Connect with SensioLabsConnect</a>
{% endif %}

Cookbooks

How to persist users

Step 1 - Create a User entity

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use SensioLabs\Connect\Api\Entity\User as ConnectApiUser;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Table(name="sl_user")
 * @ORM\Entity(repositoryClass="Sensiolabs\Bundle\HowToBundle\Repository\UserRepository")
 */
class User implements UserInterface
{
    /** @ORM\Column(type="integer") @ORM\Id @ORM\GeneratedValue(strategy="AUTO") */
    private $id;

    /** @ORM\Column(type="string", length=255) */
    private $uuid;

    /** @ORM\Column(type="string", length=255) */
    private $username;

    /** @ORM\Column(type="string", length=255) */
    private $name;

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

    public function updateFromConnect(ConnectApiUser $apiUser)
    {
        $this->username = $apiUser->getUsername();
        $this->name = $apiUser->getName();
    }

    public function getUuid()
    {
        return $this->uuid;
    }

    public function getUsername()
    {
        return $this->username;
    }

    public function getName()
    {
        return $this->name;
    }

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

    public function getPassword()
    {
    }

    public function getSalt()
    {
    }

    public function eraseCredentials()
    {
    }
}

Step 2 - Create the repository

<?php

namespace AppBundle\Repository;

use Doctrine\ORM\EntityRepository;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserRepository extends EntityRepository implements UserProviderInterface
{
    public function loadUserByUsername($uuid)
    {
        $user = $this->findOneByUuid($uuid);

        if (!$user) {
            $user = new User($uuid);
        }

        return $user;
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('class %s is not supported', get_class($user)));
        }

        return $this->loadUserByUsername($user->getUuid());
    }

    public function supportsClass($class)
    {
        return 'AppBundle\Entity\User' === $class;
    }
}

Don't forget to update your database.

Step 3 - Create the event listener

<?php

namespace AppBundle\EventListener;

use Doctrine\ORM\EntityManager;
use SensioLabs\Connect\Security\Authentication\Token\ConnectToken;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class SecurityInteractiveLoginListener implements EventSubscriberInterface
{
    private $em;

    public static function getSubscribedEvents()
    {
        return array(
            SecurityEvents::INTERACTIVE_LOGIN => 'registerUser',
        );
    }

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

    public function registerUser(InteractiveLoginEvent $event)
    {
        $token = $event->getAuthenticationToken();

        if (!$token instanceof ConnectToken) {
            return;
        }

        $user = $token->getUser();
        $user->updateFromConnect($token->getApiUser());

        $this->em->persist($user);
        $this->em->flush($user);
    }
}

Step 4 - Wire everything

Step 4.1 - Add new services
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="app.repository.user" class="AppBundle\Repository\UserRepository" factory-service="doctrine" factory-method="getRepository">
            <argument>SensiolabsHowToBundle:User</argument>
        </service>
        <service id="app.event_listener.interactive_login" class="AppBundle\EventListener\SecurityInteractiveLoginListener">
            <tag name="kernel.event_subscriber" />
            <argument type="service" id="doctrine.orm.entity_manager" />
        </service>
    </services>
</container>
Step 4.2 - Configure security
# app/config/security.yml
security:
    encoders:
        AppBundle\Entity\User: plaintext

    providers:
        sensiolabs_connect:
            id: app.repository.user

Step 5 - Enjoy

You can store more things if you want. But don't forget to update your
application scope.

License

This bundle is licensed under the MIT license.

Copyright (c) 2013-2017 SensioLabs

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
sensiolabs_connect:
app_id: ~ # Required
app_secret: ~ # Required
scope: ~ # Required
oauth_callback_path: /session/callback
oauth_endpoint: https://connect.sensiolabs.com
api_endpoint: https://connect.sensiolabs.com/api
timeout: 5
strict_checks: true
  • Merge pull request #26 from HeahDude/fix-controller-name
    By web-flow, 22 days ago
  • Fixed oauth controller reference
    By HeahDude, 24 days ago
  • bug #25 Fix depreciation in routing (tucksaun)
    By fabpot, 25 days ago
  • Fix depreciation in routing
    By tucksaun, 25 days ago
  • bug #23 #22 fix untag service controller as "controller.service_arguments" (Alexandre Vinet)
    By fabpot, 9 months ago
  • #22 fix untag service controller as "controller.service_arguments"
    By Alexandre Vinet, 9 months ago
  • bug #21 Fix Symfony4 support for DataCollector (fabpot)
    By fabpot, 1 year ago
  • fixed Symfony4 support for DataCollector
    By fabpot, 1 year ago
  • Merge pull request #20 from sensiolabs/symfony4-compat
    By web-flow, 1 year ago
  • fixed Symfony 4 compat
    By fabpot, 1 year ago
  • Merge pull request #19 from sensiolabs/symfony4-compat
    By web-flow, 1 year ago
  • fixed Symfony 4 compat
    By fabpot, 1 year ago
  • feature #18 Add compat for Symfony 4.0 (fabpot)
    By fabpot, 1 year ago
  • added compat for Symfony 4.0
    By fabpot, 1 year ago
  • bug #17 Fixed configuration processing (jeremyFreeAgent)
    By fabpot, 1 year ago
  • Fixed configuration processing
    By web-flow, 1 year ago
  • bug #16 Automatically enable/disable security if SecurityBundle is here (tucksaun)
    By lyrixx, 1 year ago
  • Automatically enable/disable security if SecurityBundle is here
    By tucksaun, 1 year ago
  • bug #15 fixed Symfony 3 compat (fabpot)
    By fabpot, 1 year ago
  • fixed Symfony 3 compat
    By fabpot, 1 year ago
  • fixed logic
    By fabpot, 1 year ago
  • fixed typo
    By fabpot, 1 year ago
  • feature #14 allowed to disable the security feature of the bundle (fabpot)
    By fabpot, 1 year ago
  • updated LICENSE year
    By fabpot, 1 year ago
  • allowed to disable the security feature of the bundle
    By fabpot, 1 year ago
  • minor #13 Updated the web debug toolbar panel (javiereguiluz)
    By fabpot, 2 years ago
  • Updated the web debug toolbar panel
    By fabpot, 2 years ago
  • minor #12 [Resources] fixes deprecated attributes in routing.xml file. (hhamon)
    By lyrixx, 3 years ago
  • [Resources] fixes deprecated attributes in routing.xml file.
    By Hugo Hamon, 3 years ago
  • minor #11 Updated the README file (javiereguiluz)
    By lyrixx, 3 years ago