I use Lavarel 5.2 framework with jwt for authorization
jwt takes user info form token just with one model,
now how can i parse user token with jwt on multiple model?
For sample when i use customer token in a api jwt parse that token from customer model , default guard should be customer
auth.php :
'defaults' => [
'guard' => 'operator',
'passwords' => 'operators',
],
'guards' => [
'operator' => [
'driver' => 'session',
'provider' => 'operators',
],
'customer' => [
'driver' => 'session',
'provider' => 'customers',
],
'biker' => [
'driver' => 'session',
'provider' => 'bikers',
]
],
'providers' => [
'operators' => [
'driver' => 'eloquent',
'model' => App\Http\Services\Auth\Model\User::class,
],
'customers' => [
'driver' => 'eloquent',
'model' => App\Http\Aggregate\Customer\Model\Customer::class,
],
'bikers' => [
'driver' => 'eloquent',
'model' => App\Http\Aggregate\Biker\Model\Biker::class,
]
],
You can create a separate middleware like AuthModel. In that you can set the config to take which providers like the below,
Config::set('auth.providers.users.model',\App\Models\Customer::class);
If you want to use multiple models, then need to use if conditions to check which url can access which models. It can be like,
if(url == '/customer/api/') {
Config::set('auth.providers.users.model',\App\Models\Customer::class);
} else if(url == '/biker/api/') {
Config::set('auth.providers.users.model',\App\Models\Biker::class);
}
In the above example, I have used url just for example, so get it from the request.
You can change the __construct function in each of your controllers as follows. So that jwt know which model to authenticate.
BikerController
function __construct()
{
Config::set('jwt.user', Biker::class);
Config::set('auth.providers', ['users' => [
'driver' => 'eloquent',
'model' => Biker::class,
]]);
}
CustomerController
function __construct()
{
Config::set('jwt.user', Customer::class);
Config::set('auth.providers', ['users' => [
'driver' => 'eloquent',
'model' => Customer::class,
]]);
}
This is my solution. Tested on Laravel 6
User Model
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Tymon\JWTAuth\Contracts\JWTSubject;
class User extends Authenticatable implements JWTSubject
{
use SoftDeletes;
use Notifiable;
public $incrementing = false;
protected $keyType = 'string';
protected $fillable =
[
];
protected $hidden =
[
'password',
'created_at',
'updated_at',
'deleted_at'
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
}
}
Teacher Model
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Tymon\JWTAuth\Contracts\JWTSubject;
class Teacher extends Authenticatable implements JWTSubject
{
use SoftDeletes;
use Notifiable;
public $incrementing = false;
protected $keyType = 'string';
protected $fillable =
[
];
protected $hidden =
[
'password',
'oldpassword',
'created_at',
'updated_at',
'deleted_at'
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
}
}
config/auth.php
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users'
],
'teacher-api' => [
'driver' => 'jwt',
'provider' => 'teachers'
],
],
AuthController function :
if (
$request->getRequestUri() ===
'OTHER_AUTH_ROUTE'
) {
$credentials = $request->only('username', 'password']);
$token = Auth::shouldUse('teacher-api');
$token = Auth::attempt($credentials);
if (!$token) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
}
$credentials = $request->only([USERNAME, 'password']);
$token = Auth::attempt($credentials);
if (!$token) {
return response()->json(['error' => 'Unauthorized'], 401);
}
return $this->respondWithToken($token);
Hope this can help you all at future
Laravel 8 compatible
For others still looking for a clean solution:
I would suggest manually configure the providers and guards in config/auth.php and not programmatically change any providers.
The next thing to make sure the right JWTSubject auth model is used, is to create different Middleware (don't forget to specify it in Kernel.php under $routeMiddleware) for a group of routes that has to be only accessible by a specific guard/auth model. Then a middleware handle function could look like this for a Manager model:
public function handle(Request $request, Closure $next) {
if (!($request->user('managers'))) abort(401);
Auth::shouldUse('managers');
return $next($request);
}
Then create another middleware for, let's say an Employee model and change the 'managers' guard value to 'employees' which you configured in config/auth.php.
In your routes/api.php you can specify a route group using (e.g.):
Route::group(['middleware' => 'management'], function() { });
In order to make this all work correctly, specify the guard when the auth()->attempt() function is called, e.g. auth('managers')->attempt($credentials)).
Related
I hope my english is good enough to explain my problem. I'm working with laravel 7, and I'm trying to implement my own AuthController, because I can't use migrations and I can't use a table 'users' because I have a db implemented. I've read all the documentation of laravel about authentication and spent days reading a lot of posts with this problem, and I tried everything but still does not work. The problem is with the attempt method. My register method is working fine, but I can't login, actually I tried to put manually the data(that's why credentials are commented) to find the problem but I don't know why is not working
My Regsiter method.
public function storeUser(Request $request)
{
//dd($request);
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:administradores,ADMIN_Correo',
'password' => 'required|string|min:8|confirmed',
'password_confirmation' => 'required',
]);
Administrador::create([
'ADMIN_Nombre' => $request->name,
'ADMIN_Correo' => $request->email,
'ADMIN_Contrasena' => Hash::make($request->password),
]);
//return redirect('home');
}
My Login method.
public function authenticate(Request $request)
{
/*$request->validate([
'ADMIN_Correo' => 'required|string|email',
'ADMIN_Contrasena' => 'required|string',
]);*/
//$credentials = $request->only('ADMIN_Correo', 'ADMIN_Contrasena');
if (Auth::guard('admin')->attempt(['ADMIN_Correo' => 'edwin2#gmail.com
','ADMIN_Contrasena' => '12345678'])) {
return redirect()->intended('home');
}else{
echo 'error';
}
//return redirect('login')->with('error', 'Oppes! You have entered invalid credentials');
}
My config/auth.php file
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
]
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Administrador::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Administrador::class,
],
],
My Model
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Administrador extends Authenticatable
{
use Notifiable;
protected $table = 'administradores';
protected $primaryKey = 'ADMIN_Id';
public $timestamps = false;
public $incrementing = true;
protected $fillable = ['ADMIN_Nombre','ADMIN_Correo','ADMIN_Contrasena'];
protected $guard = 'admin';
public function getAuthPassword()
{
return $this->ADMIN_Contrasena;
}
}
NOTES The field for password:(ADMIN_Contrasena) is varchar 255
I'm using Laravel passport for API authentication, with api provider 'user' and web provider 'admin'. However, my API login URL keeps using the default provider for web instead of its own provider.
config/auth.php:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'admin',
],
'api' => [
'driver' => 'passport',
'provider' => 'user',
'hash' => 'false',
],
],
'providers' => [
'admin' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
'user' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
],
User Model:
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use \App\Http\Traits\UsesUuid, HasApiTokens,Notifiable;
protected $table = 'user';
protected $primaryKey = 'user_id';
protected $fillable =
['user_id',
'user_fname',
'user_lname',
'user_email',
'password',
'user_contact',
'user_token' ];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function setPasswordAttribute($password)
{
$this->attributes['password'] = bcrypt($password);
}
}
Admin Model:
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class Admin extends Authenticatable
{
use Notifiable;
protected $table = 'admin';
/**
* 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',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime'
];
}
API Auth Controller:
public function login(Request $request){
$credentials = request(['user_email', 'password']);
if(!auth()->attempt($credentials)){
return response()->json([
"message"=>"Invalid credentials"
], 201);
}
$accessToken = auth()->user()->createToken('authToken')->accessToken;
return response()->json([
"message"=>"Login successful",
"user"=>auth()->user(),
"access_token"=>$accessToken
], 201);
}
Login route:
Route::post('/login', 'AuthController#login');
My error is the login route uses the default Admin model instead of the User model specified for the API provider giving me this error:
Illuminate\Database\QueryException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'user_email' in 'where clause' (SQL: select * from `admin` where `user_email` = user#gmail.com limit 1)
I have seen a similar error here https://laracasts.com/discuss/channels/laravel/changing-the-model-provider-laravel-passport-authenticates-against and tried the fixes suggested here https://github.com/laravel/passport/issues/161#issuecomment-299690583 but they do not work
you need to create a user guard as you need users table session driver to call attempt() function
config/auth.php:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'admin',
],
'user' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'user',
'hash' => 'false',
],
],
then your code should be like this
public function login(Request $request)
{
$credentials = request(['user_email', 'password']);
if (!auth()->guard('user')->attempt($credentials)) {
return response()->json([
"message" => "Invalid credentials"
], 201);
}
$accessToken = auth()->guard('user')->user()->createToken('authToken')->accessToken;
return response()->json([
"message" => "Login successful",
"user" => auth()->user(),
"access_token" => $accessToken
], 201);
}
NOTE:- we create new guard because passport driver not allow to use attempt() and web guard you have set admin table so
In my project, I just need one authentication for admins and not for users and also have users table in my database but I don't want to use it in the authentication and in Admins table I want to use 'AD_AdminEmail' field as email and 'AD_AdminPassword' as authentication password and admins routes separated to management.php file.
These codes below not working and no errors but they redirect me to the login page again and Passwords in database hashed with laravel Hash::make() but Auth::attempt do not recognize it as hashed one and send a select query with plain password not hashed yet.
Here are My web route Codes:
Route::get('/login', [ 'as' => 'login', 'uses' => function() {
return view('LoginAndRegister');
}])->name('login');
Route::post('/ManagerLogin', 'Auth\LoginController#Authenticate');
Here are My LoginController Codes:
<?php
namespace App\Http\Controllers\Auth;
use App\Admin;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Session;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
use AuthenticatesUsers;
protected $redirectTo = RouteServiceProvider::HOME;
public function username()
{
return 'AD_AdminEmail';
}
public function __construct()
{
$this->middleware('guest')->except('logout');
}
protected function credentials(Request $request)
{
return $request->only($this->username(), 'AD_AdminPassword');
}
protected function validateLogin(Request $Request){
$this->validate($Request, [
$this->username() => 'required|email,',
'AD_AdminPassword' => 'required|min:8',
]);
}
public function Show()
{
return view('LoginAndRegister');
}
public function Authenticate(\Illuminate\Http\Request $Request)
{
$AdminEmail = $Request->input('AD_AdminEmail');
$AdminPassword = $Request->input('AD_AdminPassword');
if (Auth::attempt([
'AD_AdminEmail' => $AdminEmail,
'AD_AdminPassword' => $AdminPassword
], false))
echo 200 . "OK";
else
echo 400 . "Problem Found";
}
public function Logout()
{
Session::flush();
Auth::logout();
return back();
}
}
Here are My Admin model Codes:
<?php
namespace App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Admin extends Authenticatable
{
protected $fillable = ['AD_AdminEmail', 'AD_AdminPassword'];
protected $hidden = ['AD_AdminId', 'remember_token',];
protected $primaryKey = 'AD_AdminId';
protected $guarded = ['AD_AdminId'];
protected $table = 'Admins';
use Notifiable;
public function getEmailAttribute() {
return $this->AD_AdminEmail;
}
public function setEmailAttribute($Value)
{
$this->attributes['AD_AdminEmail'] = strtolower($Value);
}
public function getAuthPassword()
{
return $this->AD_AdminPassword;
}
public function setPasswordAttribute($Value)
{
$this->attributes['password'] = bcrypt($Value);
}
}
Here are My config>auth.php Codes:
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'admins',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'admins',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
'users' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
],
'passwords' => [
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 60,
],
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
I am trying to create a new type of login that works alongside users table called business_users.
I have added the associated, tables, models and config into my auth.php files.
Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\User as Authenticatable;
class BusinessUser extends Authenticatable
{
protected $fillable = ['first_name', 'last_name', 'email', 'password', 'username'];
protected $hidden = [
'password', 'remember_token',
];
protected $guard = 'business_user';
public function business()
{
return $this->belongsTo('App\Business');
}
public function username()
{
return 'username';
}
public function getAuthPassword()
{
return $this->password;
}
}
auth.php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'business_user' => [
'driver' => 'session',
'provider' => 'business_users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'business_users' => [
'driver' => 'eloquent',
'model' => App\BusinessUser::class,
],
],
Route (which fakes a login for testing)
Route::get('/business/fake-login', function () {
$user = \App\BusinessUser::first();
if (Auth::guard('business_user')->attempt(['username' => $user->username, 'password' => $user->password])) {
return redirect()->intended('/business/dashboard');
}
});
I am trying to use the business.username and business.password to login but the Auth:guard condition above returns false.
Can anyone explain what I'm doing wrong?
(fyi I am using Laravel 7.x)
You are retriving $user from the database, the password is encrypted.
Auth::attempt() will encrypt the password for you, so in the check password part, your password is actually being encrypted twice.
Instead, you may use Auth:attempt() like this:
$res = Auth::guard('business_guard')->attempt([
'username' => "test",
'password' => "test",
]);
dd( $res );
To understand further, you can go to EloquentUserProvider.php
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
return $this->hasher->check($plain, $user->getAuthPassword());
}
Use you original code, and dd() the $plain to see what's going on.
I've got an issue with the following scenario an I hope to find advice here:
I've got Laravel 5.7 installed and ran make:auth for the authentication scaffolding. The native User model has been removed and instead I'm using two custom models "Customer" and "Admin" (migrations have been changed accordingly).
Admin model:
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Traits\FormatUserdata;
class Admin extends Authenticatable
{
use Notifiable, FormatUserdata;
protected $guard = 'admin';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'firstname', 'lastname', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
Customer model:
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use App\Traits\FormatUserdata;
class Customer extends Authenticatable
{
use HasApiTokens, Notifiable, FormatUserdata;
protected $guard = 'customer';
protected $fillable = [
'firstname', 'lastname', 'email', 'company', 'address', 'zip', 'city', 'country', 'vatid', 'customer_no', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
public function licenses()
{
return $this->hasMany('App\License');
}
}
So I have to authenticate users from two different models and tables. In order for this to work I changed my config/auth.php like so:
return [
'defaults' => [
'guard' => 'customer',
'passwords' => 'customers',
],
'guards' => [
'customer' => [
'driver' => 'session',
'provider' => 'customers',
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'api' => [
'driver' => 'passport',
'provider' => 'admins',
],
],
'providers' => [
'customers' => [
'driver' => 'eloquent',
'model' => App\Customer::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
],
'passwords' => [
'customers' => [
'provider' => 'customers',
'table' => 'password_resets',
'expire' => 60,
],
'admins' => [
'provider' => 'admins',
'table' => 'password_resets',
'expire' => 60,
],
],
];
For my Customer model I set up a resource controller which is protected by a resource policy as well as the auth middleware for the index method:
namespace App\Http\Controllers;
use App\Customer;
use Illuminate\Http\Request;
class CustomerController extends Controller
{
public function __construct()
{
$this->middleware('auth:admin')->only('index');
$this->authorizeResource(Customer::class);
}
public function show(Customer $customer)
{}
...shortened
}
Here is the policy that I'm referencing in this controller:
namespace App\Policies;
use App\Customer;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Foundation\Auth\User;
class CustomerPolicy
{
use HandlesAuthorization;
public function view(User $user, Customer $customer)
{
return true;
}
}
My routes do not have any additional middleware applied.
Here is the issue:
When I'm accessing my route /customers/1 with a user of the default guard "customer" everything works as expected (no error). However when I'm trying to access that route with a admin user I'll get a 403 unauthorized response. The strange thing is that when I'm trying to check the permissions in the view with
#can('view', App\Customer::findOrFail(3))
the policy works correctly. I've found that the view method of the policy isn't being called at all when I'm accessing the route as an admin. I digged a little bit through the laravel code and I'm pretty sure that the issue is related to the fact that the customer guard is default and the user resolver only the default.
Does anyone have any idea how to solve that problem?
Thank you!
Andreas
edit:
Here is my AuthServiceProvicer.php:
namespace App\Providers;
use App\Policies\CustomerPolicy;
use App\Customer;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
Customer::class => CustomerPolicy::class
];
public function boot()
{
$this->registerPolicies();
}
}
And here is my routes/web.php
Route::get('/', function () {
return view('welcome');
});
Auth::routes(['register' => false]);
Route::prefix('admin')->name('admin.')->group(function () {
Route::get('/', function () {
return redirect( route('admin.dashboard') );
});
Route::get('dashboard', 'AdminController#dashboard')->name('dashboard');
Route::get('login', 'Auth\AdminLoginController#showLoginForm')->name('login');
Route::post('login', 'Auth\AdminLoginController#login')->name('login.post');
Route::post('logout', 'Auth\AdminLoginController#logout')->name('logout');
Route::post('password/email', 'Auth\AdminForgotPasswordController#sendResetLinkEmail')->name('password.email');
Route::get('password/reset', 'Auth\AdminForgotPasswordController#showLinkRequestForm')->name('password.request');
Route::post('password/reset', 'Auth\AdminResetPasswordController#reset')->name('password.update');
Route::post('password/reset/{token}', 'Auth\AdminResetPasswordController#showResetForm')->name('password.reset');
Route::resource('customers', 'CustomerController');
});
Route::prefix('backend')->name('backend.')->group(function () {
Route::get('/', function () {
return redirect( route('backend.dashboard') );
});
Route::get('dashboard', 'BackendController#dashboard')->name('dashboard');
Route::resource('customers', 'CustomerController')->except([
'index'
]);
});
So for customers I'm using the route /backend/customer/{customer} and for admins I'm using the route /admin/customer/{customer}
After a bit of further research I came across this awesome blog post
https://medium.com/#JinoAntony/multi-user-authentication-using-guards-in-laravel-5-6-f18b4e61bdde
The important step here is to create an additional middleware "AssignGuard" that sets the guard depending on the used route.