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);
}
});
}
}
@leabdalla
Copy link
Author

leabdalla commented Jun 21, 2014

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

@pkhodaveissi
Copy link

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
Copy link

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
Copy link

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
Copy link

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