Cartalyst LLC.
Sentinel-social by Cartalyst
0
33
29
0
0

This package requires a valid subscription. Subscribe for access.

Introduction

Sentinel Social makes authenticating your users through social networks & third-party OAuth providers an absolute breeze.

The package follows the FIG standard PSR-4 to ensure a high level of interoperability between shared PHP code and is fully unit-tested.

The package requires PHP 5.4+.

Have a read through the Installation Guide.

Quick Example

Add Connections

Social::addConnection('facebook' => [
        'driver'     => 'Facebook',
        'identifier' => '',
        'secret'     => '',
        'scopes'     => ['email'],
    ],
);

Authorize

$callback = 'http://app.dev/callback.php';
$url      = Social::getAuthorizationUrl('facebook', $callback);

header('Location: ' . $url);
exit;

Authenticate

$callback = 'http://app.dev/callback.php';

try
{
    $user = Social::authenticate('facebook', $callback, function(Cartalyst\Sentinel\Addons\Social\Models\LinkInterface $link, $provider, $token, $slug)
    {
        // Retrieve the user in question for modificiation
        $user = $link->getUser();

        // You could add your custom data
        $data = $provider->getUserDetails($token);

        $user->foo = $data->foo;
        $user->save();
    });
}
catch (Cartalyst\Sentinel\Addons\Social\AccessMissingException $e)
{
    var_dump($e); // You may save this to the session, redirect somewhere
    die();

    header('HTTP/1.0 404 Not Found');
}

Installation

The best and easiest way to install the Sentinel Social package is with Composer.

Preparation

Open your composer.json and add the following to the require array:

"cartalyst/sentinel-social": "2.0.*"

Add the following lines after the require array on your composer.json file:

"repositories": [
    {
        "type": "composer",
        "url": "https://packages.cartalyst.com"
    }
],

Note: Make sure that after the required changes your composer.json file is valid by running composer validate.

Install the dependencies

Run Composer to install or update the new requirement.

php composer install

or

php composer update

Now you are able to require the vendor/autoload.php file to autoload the package.

Integration

Laravel 5

The Sentinel Social package has optional support for Laravel 5 and it comes bundled with a
Service Provider and a Facade for easier integration.

After you have installed the package, just follow the instructions.

Open your Laravel config file config/app.php and add the following lines.

In the $providers array add the following service provider for this package.

'Cartalyst\Sentinel\Addons\Social\Laravel\SocialServiceProvider',

In the $aliases array add the following facade for this package.

'Social' => 'Cartalyst\Sentinel\Addons\Social\Laravel\Facades\Social',

Assets

Run the following commands to publish the migration and config files.

  • Sentinel

php artisan vendor:publish --provider="Cartalyst\Sentinel\Laravel\SentinelServiceProvider"

  • Sentinel Social

php artisan vendor:publish --provider="Cartalyst\Sentinel\Addons\Social\Laravel\SocialServiceProvider"

Migrations

Run the following command to migrate Sentinel after publishing the assets.

php artisan migrate

Configuration

After publishing, the sentinel social config file can be found under config/cartalyst.sentinel-addons.social.php where you can modify the package configuration.

Native

After you have installed the package, just follow the instructions.

Setup your database

Sentinel schema

`vendor/cartalyst/sentinel/schema/mysql.sql`

Sentinel Social schema

`vendor/cartalyst/sentinel-social/schema/mysql.sql`

Configuration

Instantiate Sentinel Social

// Include the composer autoload file
require_once 'vendor/autoload.php';

// Import the necessary classes
use Cartalyst\Sentinel\Addons\Social\Manager;

$manager = new Manager($instanceOfSentinel);

$manager->addConnection('facebook' => [
        'driver'     => 'Facebook',
        'identifier' => '',
        'secret'     => '',
        'scopes'     => ['email'],
    ],
);

Authorize

$callback = 'http://app.dev/callback.php';
$url      = $manager->getAuthorizationUrl('facebook', $callback);

header('Location: ' . $url);
exit;

Authenticate

$callback = 'http://app.dev/callback.php';

try
{
    $user = $manager->authenticate('facebook', $callback, function(Cartalyst\Sentinel\Addons\Social\Models\LinkInterface $link, $provider, $token, $slug)
    {
        // Retrieve the user in question for modificiation
        $user = $link->getUser();

        // You could add your custom data
        $data = $provider->getUserDetails($token);

        $user->foo = $data->foo;
        $user->save();
    });
}
catch (Cartalyst\Sentinel\Addons\Social\AccessMissingException $e)
{
    var_dump($e); // You may save this to the session, redirect somewhere
    die();

    header('HTTP/1.0 404 Not Found');
}

Usage

OAuth Flow

While OAuth1 and OAuth2 are incompatible protocols, they (for the most part) follow the same process:

  1. A secure connection is established between a your app and a provider.
  2. A user is redirected to the provider where they may login and approve (or reject) your app to have access.
  3. Your app receives a token from the service so your app may act on behalf of the person who authenticated. You never find out their password and they have the option to revoke your access at any point.

Sentinel Social abstracts all the differences between OAuth 1 and OAuth 2, so that you can focus on the more interesting parts of your app.

Manager

$manager = new Cartalyst\Sentinel\Addons\Social\Manager($instanceOfSentinel);

Social is the Laravel alias for the manager and can be directly used without instantiation.

Connections

Single connection

Social::addConnection('facebook', [
        'driver'     => 'Facebook',
        'identifier' => '',
        'secret'     => '',
        'scopes'     => ['email'],
    ],
);

Multiple connections

$connections = [

    'facebook' => [
        'driver'     => 'Facebook',
        'identifier' => '',
        'secret'     => '',
        'scopes'     => ['email'],
    ],

    'github' => [
        'driver'     => 'GitHub',
        'identifier' => '',
        'secret'     => '',
        'scopes'     => ['user'],
    ],

);

Social::addConnections($connections);

Connections on Laravel are stored in config/cartalyst.sentinel-addons.social.php

Authorization

Authorizing a user (redirecting them to the provider's login/approval screen) is extremely easy.

Once you've configured a provider with Sentinel Social, you simply need to redirect the user to the authorization URL.

Route::get('oauth/authorize', function()
{
    $callback = URL::to('oauth/callback');
    $url = Social::getAuthorizationUrl('facebook', $callback);
    return Redirect::to($url);
});

Authentication

Once a user has finished authorizing (or rejecting) your application, they're redirected to the callback URL which you specified.

To handle the authentication process, you will need to respond to the response from the provider on that callback URL.

Route::get('oauth/callback', function()
{
    // Callback is required for providers such as Facebook and a few others (it's required
    // by the spec, but some providers omit this).
    $callback = URL::current();

    try
    {
        $user = Social::authenticate('facebook', URL::current(), function(Cartalyst\Sentinel\Addons\Social\Models\LinkInterface $link, $provider, $token, $slug)
        {
            // Retrieve the user in question for modificiation
            $user = $link->getUser();

            // You could add your custom data
            $data = $provider->getUserDetails($token);

            $user->foo = $data->foo;
            $user->save();
        });
    }
    catch (Cartalyst\Sentinel\Addons\Social\AccessMissingException $e)
    {
        // Missing OAuth parameters were missing from the query string.
        // Either the person rejected the app, or the URL has been manually
        // accesed.
        if ($error = Input::get('error'))
        {
            return Redirect::to('oauth')->withErrors($error);
        }

        App::abort(404);
    }
});

Note: If you attempt to authenticate a provider when a Sentinel user is already logged in, the authenticated provider account will be linked with that User. For you as a developer, this allows your users to link multiple social accounts easily. If you don't want to allow other accounts to be linked, either don't show the social login links and/or log the user out at the start of the authorization process (in your controller).

Hooks

In addition to providing a hook (callback) for when a user is being linked (the second parameter passed to authenticate()), we also provide ways to hook into new user registrations as well as existing user linking.

For example, this may be useful to send welcome emails when new users are being registered:

Social::registered(function(Cartalyst\Sentinel\Addons\Social\Models\LinkInterface $link, $provider, $token, $slug)
{
    $user = $link->getUser();

    Mail::later($user->email, 'welcome', compact('user', 'slug'));
});

Social::existing(function(Cartalyst\Sentinel\Addons\Social\Models\LinkInterface $link, $provider, $token, $slug)
{
    // Callback for existing users
});

// Finally, after hooks are registered, you may authenticate away
$user = Social::authenticate($params);

Extending Sentinel Social

Sentinel Social was designed from the ground up with extendability in mind.

Extending is as simple as 2 steps

  1. Creating your implementation class.
  2. Adding the connection to Sentinel Social.

Creating An Implementation Class

To create an implementation class, you firstly need to determine if you're dealing with OAuth 1 or OAuth 2.

OAuth 1

To create an OAuth 1 implementation class for Sentinel Social, simply create a class which extends League\OAuth1\Client\Server\Server.

Example:

    use League\OAuth1\Client\Server\User;

    class MyOAuth1Provider extends \League\OAuth1\Client\Server\Server {

        /**
         * The response type for data returned from API calls.
         *
         * @var string
         */
        protected $responseType = 'json';

        /**
         * Get the URL for retrieving temporary credentials.
         *
         * @return string
         */
        public function urlTemporaryCredentials()
        {
            return 'https://api.myprovider.com/oauth/temporary_credentials';
        }

        /**
         * Get the URL for redirecting the resource owner to authorize the client.
         *
         * @return string
         */
        public function urlAuthorization()
        {
            return 'https://api.myprovider.com/oauth/authorize';
        }

        /**
         * Get the URL retrieving token credentials.
         *
         * @return string
         */
        public function urlTokenCredentials()
        {
            return 'https://api.myprovider.com/oauth/token_credentials';
        }

        /**
         * Get the URL for retrieving user details.
         *
         * @return string
         */
        public function urlUserDetails()
        {
            return 'https://api.myprovider/1.0/user.json';
        }

        /**
         * Take the decoded data from the user details URL and convert
         * it to a User object.
         *
         * @param  mixed  $data
         * @param  TokenCredentials  $tokenCredentials
         * @return User
         */
        public function userDetails($data, TokenCredentials $tokenCredentials)
        {
            $user = new User;

            // Take the decoded data (determined by $this->responseType)
            // and fill out the user object by abstracting out the API
            // properties (this keeps our user object simple and adds
            // a layer of protection in-case the API response changes)

            $user->first_name = $data['user']['firstname'];
            $user->last_name  = $data['user']['lastname'];
            $user->email      = $data['emails']['primary'];
            // Etc..

            return $user;
        }

        /**
         * Take the decoded data from the user details URL and extract
         * the user's UID.
         *
         * @param  mixed  $data
         * @param  TokenCredentials  $tokenCredentials
         * @return string|int
         */
        public function userUid($data, TokenCredentials $tokenCredentials)
        {
            return $data['unique_id'];
        }

        /**
         * Take the decoded data from the user details URL and extract
         * the user's email.
         *
         * @param  mixed  $data
         * @param  TokenCredentials  $tokenCredentials
         * @return string
         */
        public function userEmail($data, TokenCredentials $tokenCredentials)
        {
            // Optional
            if (isset($data['email']))
            {
                return $data['email'];
            }
        }

        /**
         * Take the decoded data from the user details URL and extract
         * the user's screen name.
         *
         * @param  mixed  $data
         * @param  TokenCredentials  $tokenCredentials
         * @return User
         */
        public function userScreenName($data, TokenCredentials $tokenCredentials)
        {
            // Optional
            if (isset($data['screen_name']))
            {
                return $data['screen_name'];
            }
        }
    }

OAuth 2

Creating an OAuth 2 implementation is much the same, however just a little easier (as there's less methods to implement):

use League\OAuth2\Client\Provider\User;

class MyOAuth2Provider extends \League\OAuth2\Client\Provider\AbstractProvider {

    // Default scopes
    public $scopes = ['scope1', 'scope2'];

    // Response type
    public $responseType = 'json';

    public function urlAuthorize()
    {
        return 'https://api.myprovider.com/authorize';
    }

    public function urlAccessToken()
    {
        return 'https://api.myprovider.com/access_token';
    }

    public function urlUserDetails(\League\OAuth2\Client\Token\AccessToken $token)
    {
        return 'https://api.myprovider.com/1.0/user.json?access_token='.$token;
    }

    public function userDetails($response, \League\OAuth2\Client\Token\AccessToken $token)
    {
        $user = new User;

        // Take the decoded data (determined by $this->responseType)
        // and fill out the user object by abstracting out the API
        // properties (this keeps our user object simple and adds
        // a layer of protection in-case the API response changes)

        $user->first_name = $response['user']['firstname'];
        $user->last_name  = $response['user']['lastname'];
        $user->email      = $response['emails']['primary'];
        // Etc..

        return $user;
    }

    public function userUid($response, \League\OAuth2\Client\Token\AccessToken $token)
    {
        return $response['unique_id'];
    }

    public function userEmail($response, \League\OAuth2\Client\Token\AccessToken $token)
    {
        // Optional, however OAuth2 usually provides a scope
        // to receive access to a user's email, you should always
        // ask for this scope, as having an email is awesome.
        if (isset($response['email']))
        {
            return $response['email'];
        }
    }

    public function userScreenName($response, \League\OAuth2\Client\Token\AccessToken $token)
    {
        // Optional
        if (isset($response['screen_name']))
        {
            return $response['screen_name'];
        }
    }
}

Adding a Connection

Now that you've made an implementation class for Sentinel Social, you need to add a connection.

In Laravel

In Laravel, the easiest way is to add the connection to your config file

// After publishing your config, this is in app/config/packages/cartalyst/sentinel-social/config.php
'connections' => [

    // Additional, default connections…

    'myprovider' => [

        // The driver should match your implementation's name (including namespace)
        'driver'     => 'MyOAuth2Provider',

        'identifier' => 'your-app-identifier',
        'secret'     => 'your-app-secret',

        // To override OAuth2 scopes (scopes don't exist on OAuth 1), specify
        // this parameter. Otherwise, the default scopes from your implementation
        // class will be used.
        'scopes'     => ['scope1', 'scope2', 'scope3'],
    ],
),

Outside Laravel

Outside of Laravel, you can add a connection by calling a method on your $social object

$social->addConnection('myprovider', [

    // See the comments for "In Laravel" above, these parameters
    // are the same
    'driver'     => 'MyOAuth2Provider',
    'identifier' => 'your-app-identifier',
    'secret'     => 'your-app-secret',
    'scopes'     => ['scope1', 'scope2', 'scope3'],
]);

Now, continue to use Sentinel Social as normal, instead substituting myprovider (or whatever you named your connection as) when authorizing and authenticating!

Tips

Once you've made a provider, if it could potentially be used by anybody, you should endeavour to submit a pull request back to the underlying OAuth 1 or OAuth2 repository, so everybody can make use of it.

Once that pull request has been merged, you may submit a pull request back to Sentinel Social, to simply update the default config file to ship with your provider built-in!

You wont find fancy lifestyle graphics and marketing bravado here. Just cold... hard... code...

Code Well, Rock On!
Processing Payment...