In Laravel 5 how can you customize the default authentication which comes out of the box? For example I have multiple types of users to authenticate against. Each type of user is defined by a role i.e Jobseeker, Recruiter etc. Each type of user will have a different type of registration form to capture some of the profile details as well. So I have the following tables:
users
roles
role_user
jobseeker_profile
recruiter_profile
The default authcontroller and passwordcontroller in Laravel 5 uses traits for all the authentication methods. How would you go about customizing it - do you guys edit the existing trait files? So for example the getRegister method returns the register view but I would want it to check the route before deciding which view to show.
// default method
public function getRegister()
{
return view('auth.register');
}
// custom method
public function getRegister()
{
if (Request::is('jobseeker/register'))
{
return view('auth.jobseeker_register');
}
elseif (Request::is('recruiter/register'))
{
return view('auth.recruiter_register');
}
}
Similarly the default postLogin method is as follows:
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if ($this->auth->attempt($credentials, $request->has('remember')))
{
return redirect()->intended($this->redirectPath());
}
return redirect($this->loginPath())
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => 'These credentials do not match our records.',
]);
}
But I would want the method to also check the user roles as follows:
public function postLogin(Request $request)
{
$this->validate($request, [
'email' => 'required|email', 'password' => 'required',
]);
$credentials = $request->only('email', 'password');
if ($this->auth->attempt($credentials, $request->has('remember')))
{
if(Auth::user()->role->name == 'recruiter')
{
return redirect()->to('/recruiter/dashboard');
}
elseif(Auth::user()->role->name == 'jobseeker')
{
return redirect()->to('jobseeker/dashboard');
}
}
return redirect($this->loginPath())
->withInput($request->only('email', 'remember'))
->withErrors([
'email' => 'These credentials do not match our records.',
]);
}
So my question is how do you go about customizing the existing authentication? Do you guys create a new controller perhaps CustomAuthController, CustomPasswordController and copy all the traits from the default auth controllers into these custom controllers and edit them as appropriate? I'm unable to find any Laravel 5 tutorials on how to acheive this - they all simply talk about the default out of the box authentication. If anyone has done something similar before I would love to hear about how you went about it and which files were edited to wire this custom auth up.
You have a couple of options:
Override the methods in the existing auth controller.
Just don’t implement the AuthenticatesAndRegistersUsers trait at all, and implement authentication logic entirely yourself.
With regards to redirect, I’d listen on the auth.login event, check your user’s type there, and then redirect to the specific dashboard there and then.
Related
I am trying to write a large project using laravel8 and will need role based access. So I have created a roles table and linked it to the users table via a modal table called role_user. Now I am able to create the access and perform checks inside controllers and users model and everything works correctly
my problem is that this way I have to keep checcking if a user has access in each and every function for all my user access levels and this is tedious.
I have thus tried to convert this approcah and use middlewares but the problem is that I am unable to redirect the users to the appropriate dashboard upon authentication and they are both being redirected to the home page that is designated for users only.
I have tried the following
I have addes the following code to the users model to create a many to many relationship between the users and the roles and also to check if the user has roles and peform appropriate tasks as per the code blocks
public function roles() {
return $this->belongsToMany(Role::class);
}
public function checkRoles($roles) {
if ( ! is_array($roles)) {
$roles = [$roles];
return false;
}
if ( ! $this->hasAnyRole($roles)) {
auth()->logout();
abort(404);
}
}
public function hasAnyRole($roles): bool {
return (bool) $this->roles()->whereIn('name', $roles)->first();
}
public function hasRole($role): bool {
return (bool) $this->roles()->where('name', $role)->first();
}
Now this would have worked if I proceeded to peforem checks on each controller's model directly but after creating middlewares for each role and peforming checks in each as follows things failled
a) Admin Middleware
public function handle(Request $request, Closure $next){
if (Auth::user() && $request->user()->hasRole('admin')) {
return $next($request);
}
return redirect('home')->with('error','You have not admin access');
}
b) SuperAdmin Middleware
public function handle(Request $request, Closure $next) {
if (Auth::user() && $request->user()->hasRole('super_admin')) {
return $next($request);
}
return redirect('home')->with('error','You have not permission to peform this task');
}
Now after registering all the middlewareds inside the Kernel class, I modified the login Controller class and added the following code inside the construct function:
if(Auth::check()){
if(Auth::user()->hasRole('super_admin')){
return redirect(RouteServiceProvider::SUPERADMINADMINHOME);
}elseif(Auth::user()->hasRole('admin')){
return redirect(RouteServiceProvider::ADMINHOME);
}elseif(Auth::user()->hasRole('vendor')){
return redirect(RouteServiceProvider::VENDORHOME);
}else {
return redirect(RouteServiceProvider::HOME);
}
}
I also updated my routes as follows
Route::group(['prefix' => 'super', 'as' => 'super.', 'namespace' => 'Super', 'middleware' => ['Super']], function () {
Route::get('/', [App\Http\Controllers\SuperAdminController::class,'index'])->name('superadminhome');
});
Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin', 'middleware' => ['Admin']], function () {
Route::get('/', [App\Http\Controllers\AdminController::class,'index'])->name('adminhome');
});
Route::group(['prefix' => 'vendor', 'as' => 'vendor.', 'namespace' => 'Vendor', 'middleware' => ['Vendor']], function () {
Route::get('/', [App\Http\Controllers\VendorController::class,'index'])->name('vendorhome');
});
After doing all this, I tried tom login with the superadmin credentials and the admin credentials but all of them take me to the same page home. What could I be doing wrong or where can I get step by step guide to how to achieve this task noting that I am new to middlewares in laravel.
From what I can see you are having this problem becasue the checks are all yielding false hence the last is being taken into account. That would mean that the way in which you are checking for the roles is probably wrong. I would suggest looping through the roles and using a switch statement. checking if it matches the ones you need and then redirecting appropriately.
This would mean changing your if statements that check for the availability of a specific roles.
That is this part of your code
if(Auth::user()->hasRole('super_admin')){
return redirect(RouteServiceProvider::SUPERADMINADMINHOME);
}elseif(Auth::user()->hasRole('admin')){
return redirect(RouteServiceProvider::ADMINHOME);
}elseif(Auth::user()->hasRole('vendor')){
return redirect(RouteServiceProvider::VENDORHOME);
}else {
return redirect(RouteServiceProvider::HOME);
}
I use CakePHP's AuthenticationPlugin. I was trying to implement RememberMe functionality into this.
I found the following article when I was reading the Cakephp documentation.
Cookie Authenticator aka “Remember Me”
However, the documentation here is difficult for me to understand. I have no idea what to do with it.
I've successfully implemented EncryptedCookieMiddleware. I have no idea what to do after that.
I don't know how to use rememberMeField, how to use fields and how to use cookies.
$this->Authentication->rememberMeField
$this->Authentication->fields
I tried to see if I could use it like these, but it was still no good.
Please let me know how to use these.
Also, do you know of any RememberMe tutorials?
How do I implement it?
Sorry. Please help me...
// in config/app.php
'Security' => [
.....
'cookieKey' => env('SECURITY_COOKIE_KEY', 'AnyString'), // <- add
],
// in src/Application.php
use Cake\Http\Middleware\EncryptedCookieMiddleware; // <- add
// in middleware()
public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
$cookies = new EncryptedCookieMiddleware( // <- add
['mail', 'password'],
Configure::read('Security.cookieKey')
);
$middlewareQueue
// . . .
->add($cookies) // <-add
->add(new AuthenticationMiddleware($this));
So far I've been able to implement it myself. I'm confident.
The problem is after this. We have no idea what to do with it...
A Remember me checkbox was implemented in the template Form.
$this->request->getData('rememberMe'); to get it.
If this is 1, the checkbox was pressed.
// in src/Controller/UsersController
public function login()
{
$this->request->allowMethod(['get', 'post']);
if ($this->request->is('post')) {
$result = $this->Authentication->getResult();
// If the user is logged in, whether POST or GET, we will redirect
$requestGetData = $this->request->getData('rememberMe');
if ($requestGetData['rememberMe'] == 1){
$this->Authentication->cookie['name'] = $requestGetData['mail'];
$this->Authentication->cookie['name'] = $requestGetData['password']
}
if ($result->isValid()) {
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Stores',
'action' => 'index',
]);
return $this->redirect($redirect);
}
// If the user fails to authenticate after submitting, an error is displayed.
if (!$result->isValid()) {
$this->Flash->error(__('Your email address or password is incorrect.'));
}
}
$title = $this->config('Users.title.login');
$message = $this->config('Users.message.login');
$this->set(compact('login_now', 'title', 'message'));
}
I know that's not true. But I tried to implement something like this just in case.
Please help me!
Changed around the login.
public function login()
{
$this->request->allowMethod(['get', 'post']);
if ($this->request->is('post')) {
$result = $this->Authentication->getResult();
$requestData = $this->request->getData();
if ($result->isValid()) {
$redirect = $this->request->getQuery('redirect', [
'controller' => 'Stores',
'action' => 'index',
]);
$this->Authentication->getAuthenticationService()->loadAuthenticator( 'Authentication.Cookie', [
'fields' => ['mail', 'password']
]
);
return $this->redirect($redirect);
}
if ($this->request->is('post') && !$result->isValid()) {
$this->Flash->error(__('Your email address or password is incorrect.'));
}
}
$title = $this->config('Users.title.login');
$message = $this->config('Users.message.login');
$this->set(compact('title', 'message'));
}
You're not supposed to load authenticators in your controllers, authentication happens at middleware level, before any of your controllers are being invoked.
The cookie authenticator is ment to be loaded and configured just like any other authenticator, that is where you create the authentication service, usually in Application::getAuthenticationService() in src/Application.php.
By default the field in the form must be remember_me, not rememberMe, that is unless you would configure the cookie authenticator's rememberMeField option otherwise.
Furthermore the default cookie name of the cookie authenticator is CookieAuth, so if you wanted to encrypt it, you'd have to use that name in the EncryptedCookieMiddleware config accordingly.
tl;dr
Remove all cookie related code from your controller, and load the authenticator in your Application::getAuthenticationService() method:
use Authentication\Identifier\IdentifierInterface;
// ...
public function getAuthenticationService(ServerRequestInterface $request): AuthenticationServiceInterface
{
$service = new AuthenticationService();
// ...
// The cookie authenticator should be loaded _after_ the session authenticator,
// and _before_ other authenticators like the form authenticator
$service->loadAuthenticator('Authentication.Cookie', [
// 'rememberMeField' => 'custom_form_field_name', // if you want to change the default
'fields' => [
IdentifierInterface::CREDENTIAL_USERNAME => 'mail',
IdentifierInterface::CREDENTIAL_PASSWORD => 'password',
],
]);
// ...
return $service;
}
set the authentication cookie name in the EncryptedCookieMiddleware config:
$cookies = new EncryptedCookieMiddleware(
['CookieAuth'],
Configure::read('Security.cookieKey')
);
and change the field name in your form to remember_me if you're using the cookie authenticator's defaults:
echo $this->Form->control('remember_me', [
'type' => 'checkbox'
]);
That's all that should be required, if you tick the checkbox in your login form, then the authentication middleware will set a cookie after successful authentication accordingly, and it will pick up the cookie if it's present on a request and no other authenticator successfully authenticates the request first (like the session authenticator for example).
Can anyone help me understand the deal with Cakephp 3.3 and a BeforeFilter/Auth Redirect issue I'm experiencing.
I'm using the default Auth component. I've created a custom component that additionally checks for a session variable (Registration), and if that variable is not set redirects to a page designed to make a selection to set the desired Registration.
Here's my custom component:
<?php
namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\Network\Request;
class RegistrationCheckComponent extends Component
{
private $_allowedActions = [];
private $_superUserBypass = false;
public $components = ['Auth'];
public function superUserBypass($val = false) {
$this->_superUserBypass = $val;
}
public function allow(Array $allowedActions = []) {
$this->_allowedActions = $allowedActions;
}
public function verify() {
if($this->_superUserBypass) {
return true;
}
$session = $this->request->session();
//if Auth Registration is not set
if(!$session->read('Auth.Registration')) {
//if requested action is not in the array of allowed actions, redirect to select registration
if(!in_array($this->request->param('action'), $this->_allowedActions)) {
return $this->redirect();
};
return true;
}
return true;
}
public function redirect() {
$controller = $this->_registry->getController();
return $controller->redirect($this->config('redirect'));
}
}
Not all controller's require the Registration variable to be set, that's why I decided to go with the component approach. The component is however loaded in the AppController by this line:
$this->loadComponent('RegistrationCheck', ['redirect' => ['controller' => 'Users', 'action' => 'registrations']]);
In the controllers that require the Registration variable to be set, I include the following beforeFilter function:
public function beforeFilter(Event $event) {
parent::beforeFilter($event);
return $this->RegistrationCheck->verify();
}
Now, I've had some Integration Tests defined, here's one of them:
public function testUnauthenticatedEdit()
{
$this->get('/teams/edit');
$this->assertRedirect(['controller' => 'Users', 'action' => 'login']);
}
So, after I implemented my RegistrationCheck component, I ran the Integration Tests. I was expecting the test to pass, it did not. The interesting thing is that it actually returned a redirect to Users->registrations rather than Users->login as I had expected.
It looks to me that the RegistrationCheck redirect is happening before the Auth component redirect. I'm not sure it's a huge deal, because a redirect to registrations without Auth set is going to end up redirecting back to login, but it seems incorrect to ignore it...also, I'd just like to understand a bit more of what is actually going on.
Can anyone suggest changes to my code that would ensure the Auth component is handled before the RegistrationCheck component?
Thanks in advance.
Well, after a little more research, I found the answer I'm looking for here: http://book.cakephp.org/3.0/en/controllers/components/authentication.html#deciding-when-to-run-authentication
Pretty simple really, just wanted to include an answer here for anyone who may stumble across the same question.
REST API is working without authentication methods. Now i wanted to authenticate REST API with OAuth2 authentication for API requests via mobile application. I tried with yii2 guide, but it didn't work for me.
basically mobile user need to be login with username & password, if a username and password are correct, user need to be login and further API request need to be validate with token.
Do i need to create custom OAuth 2 client like this ?
Creating your own auth clients
access_token field in user table is empty. do i need to save it manually ?
how to return access_token as a respond?
is there any reason for user all three methods(HttpBasicAuth, HttpBearerAuth, QueryParamAuth) at once, why? how?
my application folder structure looks like below.
api
-config
-modules
--v1
---controllers
---models
-runtime
-tests
-web
backend
common
console
environments
frontend
api\modules\v1\Module.php
namespace api\modules\v1;
class Module extends \yii\base\Module
{
public $controllerNamespace = 'api\modules\v1\controllers';
public function init()
{
parent::init();
\Yii::$app->user->enableSession = false;
}
}
api\modules\v1\controllers\CountryController.php
namespace api\modules\v1\controllers;
use Yii;
use yii\rest\ActiveController;
use common\models\LoginForm;
use common\models\User;
use yii\filters\auth\CompositeAuth;
use yii\filters\auth\HttpBasicAuth;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\auth\QueryParamAuth;
/**
* Country Controller API
*
* #author Budi Irawan <deerawan#gmail.com>
*/
class CountryController extends ActiveController
{
public $modelClass = 'api\modules\v1\models\Country';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
//'class' => HttpBasicAuth::className(),
'class' => CompositeAuth::className(),
'authMethods' => [
HttpBasicAuth::className(),
HttpBearerAuth::className(),
QueryParamAuth::className(),
],
];
return $behaviors;
}
}
common\models\User.php
namespace common\models;
use Yii;
use yii\base\NotSupportedException;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\web\IdentityInterface;
class User extends ActiveRecord implements IdentityInterface
{
const STATUS_DELETED = 0;
const STATUS_ACTIVE = 10;
public static function tableName()
{
return '{{%user}}';
}
public function behaviors()
{
return [
TimestampBehavior::className(),
];
}
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_ACTIVE],
['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],
];
}
public static function findIdentity($id)
{
return static::findOne(['id' => $id, 'status' => self::STATUS_ACTIVE]);
}
public static function findIdentityByAccessToken($token, $type = null)
{
return static::findOne(['access_token' => $token]);
}
}
user table
id
username
auth_key
password_hash
password_reset_token
email
status
created_at
access_token
access_token was added after migrate user table
I'm using JWT for validating the request. Basically JWT is a token which also contain information about a user, and about the token itself such as the validity and the expiration time of the token. You can read more about JWT here.
The flow of my application is like this:
First, when a user logged in, create a JWT for the user
$key = base64_decode('some_random_string');
$tokenId = base64_encode(mcrypt_create_iv(32));
$issuedAt = time();
$notBefore = $issuedAt + 5;
$expire = $notBefore + 1800;
$user = User::findByEmail($email);
$data = [
'iss' => 'your-site.com',
'iat' => $issuedAt,
'jti' => $tokenId,
'nbf' => $notBefore,
'exp' => $expire,
'data' => [
'id' => $user->id,
'username' => $user->username,
//put everything you want (that not sensitive) in here
]
];
$jwt = JWT::encode($data, $key,'HS256');
return $jwt;
Then, the client (e.g the mobile app) must provide the token in every request via Authorization header. The header will look like this:
Authorization:Bearer [the JWT token without bracket]
In the User model, add a method like this for validating the token:
public static function findIdentityByAccessToken($token, $type = null) {
$key = base64_decode('the same key that used in login function');
try{
$decoded = JWT::decode($token, $key, array('HS256'));
return static::findByEmail($decoded->data->email);
}catch (\Exception $e){
return null;
}
}
The JWT library will raise an Exception if the token is no longer invalid (have been tampered or have been past the expiry time).
Then, add this to the behaviors function in every controller:
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
'except' => ['login'] //action that you don't want to authenticate such as login
];
That's it! I hope this work like you wanted. Oh, and there is lot of JWT libraries that you can use (you can see it here), but I personally use this library by people from firebase
You Can create Your Auth System, usually I do it.
You can Save Tokens for every user, and after it authentify user by that token.
In every action You can send that token for authentify user.
You need to do the following:
set the token before saving the user in the User model.
add actionLogin in the UserController to return the auth_key on user login.
in each API request you send the auth_key in the header instead of
sending username and password.
to check if the auth_key is valid, define 'authenticator' in the
UserController behaviors.
you can find code samples in my answer to another question here
I need to create a new "auth" config with another table and users. I have one table for the "admin" users and another table for the normal users.
But how can I create another instance of Auth with a different configuration?
While trying to solve this problem myself, I found a much simpler way. I basically created a custom ServiceProvider to replace the default Auth one, which serves as a factory class for Auth, and allows you to have multiple instances for multiple login types. I also stuck it all in a package which can be found here: https://github.com/ollieread/multiauth
It's pretty easy to use really, just replace the AuthServiceProvider in app/config/app.php with Ollieread\Multiauth\MultiauthServiceProvider, then change app/config/auth.php to look something like this:
return array(
'multi' => array(
'account' => array(
'driver' => 'eloquent',
'model' => 'Account'
),
'user' => array(
'driver' => 'database',
'table' => 'users'
)
),
'reminder' => array(
'email' => 'emails.auth.reminder',
'table' => 'password_reminders',
'expire' => 60,
),
);
Now you can just use Auth the same way as before, but with one slight difference:
Auth::account()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::user()->attempt(array(
'email' => $attributes['email'],
'password' => $attributes['password'],
));
Auth::account()->check();
Auth::user()->check();
It also allows you to be logged in as multiple user types simultaneously which was a requirement for a project I was working on. Hope it helps someone other than me.
UPDATE - 27/02/2014
For those of you that are just coming across this answer, I've just recently added support for reminders, which can be accessed in the same factory style way.
You can "emulate" a new Auth class.
Laravel Auth component is basically the Illuminate\Auth\Guard class, and this class have some dependencies.
So, basically you have to create a new Guard class and some facades...
<?php
use Illuminate\Auth\Guard as AuthGuard;
class CilentGuard extends AuthGuard
{
public function getName()
{
return 'login_' . md5('ClientAuth');
}
public function getRecallerName()
{
return 'remember_' . md5('ClientAuth');
}
}
... add a ServiceProvider to initialize this class, passing it's dependencies.
<?php
use Illuminate\Support\ServiceProvider;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Hashing\BcryptHasher;
use Illuminate\Auth\Reminders\PasswordBroker;
use Illuminate\Auth\Reminders\DatabaseReminderRepository;
use ClientGuard;
use ClientAuth;
class ClientServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerAuth();
$this->registerReminders();
}
protected function registerAuth()
{
$this->registerClientCrypt();
$this->registerClientProvider();
$this->registerClientGuard();
}
protected function registerClientCrypt()
{
$this->app['client.auth.crypt'] = $this->app->share(function($app)
{
return new BcryptHasher;
});
}
protected function registerClientProvider()
{
$this->app['client.auth.provider'] = $this->app->share(function($app)
{
return new EloquentUserProvider(
$app['client.auth.crypt'],
'Client'
);
});
}
protected function registerClientGuard()
{
$this->app['client.auth'] = $this->app->share(function($app)
{
$guard = new Guard(
$app['client.auth.provider'],
$app['session.store']
);
$guard->setCookieJar($app['cookie']);
return $guard;
});
}
protected function registerReminders()
{
# DatabaseReminderRepository
$this->registerReminderDatabaseRepository();
# PasswordBroker
$this->app['client.reminder'] = $this->app->share(function($app)
{
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['redirect'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
});
}
protected function registerReminderDatabaseRepository()
{
$this->app['client.reminder.repository'] = $this->app->share(function($app)
{
$connection = $app['db']->connection();
$table = 'client_reminders';
$key = $app['config']['app.key'];
return new DatabaseReminderRepository($connection, $table, $key);
});
}
public function provides()
{
return array(
'client.auth',
'client.auth.provider',
'client.auth.crypt',
'client.reminder.repository',
'client.reminder',
);
}
}
In this Service Provider, I put some example of how to create a 'new' password reminder component to.
Now you need to create two new facades, one for authentication and one for password reminders.
<?php
use Illuminate\Support\Facades\Facade;
class ClientAuth extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.auth';
}
}
and...
<?php
use Illuminate\Support\Facades\Facade;
class ClientPassword extends Facade
{
protected static function getFacadeAccessor()
{
return 'client.reminder';
}
}
Of course, for password reminders, you need to create the table in database, in order to work. In this example, the table name should be client_reminders, as you can see in the registerReminderDatabaseRepository method in the Service Provider. The table structure is the same as the original reminders table.
After that, you can use your ClientAuth the same way you use the Auth class. And the same thing for ClientPassword with the Password class.
ClientAuth::gust();
ClientAuth::attempt(array('email' => $email, 'password' => $password));
ClientPassword::remind($credentials);
Don't forget to add your service provider to the service providers list in the app/config/app.php file.
UPDATE:
If you are using Laravel 4.1, the PasswordBroker doesn't need the Redirect class anymore.
return new PasswordBroker(
$app['client.reminder.repository'],
$app['client.auth.provider'],
$app['mailer'],
'emails.client.reminder' // email template for the reminder
);
UPDATE 2
Laravel 5.2 just introduced multi auth, so this is no longer needed in this version.
Ok, I had the same problem and here is how I solved it:
actually in laravel 4 you can simply change the auth configs at runtime so to do the trick you can simply do the following in your App::before filter:
if ($request->is('admin*'))
{
Config::set('auth.model', 'Admin');
}
this will make the Auth component to use th Admin model when in admin urls. but this will lead to a new problem, because the login session key is the same if you have two users in your admins and users table with the same id you will be able to login to the admin site if you have logged in before as a regular user! so to make the two different authetications completely independent I did this trick:
class AdminGuard extends Guard
{
public function getName()
{
return 'admin_login_'.md5(get_class($this));
}
public function getRecallerName()
{
return 'admin_remember_'.md5(get_class($this));
}
}
Auth::extend('eloquent.admin', function()
{
return new AdminGuard(new EloquentUserProvider(new BcryptHasher, 'Admin'), App::make('session.store'));
});
and change the App::before code to:
if ($request->is('admin*'))
{
Config::set('auth.driver', 'eloquent.admin');
Config::set('auth.model', 'Admin');
}
you can see that I made a new auth driver and rewrote some methods on the Guard class so it will generate different session keys for admin site. then I changed the driver for the admin site. good luck.
I had the same problem yesterday, and I ended up creating a much simpler solution.
My requirements where 2 different tables in two different databases. One table was for admins, the other was for normal users. Also, each table had its own way of hashing. I ended up with the following (Code also available as a gist on Github: https://gist.github.com/Xethron/6790029)
Create a new UserProvider. I called mine MultiUserProvider.php
<?php
// app/libraries/MultiUserProvider.php
use Illuminate\Auth\UserProviderInterface,
Illuminate\Auth\UserInterface,
Illuminate\Auth\GenericUser;
class MultiUserProvider implements UserProviderInterface {
protected $providers;
public function __construct() {
// This should be moved to the config later...
// This is a list of providers that can be used, including
// their user model, hasher class, and hasher options...
$this->providers = array(
'joomla' => array(
'model' => 'JoomlaUser',
'hasher' => 'JoomlaHasher',
)
'another' => array(
'model' => 'AnotherUser',
'hasher' => 'AnotherHasher',
'options' => array(
'username' => 'empolyee_number',
'salt' => 'salt',
)
),
);
}
/**
* Retrieve a user by their unique identifier.
*
* #param mixed $identifier
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveById($identifier)
{
// Returns the current provider from the session.
// Should throw an error if there is none...
$provider = Session::get('user.provider');
$user = $this->createModel($this->providers[$provider]['model'])->newQuery()->find($identifier);
if ($user){
$user->provider = $provider;
}
return $user;
}
/**
* Retrieve a user by the given credentials.
*
* #param array $credentials
* #return \Illuminate\Auth\UserInterface|null
*/
public function retrieveByCredentials(array $credentials)
{
// First we will add each credential element to the query as a where clause.
// Then we can execute the query and, if we found a user, return it in a
// Eloquent User "model" that will be utilized by the Guard instances.
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$query = $this->createModel($this->providers[$provider]['model'])->newQuery();
foreach ($credentials as $key => $value)
{
if ( ! str_contains($key, 'password') && ! str_contains($key, 'provider'))
$query->where($key, $value);
}
$user = $query->first();
if ($user){
Session::put('user.provider', $provider);
$user->provider = $provider;
}
return $user;
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Auth\UserInterface $user
* #param array $credentials
* #return bool
*/
public function validateCredentials(UserInterface $user, array $credentials)
{
$plain = $credentials['password'];
// Retrieve the provider from the $credentials array.
// Should throw an error if there is none...
$provider = $credentials['provider'];
$options = array();
if (isset($this->providers[$provider]['options'])){
foreach ($this->providers[$provider]['options'] as $key => $value) {
$options[$key] = $user->$value;
}
}
return $this->createModel($this->providers[$provider]['hasher'])
->check($plain, $user->getAuthPassword(), $options);
}
/**
* Create a new instance of a class.
*
* #param string $name Name of the class
* #return Class
*/
public function createModel($name)
{
$class = '\\'.ltrim($name, '\\');
return new $class;
}
}
Then, I told Laravel about my UserProvider by adding the following lines to the top of my app/start/global.php file.
// app/start/global.php
// Add the following few lines to your global.php file
Auth::extend('multi', function($app) {
$provider = new \MultiUserProvider();
return new \Illuminate\Auth\Guard($provider, $app['session']);
});
And then, I told Laravel to use my user provider instead of EloquentUserProvider in app/config/auth.php
'driver' => 'multi',
Now, when I authenticate, I do it like so:
Auth::attempt(array(
'email' => $email,
'password' => $password,
'provider'=>'joomla'
)
)
The class would then use the joomlaUser model, with the joomlaHasher, and no options for the hasher... If using 'another' provider, it will include options for the hasher.
This class was built for what I required but can easily be changed to suite your needs.
PS: Make sure the autoloader can find MultiUserProvider, else it won't work.
I'm using Laravel 5 native auth to handle multiple user tables...
It's not difficult, please check this Gist:
https://gist.github.com/danielcoimbra/64b779b4d9e522bc3373
UPDATE: For Laravel 5, if you need a more robust solution, try this package:
https://github.com/sboo/multiauth
Daniel