Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Laravel Sentry 2 with multiple user types, finally.

Sentry is an amazing auth system. But I really need a feature: multiple user types in the same app. And I cannot separate those in groups, because they have different table columns. After 2 days burning my head, I think I found a good solution. The magic is duplicate SentryServiceProvider with new different settings.

This was tested on Laravel 4.0 and Sentry 2. If you're using other version of Sentry, my suggestion is to follow same steps from this gist but use your local files instead copying files from here.

Lets suppose we have a fresh Sentry install with default User ambient. Now we want another ambient called Admin, with new model and different settings. How to do:

1. One model, one ambient

Let's create one model called Admin for our new ambient. Just create an empty model and extend it to the Sentry user.

<?php
namespace App\Models;

use Eloquent;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;

class Admin extends \Cartalyst\Sentry\Users\Eloquent\User implements UserInterface, RemindableInterface {

	protected $table = 'admins';

	protected $guarded = array(
	);

	protected $fillable = array(
	);

	public static $rules = array(
	);

	/**
	 * sentry methods
	 */
	public function groups(){ return $this->belongsToMany('Cartalyst\Sentry\Groups\Eloquent\Group', 'admins_groups'); }
	public function getAuthIdentifier(){ return $this->getKey(); }
	public function getAuthPassword(){ return $this->password; }
	public function getReminderEmail(){ return $this->email; }

}

2. Duplicate Sentry ServiceProvider.

Create a folder called app/lib and add in composer autoload. In this folder, create this structure:

/app/lib
    /SentryMods
        SentryAdmin.php
        SentryAdminServiceProvider.php

Now we copy contents from original Sentry files just changing some sentry.admin names.

#####SentryAdmin.php

<?php namespace Lib\SentryMods;
use Illuminate\Support\Facades\Facade;
class SentryAdmin extends Facade {
    protected static function getFacadeAccessor(){ return 'sentry.admin'; }
}

#####SentryAdminServiceProvider.php

// check attached file to this gist

3. Set configurations for the new ambient

In the file app/config/cartalyst/sentry/config.php, add this lines. Here is the magic. Here we can choose settings for the new sentry provider. You can change anything, but I've tested only changing the user model.

	'admin'=>array(
		'driver' => 'eloquent',
		'hasher' => 'native',
		'groups' => array(
			'model' => 'Cartalyst\Sentry\Groups\Eloquent\Group',
		),
		'cookie' => array(
			'key' => 'cartalyst_sentry',
	 	),
		'users' => array(
			'model' => 'App\Models\Admin',
			'login_attribute' => 'login',
		),
		'throttling' => array(
			'enabled' => true,
			'model' => 'Cartalyst\Sentry\Throttling\Eloquent\Throttle',
			'attempt_limit' => 5,
			'suspension_time' => 15,
		),
	),

4. Prepare database

I created a migration to run after the migrate of sentry package. This is called sentrymods and copies original tables for the new ambient. This migration can resolves too some adjustments for sentry tables, like adding new columns.

public function up()
{
	DB::statement('CREATE TABLE admins LIKE users');
	DB::statement('CREATE TABLE admins_groups LIKE users_groups');

	Schema::table('admins', function($table){
		$table->string('login')->nullable();
		$table->softDeletes();
	});

	Schema::table('admins_groups', function($table){
		$table->renameColumn('user_id', 'admin_id');
	});
}

5. Lets see this working.

Now you have two Sentry ambients working. Then, just use SentryAdmin when you want :) Try this little test:

print_r(Sentry::getUserProvider());
print_r(SentryAdmin::getUserProvider());

The new one prints the default sentry. It will show the default settings. The second will shows our new sentry ambient with different settings. Something like:

// Cartalyst\Sentry\Users\Eloquent\Provider
// Object (
//     [model:protected] => App\Models\Admin
//     [hasher:protected] => Cartalyst\Sentry\Hashing\NativeHasher
// )

Want a seeder file? try this

<?php

use App\Models\User;
use Lib\SentryMods\SentryAdmin;
use Lib\SentryMods\SentryAdminServiceProvider;

class SentryTableSeeder extends Seeder {

	public function run()
	{
		$group_admin = SentryAdmin::getGroupProvider()->create(array('name'=>'Admins', 'permissions'=>array('admin'=>1)));

		$user = SentryAdmin::getUserProvider()->create(array(
			'email'		=> 'admin@admin.com',
			'login'		=> 'admin',
			'password'	=> 'admin',
			'first_name'=>'Leandro', 'last_name'=>'Abdalla', 'activated'=>1,
		));
		$user->addGroup($group_admin);
	}

}
````

**More than 2 ambients?** Just follow the same steps :)

**Remember that you can use this approach to customize the Group class too.**

#### Better solution?
Let me know! I'm including this feature in many projects!

Thanks for Ben of Cartalyst staff with the initial help here: http://help.cartalyst.com/discussions/questions/110-how-to-use-multiple-configurations-of-sentry-in-the-same-laravel-4-app-one-for-public-and-one-for-admin-area
<?php namespace Lib\SentryMods;
use Cartalyst\Sentry\Cookies\IlluminateCookie;
use Cartalyst\Sentry\Groups\Eloquent\Provider as GroupProvider;
use Cartalyst\Sentry\Hashing\BcryptHasher;
use Cartalyst\Sentry\Hashing\NativeHasher;
use Cartalyst\Sentry\Hashing\Sha256Hasher;
use Cartalyst\Sentry\Sentry;
use Cartalyst\Sentry\Sessions\IlluminateSession;
use Cartalyst\Sentry\Throttling\Eloquent\Provider as ThrottleProvider;
use Cartalyst\Sentry\Users\Eloquent\Provider as UserProvider;
use Illuminate\Support\ServiceProvider;
class SentryAdminServiceProvider extends ServiceProvider {
/**
* Boot the service provider.
*
* @return void
*/
public function boot(){
$this->observeEvents();
}
/**
* Register the service provider.
*
* @return void
*/
public function register(){
$this->registerHasher();
$this->registerUserProvider();
$this->registerGroupProvider();
$this->registerThrottleProvider();
$this->registerSession();
$this->registerCookie();
$this->registerSentry();
}
/**
* Register the hasher used by Sentry.
*
* @return void
*/
protected function registerHasher(){
$this->app['sentry.admin.hasher'] = $this->app->share(function($app){
$hasher = $app['config']['cartalyst/sentry::config.admin.hasher'];
switch ($hasher){
case 'native':
return new NativeHasher;
break;
case 'bcrypt':
return new BcryptHasher;
break;
case 'sha256':
return new Sha256Hasher;
break;
}
throw new \InvalidArgumentException("Invalid hasher [$hasher] chosen for Sentry.");
});
}
/**
* Register the user provider used by Sentry.
*
* @return void
*/
protected function registerUserProvider(){
$this->app['sentry.admin.user'] = $this->app->share(function($app){
$model = $app['config']['cartalyst/sentry::config.admin.users.model'];
// We will never be accessing a user in Sentry without accessing
// the user provider first. So, we can lazily setup our user
// model's login attribute here. If you are manually using the
// attribute outside of Sentry, you will need to ensure you are
// overriding at runtime.
if (method_exists($model, 'setLoginAttribute')){
$loginAttribute = $app['config']['cartalyst/sentry::sentry.users.login_attribute'];
forward_static_call_array(
array($model, 'setLoginAttribute'),
array($loginAttribute)
);
}
return new UserProvider($app['sentry.admin.hasher'], $model);
});
}
/**
* Register the group provider used by Sentry.
*
* @return void
*/
protected function registerGroupProvider(){
$this->app['sentry.admin.group'] = $this->app->share(function($app){
$model = $app['config']['cartalyst/sentry::config.admin.groups.model'];
return new GroupProvider($model);
});
}
/**
* Register the throttle provider used by Sentry.
*
* @return void
*/
protected function registerThrottleProvider(){
$this->app['sentry.admin.throttle'] = $this->app->share(function($app){
$model = $app['config']['cartalyst/sentry::sentry.throttling.model'];
$throttleProvider = new ThrottleProvider($app['sentry.admin.user'], $model);
if ($app['config']['cartalyst/sentry::config.admin.throttling.enabled'] === false){
$throttleProvider->disable();
}
if (method_exists($model, 'setAttemptLimit')){
$attemptLimit = $app['config']['cartalyst/sentry::config.admin.throttling.attempt_limit'];
forward_static_call_array(
array($model, 'setAttemptLimit'),
array($attemptLimit)
);
}
if (method_exists($model, 'setSuspensionTime')){
$suspensionTime = $app['config']['cartalyst/sentry::config.admin.throttling.suspension_time'];
forward_static_call_array(
array($model, 'setSuspensionTime'),
array($suspensionTime)
);
}
return $throttleProvider;
});
}
/**
* Register the session driver used by Sentry.
*
* @return void
*/
protected function registerSession()
{
$this->app['sentry.admin.session'] = $this->app->share(function($app)
{
return new IlluminateSession($app['session']);
});
}
/**
* Register the cookie driver used by Sentry.
*
* @return void
*/
protected function registerCookie()
{
$this->app['sentry.admin.cookie'] = $this->app->share(function($app)
{
return new IlluminateCookie($app['cookie']);
});
}
/**
* Takes all the components of Sentry and glues them
* together to create Sentry.
*
* @return void
*/
protected function registerSentry()
{
$this->app['sentry.admin'] = $this->app->share(function($app)
{
// Once the authentication service has actually been requested by the developer
// we will set a variable in the application indicating such. This helps us
// know that we need to set any queued cookies in the after event later.
$app['sentry.admin.loaded'] = true;
return new Sentry(
$app['sentry.admin.user'],
$app['sentry.admin.group'],
$app['sentry.admin.throttle'],
$app['sentry.admin.session'],
$app['sentry.admin.cookie'],
$app['request']->getClientIp()
);
});
}
/**
* Sets up event observations required by Sentry.
*
* @return void
*/
protected function observeEvents()
{
// Set the cookie after the app runs
$app = $this->app;
$this->app->after(function($request, $response) use ($app)
{
if (isset($app['sentry.admin.loaded']) and $app['sentry.admin.loaded'] == true and ($cookie = $app['sentry.admin.cookie']->getCookie()))
{
$response->headers->setCookie($cookie);
}
});
}
}
@drsii

This comment has been minimized.

Copy link

@drsii drsii commented Jul 15, 2013

this is great, lets have Ben review and see about getting this added to a docs section called use cases.

@bencorlett

This comment has been minimized.

Copy link

@bencorlett bencorlett commented Jul 15, 2013

This is great! Let's get this as official docs!!

@dhrrgn

This comment has been minimized.

Copy link

@dhrrgn dhrrgn commented Jul 15, 2013

Just throwing in my 2 cents: That seems awfully un-maintainable in the long run. What if something changes in the Sentry core that breaks your provider? You have to keep it up to date with the core provider with every update...not very efficient.

Other than that, this is well done.

@ronaldcastillo

This comment has been minimized.

Copy link

@ronaldcastillo ronaldcastillo commented Jul 16, 2013

If using Laravel, why not using a polymorphic relationship ? http://laravel.com/docs/eloquent#polymorphic-relations
That way you can still have different columns for different user types.

@yusidabcs

This comment has been minimized.

Copy link

@yusidabcs yusidabcs commented Jul 18, 2013

hello there, I've try step by step but i got error : Class 'SentryAdmin' not found. If i need to register alias for it? thanks you

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Jul 18, 2013

@yusida, what is the file the error is throwing?

Don't forget to run composer dump-autoload

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Jul 18, 2013

@ronaldcastillo, can you show a working example?

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Jul 18, 2013

@dandoescode, this is really un-maintable for a long run, but for now it's working fine for me.
Let's wait better improvements from Cartalyst team in the future. So, while this not happen, I'll post here all modifications I do.

@pajcho

This comment has been minimized.

Copy link

@pajcho pajcho commented Jul 31, 2013

This is my solution for FuelPHP implementation: https://gist.github.com/pajcho/6121291

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Aug 2, 2013

Nice work, @pajcho. I've extended the group model with the same approach and it worked fine.

Waiting for official docs with better adjusts :)

@barinali

This comment has been minimized.

Copy link

@barinali barinali commented Aug 10, 2013

I can't use this. I get "Class sentry.admin does not exist" error. Where am I doing wrong?

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Aug 10, 2013

@alibarin what is the file and line of the error?

@barinali

This comment has been minimized.

Copy link

@barinali barinali commented Aug 10, 2013

@leabdalla I think I fixed the problem by adding "SentryAdmin ServiceProvider" line to providers array in app.php.

@tavicu

This comment has been minimized.

Copy link

@tavicu tavicu commented Jan 28, 2014

Hei!

Can someone help me? I can't access http://paste.laravel.com

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Jan 29, 2014

@tavicu, I've attached files to this gist. Thanks for advising!

@tavicu

This comment has been minimized.

Copy link

@tavicu tavicu commented Feb 11, 2014

@leadbella Thanks. And also, i think at step 3 the path need to be app/config/packages/cartalyst/sentry/config.php instead of app/config/cartalyst/sentry/config.php

And i have another problem that i don't know how to solve. Please take a look over here: http://laravel.io/forum/02-11-2014-serviceprovider-in-a-folder

@lorvent

This comment has been minimized.

Copy link

@lorvent lorvent commented Feb 15, 2014

is this working fine for laravel 4.1 and sentry 3?
Can i use this without any issues for now?

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Feb 15, 2014

@lorvent12345 I have no feedback with Sentry 3.
My suggestion is follow the same steps but using your current version files instead copying from this gist.

@whobutsb

This comment has been minimized.

Copy link

@whobutsb whobutsb commented Feb 19, 2014

Hey @leabdalla,
When I'm in art tinker and try to run print_r(SentryAdmin::getUserProvider()); I get the error: PHP Fatal error: Class 'SentryAdmin' not found

I was wondering if there was anything I needed to add in my config/app.php in the Providers or the Alias sections?

In my composer.json config in the autoload section I have: app/lib.

Thanks for the help!

@whobutsb

This comment has been minimized.

Copy link

@whobutsb whobutsb commented Feb 19, 2014

Alrighty I was able to hack my way through and get a fix. Here are some additions for your documentation

In my config/app.php:

  • in the providers section I added: Lib\SentryMods\SentryAdminServiceProvider
  • in the aliases section I added: 'SentryAdmin' => 'Lib\SentryMods\SentryAdmin'

Then in the lib/SentryMods/SentryAdminServiceProvider.php I had to change the registerSession function to read:

protected function registerSession()
{
    $this->app['sentry.admin.session'] = $this->app->share(function($app)
    {
        $key = $app['config']['cartalyst/sentry::admin.cookie.key'];

        return new IlluminateSession($app['session.store'], $key);
    });
}
@siegerhansma

This comment has been minimized.

Copy link

@siegerhansma siegerhansma commented May 21, 2014

Just tried this with Laravel 4.1. In the SentryAdminServiceProvider this should be the RegisterCookie function:

 protected function registerCookie()
    {
        $this->app['sentry.admin.cookie'] = $this->app->share(function($app)
        {
            $key = $app['config']['cartalyst/sentry::admin.cookie.key'];

            /**
             * We'll default to using the 'request' strategy, but switch to
             * 'jar' if the Laravel version in use is 4.0.*
             */

            $strategy = 'request';

            if (preg_match('/^4\.0\.\d*$/D', $app::VERSION))
            {
                $strategy = 'jar';
            }

            return new IlluminateCookie($app['request'], $app['cookie'], $key, $strategy);

        });
    }
@luislora-es

This comment has been minimized.

Copy link

@luislora-es luislora-es commented Jun 13, 2014

I have an error. 'Symfony\Component\Debug\Exception\FatalErrorException' with message 'Call to undefined method Cartalyst\Sentry\Cookies\IlluminateCookie::getCookie()' in D:\www\estacionagestion\app\libraries\SentryMods\SentryAdminServiceProvider.php:227

It's in function observeEvents, in this line:

if (isset($app['sentry.admin.loaded']) and $app['sentry.admin.loaded'] == true and ($cookie = $app['sentry.admin.cookie']->getCookie()))

Can anyone help me? I followed all steps and my code is identical.

Thanks in advance!

@leabdalla

This comment has been minimized.

Copy link
Owner Author

@leabdalla leabdalla commented Jun 21, 2014

@luislora-es, take a look at the comment of @shansma with registerCookie() function

@pkhodaveissi

This comment has been minimized.

Copy link

@pkhodaveissi pkhodaveissi commented Aug 26, 2014

Has too many bugs in laravel 4.2, I've tried every comment bu still there are bugs like :
Call to undefined method Cartalyst\Sentry\Cookies\IlluminateCookie::getCookie()

anything?

thanks for the work

@mariapaulinar

This comment has been minimized.

Copy link

@mariapaulinar mariapaulinar commented Sep 8, 2014

If anyone get error when is creating Admin model, try modify Sentry User model (vendor\cartalyst\sentry\src\Cartalyst\Sentry\Users\Eloquent\User.php) adding this:

public function getRememberToken()
{
return $this->remember_token;
}

public function setRememberToken($value)
{
$this->remember_token = $value;
}

public function getRememberTokenName()
{
return 'remember_token';
}

NOTE: add column remember_token VARCHAR(255) or text, in users table.

@rained23

This comment has been minimized.

Copy link

@rained23 rained23 commented Oct 19, 2014

why dont we fork Sentry, make the service provider customizable from config file ?
So we just need to maintain our new repo services provider only if something broken because of a new commit made by Sentry. So everyone that really need to use this type of customization will automatically fixed too. Would that be nice @leabdalla. MultiInstanceSentry maybe ? :)

@umanda

This comment has been minimized.

Copy link

@umanda umanda commented Nov 8, 2014

OMG..... I have spent hours.. get fix this. @leabdalla's tutorial and codes for Laravel 4 and Sentry 2. but I have Laravel 4.2 and Sentry 2.x not 2.0. So there are several differences. but I have get it done. I have added a new GITS with my fixes.

https://gist.github.com/umanda/c4be8da584715cbb901d

Thank you very much for all and cheers
@leabdalla your idea is awsom dude !!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.