Skip to content

Instantly share code, notes, and snippets.

@leabdalla
Last active February 10, 2024 10:57
  • Star 38 You must be signed in to star a gist
  • Fork 14 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save leabdalla/5999421 to your computer and use it in GitHub Desktop.
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);
}
});
}
}
@siegerhansma
Copy link

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

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

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

@pkhodaveissi
Copy link

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

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

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