i'm trying to develop a multi tenant, app in laravel, with multiple DBs and subdomains, so far i'm using the default user guard for authenticating in the main domain let's say it's example.com, it works fine, i'm also using a different guard for the subdomains, registration works fine, but the login seems to be broken, it authenticates the user but if i try to Auth:user() or even redirect to a protected route it looks like the user has already logged out.
I'm using relational database as the session driver (to avoid subdomains user to modify the cookies domain and access other subdomains), the sessions seems to be stored correctly in the sessions table of the main domain, but in the subdomain every record has the user_id set as null.
Laravel 8.28.1
PHP 7.4.12
Multi tenancy by https://tenancyforlaravel.com
Here is my config/auth.php file
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
// this is the guard for subdomains
'collaboratore' => [
'driver' => 'session',
'provider' => 'collaboratori',
],
],
/*
|--------------------------------------------------------------------------
| User Providers
|--------------------------------------------------------------------------
*/
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'collaboratori' => [
'driver' => 'eloquent',
'model' => App\Models\Collaboratore::class,
],
this is my model for users in the subdomains
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Collaboratore extends Authenticatable
{
use HasFactory, Notifiable;
protected $table = 'collaboratore';
protected $guard = 'collaboratore';
public $primaryKey = 'id';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'username',
'password',
'email',
// ... other stuff ...
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
and this is my controller for users in the subdomains
public function login(Request $request )
{
// validate request
$credentials = $this->validate($request, [
'email' => 'required|email',
'password' => 'required'
]);
if ( Auth::guard('collaboratore')->attempt( $credentials ) )
{
// login successful
return redirect('/home');
}
//dd("failed");
// login failed
return $request->expectsJson()
? response([ 'message' => 'Invalid credentials', 401 ])
: redirect()->back()->withInput($request->only('email', 'remember'));
}
any help would be appreciated, i'm kinda stuck right now
From Laravel website https://laravel.com/docs/8.x/authentication#introduction
The attempt method is normally used to handle authentication attempt's
from your application's "login" form. If authentication is successful,
you should regenerate the user's session to prevent session fixation:
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
So you should add $request->session()->regenerate(); inside your if attempt.
it looks like i managed to solve his, the problem was here, i changed
public function __construct()
{
$this->middleware('guest')->except('logout');
}
to this
public function __construct()
{
$this->middleware('web');
}
and now it's working, but the session is still note being stored
Related
I am having some problems getting Laravel Sanctum authorising two tables in two separate databases.
I am using Laravel Sanctum tokens for authorisation. I have two tables to authorise users (users & contacts) I have setup two separate guards and can get everything to work on a single database with one token table.
However I want to have the contacts table in a separate database. Doing so creates two personal_access_tokens tables, one in the Users database and the other in the Contacts database, which I don't mind. I can create the tokens just fine, however when I try to authorise contacts using a token, Sanctum is trying to look in the Users personal_access_tokens table, not the Contacts personal_access_tokens table. So essentially it's just looking at the wrong database for the personal_access_tokens table and I don't know how to change that.
My setup is as follows:
Guards:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
/*'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],*/
'users' => [
'driver' => 'sanctum',
'provider' => 'users',
'hash' => false,
],
'contacts' => [
'driver' => 'sanctum',
'provider' => 'contacts',
'hash' => false,
],
],
Providers
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'contacts' => [
'driver' => 'eloquent',
'model' => App\Models\Contact::class,
],
],
User Model
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
Contact Model
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class Contact extends Authenticatable
{
use HasApiTokens, HasFactory, Notifiable;
/**
* The connection name for the model.
*
* #var string
*/
protected $connection = 'puranet_crm';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'first_name',
'last_name',
'email',
'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
}
My two api routes for autorisation are:
Route::group(['middleware' => 'auth:sanctum'], function() {
//All secure URL's
Route::get('test',[UserController::class, 'test']);
});
Route::group(['middleware' => 'auth:contacts'], function() {
Route::get('test-contacts',[ContactController::class, 'test']);
});
Contact Controller (this is identical to the UserController with exception to the Model it is referencing)
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Contact;
use Illuminate\Support\Facades\Hash;
class ContactController extends Controller
{
/**
* #param Request $request
* #return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function login(Request $request)
{
$user = Contact::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response([
'message' => ['These credentials do not match our records.']
], 404);
}
$token = $user->createToken('contacts-app-token')->plainTextToken;
$response = [
'user' => $user,
'token' => $token
];
return response($response, 201);
}
/**
* #return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\Response
*/
public function test()
{
return response(["response" => "Test Contacts"], 201);
}
}
You need to overwrite the sanctum model on your project and overwrite the $connection variable inside of it, so you will be able to connect to the database that you would like to, same when you do with normal Models.You can find how to overwrite sanctum model on the Laravel documentation for version 8.
Create a this model in one of your projects to overwrite where sanctum will look for the token.
class PersonalAccessToken extends SanctumPersonalAccessToken{
use HasFactory;
protected $connection = 'name of your connection in database.php';
}
So both sanctum will use the same DB to auth the User.
I hope I helped you :)
I'm new to Laravel and i created two guards "user guard"(default one) and "admin guard". And i'm saving auth sessions in database instead of file.
Now the problem is user id is causing conflict in sessions table.
For example, if i create a new user in users table and new admin account in admin table both would have same id in sessions table and since the id is not unique it's automatically logging me in to admin account even though if i just login as normal user.
I've already searched on Google but couldn't find anything useful. Only this guy has asked same question but not working answer:
Multi session tables in Laravel
Here's my code:
config/auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
]
],
AdminLoginController.php
if (auth()->guard('admin')->attempt(['email' => $request->email, 'password' => $request->password])) {
$user = auth()->guard('admin')->user();
return redirect()->route('admin');
}
There probably shouldn't be a separate table and separate eloquent model for an admin, as an admin is a User. They are just a User with elevated permissions.
Instead of creating a separate model, guard, and provider for admins, consider attaching roles and/or permissions to your User model to track if a User is an administrator.
Here's a good package to help with that: https://github.com/spatie/laravel-permission
I'm not sure it's conflicting. I believe you just don't use the guards correctly.
You need to make sure, you never call Auth::user() if you're logging in as Admin. You should always need to use Auth::guard('admin')->user().
extend the laravel default DatabaseSessionHandler and override the addUserInformation function
<?php
namespace App\Extensions;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Session\DatabaseSessionHandler;
class CustomDatabaseSessionHandler extends DatabaseSessionHandler
{
/**
* Add the user information to the session payload.
*
* #param array $payload
* #return $this
* #throws BindingResolutionException
*/
protected function addUserInformation(&$payload): static
{
if ($this->container->bound(Guard::class)) {
info(($this->user() ? get_class($this->user()) : null));
$payload['userable_type'] = $this->user() ? get_class($this->user()) : null;
$payload['userable_id'] = $this->userId();
}
return $this;
}
/**
* Get the currently authenticated user's ID.
*
* #return mixed
* #throws BindingResolutionException
*/
protected function user(): mixed
{
return $this->container->make(Guard::class)->user();
}
}
then register the driver in AppServiceProvider
public function boot(): void
{
Session::extend('custom-database', function ($app) {
$table = $app['config']['session.table'];
$lifetime = $app['config']['session.lifetime'];
$connection = $app['db']->connection($app['config']['session.connection']);
return new CustomDatabaseSessionHandler($connection, $table, $lifetime, $app);
});
}
change your sessions table migration
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('userable_type')->nullable();
$table->foreignId('userable_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
then change session driver in your .env file
SESSION_DRIVER=custom-database
now the sessions stored in the database are polymorphic
Suppose I have two different models and tables named user and company.
As you know laravel uses User model to manage Authentication. but beacause I have two different model I want can manage them separately.
I'm using laravel 5.4 and I do not know how can do that.
If you are talking about multiple authentication system, then you have to create multiple guards to achieve that.
There is nice answer to the same question.
Can anyone explain Laravel 5.2 Multi Auth with example
It's on Laravel 5.2, but it can be easily implemented on Laravel 5.4.
Create a model App\Company which extends Authenticatable Class. This model will work as user model which will company guard (in the next step)
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Company extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
Create an guard and a provider for model App\Company.
// Authenticating guards and providers
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
'company' => [
'driver' => 'session',
'provider' => 'company',
],
],
// Providers
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'company' => [
'driver' => 'eloquent',
'model' => App\Company::class,
]
],
Now you can find user according to the different guards.
$user = Auth::guard('company')->user();
// Or...
$user = auth()->guard('company')->user();
dd($user);
Now create Auth controller for Company App\Http\Controllers\Auth\CompanyLoginController same as Auth\LoginController.
Specify $redirectTo and guard
//Auth\ComapnyLoginController.php
protected $redirectTo = '/comapany';
protected $guard = 'comapany';
public function showLoginForm()
{
if (view()->exists('auth.authenticate')) {
return view('auth.authenticate');
}
return view('comapany.auth.login');
}
now create login form for user - company.auth.login view same as user's login form.
Now create routes
//Login Routes...
Route::group(['prefix'=>'company', 'middleware'=>'company'], function(){
Route::get('/login','Auth\CompanyLoginController#showLoginForm');
Route::post('/login','Auth\CompanyLoginController#login');
// ...
// rest of the company dashboard and other links
// ...
Route::get('/logout','Auth\CompanyLoginController#logout');
});
Create a middleware for company
class RedirectIfNotCompany
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = 'company')
{
if (!Auth::guard($guard)->check()) {
return redirect('/');
}
return $next($request);
}
}
and register it to kernal.php
protected $routeMiddleware = [
'company' => \App\Http\Middleware\RedirectIfNotCompany::class,
];
And thats all you need.
Access user by the name of guard
Auth::guard('company')->user()
I am creating a project in laravel. My problem is, Since this is a shopping cart I am using different tables for customer and admins. So if request is admin then i want to authenticate from admin table and if it is from store i want to use customer table for authentication. Is is it possible to set auth table for controllers or is it possible to use create multiple authenticator other than the default?
Multi Auth is a common problem that one can face in Laravel so yes it possible to create it.
You can write your own code for this or use some package for this specific functionality. They are available on github easily. Example link.
There is a very good tutorial for this here which I will use for explanation.
You will need to create two tables, customers and admin. The default user table can be used for customers (or other way too). The make:auth command will create all the routes, controllers and views for the users table auth.
For admin auth, first create an admin table. Next controllers
app/Http/Controllers/AdminAuth/AuthController
app/Http/Controllers/AdminAuth/PasswordController
Edit config/auth.php file and do same for admin as given for user, using admin model when required instead of user.
//Authenticating guards
'guards' => [
'user' =>[
'driver' => 'session',
'provider' => 'user',
],
'admin' => [
'driver' => 'session',
'provider' => 'admin',
],
],
//User Providers
'providers' => [
'user' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admin' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
]
],
//Resetting Password
'passwords' => [
'clients' => [
'provider' => 'client',
'email' => 'auth.emails.password',
'table' => 'password_resets',
'expire' => 60,
],
'admins' => [
'provider' => 'admin',
'email' => 'admin.auth.emails.password',
'table' => 'password_resets',
'expire' => 60,
],
],
Edit route file
Route::group(['middleware' => ['web']], function () {
//Login Routes...
Route::get('/admin/login','AdminAuth\AuthController#showLoginForm');
Route::post('/admin/login','AdminAuth\AuthController#login');
Route::get('/admin/logout','AdminAuth\AuthController#logout');
// Registration Routes...
Route::get('admin/register', 'AdminAuth\AuthController#showRegistrationForm');
Route::post('admin/register', 'AdminAuth\AuthController#register');
Route::get('/admin', 'AdminController#index');
});
Edit AdminAuth\AuthController.php file and add functions
protected $redirectTo = '/admin';
protected $guard = 'admin';
public function showLoginForm()
{
if (view()->exists('auth.authenticate')) {
return view('auth.authenticate');
}
return view('admin.auth.login');
}
public function showRegistrationForm()
{
return view('admin.auth.register');
}
Create middleware for admin
class RedirectIfNotAdmin
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #param string|null $guard
* #return mixed
*/
public function handle($request, Closure $next, $guard = 'admin')
{
if (!Auth::guard($guard)->check()) {
return redirect('/');
}
return $next($request);
}
}
Register middleware in kernel
protected $routeMiddleware = [
'admin' => \App\Http\Middleware\RedirectIfNotAdmin::class,
];
Use this middleware in admin controller
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
class AdminController extends Controller
{
public function __construct(){
$this->middleware('admin');
}
public function index(){
return view('admin.dashboard');
}
}
Now you can use it like
Auth::guard('admin')->user()
but not directly like
Auth::user()
because we have two auths
You Should use ENTRUST (Laravel 5 Package)
But before you need to organize your database structure.
For all type of user use your users table, have a separate customer table with foreign key user_id. Assign roles to users , When a user logged in check its role and redirect to their dashboard as per assigned role.
I am working on an API for 2 frontend apps (vue2/mobile). One uses User model and the other uses the Admin model (Laravel is just an API)
I am using Laravel Passport for authenticating users and admins, i successfully provided access token for users but i'm facing some problem with admins
So for i did
1-> created Admin model
<?php
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
use HasApiTokens, Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password','role',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
2-> created a admins guard which uses passport
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
'admin' => [
'driver' => 'passport',
'provider' => 'admins',
],
],
3-> created Route and Controller for granting access token for admins
Route::post('/oauth/token/admin', [
'uses' => 'Auth\CustomAccessTokenController#issueUserToken'
]);
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use Psr\Http\Message\ServerRequestInterface;
use Laravel\Passport\Http\Controllers\AccessTokenController;
class CustomAccessTokenController extends AccessTokenController
{
/**
* Hooks in before the AccessTokenController issues a token
*
*
* #param ServerRequestInterface $request
* #return mixed
*/
public function issueUserToken(ServerRequestInterface $request)
{
$httpRequest = request();
if ($httpRequest->grant_type == 'password') {
$admin = \App\Admin::where('email', $httpRequest->username)
->where('password', $httpRequest->password)
->first();
//dd($admin);
return $this->issueToken($request);
}
}
}
4-> i tested with Postman
http://localhost:8000/api/oauth/token/admin
client_id:4
client_secret:M4QkLqhPkJ4pGL52429RipassQ3BOjKTJZe3uoWK
grant_type:password
username:admin#gmail.com
password:secret
//i'm getting
{
"error": "invalid_credentials",
"message": "The user credentials were incorrect."
}
//if i use the User model credentials
username:user#gmail.com
password:secret
//i'm getting the access token
{
"token_type": "Bearer",
"expires_in": 31536000,
"access_token": "eyJ0eXAiOiJKV1Qi....",
"refresh_token": "UI354EfJlVdmOhO...."
}
i'm really tired figuring out what went wrong
looking forward for much needed help
thank you