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
Related
I am working on a big project that has a laravel backend for API and a separate SPA (vue-cli scaffolded).
they are found on the same top level domain the laravel project runs at the domain(m.m) and the Vue Spa runs at(vue.m.m:8080)
what my problem is:
auth:sanctum protected routes does not work they return ({ "message" : "Unauthenticated" })
what works:
route /api/login works perfectly when given the correct password and email (it sends a response ({"message" : "Login successful"}))
my code snippets:
.env (it is only the part that is needed based on the docs I cant show the other because of security issues)
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=cookie
SESSION_LIFETIME=120
SESSION_DOMAIN=.m.m
SANCTUM_STATEFUL_DOMAINS=vue.m.m
backend
UserController => for Authenticating
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class UserController extends Controller
{
public function register(Request $request)
{
$this->validator($request->all())->validate();
$user = $this->create($request->all());
$this->guard()->login($user);
return response()->json(['user'=> $user,
'message'=> 'registration successful'
], 200);
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:4', 'confirmed'],
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
protected function guard()
{
return Auth::guard();
}
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (Auth::attempt($credentials)) {
// Authentication passed...
return response()->json(['message' => 'Login successful'], 200);
}
}
public function logout()
{
Auth::logout();
return response()->json(['message' => 'Logged Out'], 200);
}
}
sactum.php
<?php
return [
'stateful' => explode(',', env(
'SANCTUM_STATEFUL_DOMAINS',
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1'
)),
'expiration' => null,
'middleware' => [
'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class,
'encrypt_cookies' => \Illuminate\Cookie\Middleware\EncryptCookies::class,
],
];
auth.php
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
User Model
<?php
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 HasFactory, Notifiable,HasApiTokens;
/**
* 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',
];
}
api.php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
return "protected route";
});
Route::post("/login","App\Http\Controllers\UserController#login");
cors.php (for development purposes I allowed all incoming requests to be processed) and (credentials are set to true)
'paths' => ['*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
kernel.php
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
Frontend
axios
axios.defaults.withCredentials = true;
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
axios.defaults.baseURL = 'http://m.m/api';
Vue (this is the method that handles sending login requests)
login(evt) {
evt.preventDefault();
axios.get("/sanctum/csrf-cookie", { baseURL: "http://m.m" }).then(() => {
axios
.post(
"/login",
{ email: "john#doe.com", password: "password" },
)
.then(() => {
const res = axios.get("/user");
console.log(res.data);
});
});
};
}
what I get from the browser
network tab from chrome
this is what is stored in the browser as cookies
(image)
I had the same problem. I solved as the following:
In the api routes define
Route::get('/unauthenticated', function () {
return response()->json(["message" : "unauthenticated"]);
})->name('api.unauthenticated');
Now in the method redirectTo in the Authenticate.php middleware add this line
if ($request->is('api/*') || $request->is('api'))
return route('api.unauthenticated');
And that's it.
It works for me
i am trying to create a multiple authentication in Laravel 7 with custom guards and when i try to login am getting this error "SQLSTATE[42S22]: Column not found: 1054 Unknown column '0' in 'where clause' (SQL: select * from admins where email = mukamba#gmail.com and 0 is null limit 1)"
My auth.php in config
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'admin' => [
'driver' => 'session',
'provider' => 'admins',
],
'admin-api' => [
'driver' => 'token',
'provider' => 'admins',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class,
],
],
My adminLoginController
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use auth;
class AdminLoginController extends
Controlle
r
{
public function __construct()
{
$this->middleware('guest:admin');
}
public function showLoginForm()
{
return view('auth.admin-login');
}
public function login(Request $request)
{
// validate the data
$this->validate($request, [
'email' => 'required|email',
'password' => 'required|min:6'
]);
// attempt user to login
if(auth::guard('admin')->attempt(['email' => $request->email, 'password'=> $request->password, $request->remember])){
//if susscefull redirect to the intended location
return redirect()->intended(route('admin.dashboard'));
}
// if unsuccesfull return to the page they were
}
}
My 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 $guard = 'admin';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'job_title',
];
/**
* 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',
];
}
LoginController
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* #var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
The problem is somewhere in your password when you are passing it.
select * from admins where email = mukamba#gmail.com and 0 is null limit 1) I gues that you are getting this error when trying to log in. Check out Login controller and see the parameters that you are passing. Or drop the controller here so we can provide more info.
The attempt function was supposed to be like this
if (Auth::guard('admin')->attempt(['email' => $request->email, 'password' => $request->password], $request->get('remember'))) { return redirect()->intended(route('admin.dashboard'));
}
I have an api in laravel and im using passport to authenticate.
On my config/auth.php file i have changed this so the api guard using passport and refers to my user provider
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'passport',
'model' => App\Users::class,
],
Here is my App/user.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Passport\HasApiTokens;
class User extends Authenticatable
{
use HasApiTokens;
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',
];
public static function lookup($login)
{
return Self::where('email', $login)->first();
}
}
Now when I want to log someone in, I run this code:
$newUser = new User([
'name' => $login,
'email' => $login,
'password' => $password
]);
$authUser = User::lookup($login);
$tokenResult = $authUser->createToken('Personal Access Token');
$token = $tokenResult->token;
if ($remember)
{
$token->expires_at = Carbon::now()->addWeeks(1);
$token->save();
}
return response()->json([
'access_token' => $tokenResult->accessToken,
'token_type' => 'Bearer',
'expires_at' => Carbon::parse($tokenResult->token->expires_at)->toDateTimeString()
]);
This sends me back an access token just fine. What im trying to do is check if the user is logged in when doing other functions on the website. I am doing this like below in the routes file:
Route::get('me', 'Account#me')->middleware('auth:api');
So i want to get the infomation of whoever is logged in but it crashes at the middleware by giving me an error of 'Authentication user provider [passport] is not defined'
Any help is appriciated
You have used a wrong user provider driver of passport, Instead use eloquent as this:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
],
Also you have an extra s in the model name.
I'm trying to create multi login in laravel 5.6 and this error appeared, can anyone help me?
Symfony \ Component \ Debug \ Exception \ FatalThrowableError (E_RECOVERABLE_ERROR)
Type error: Argument 2 passed to Illuminate\Auth\SessionGuard::__construct() must be an instance of Illuminate\Contracts\Auth\UserProvider, null given, called in C:\wamp64\www\Laravel\Sistema\oficial\vendor\laravel\framework\src\Illuminate\Auth\AuthManager.php on line 123
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Route;
use Illuminate\Support\Facades\Auth;
class EmployeeLoginController extends Controller
{
public function __construct()
{
$this->middleware('auth:employee');
}
public function login(Request $request)
{
$credentials = $request->only('email', 'password');
if (Auth::guard('employee')->attempt($credentials)) {
return redirect()->intended(route('admin.dashboard'));
}
return redirect()->back()->withInput($request->only('email', 'remember'));
}
}
guard:
<?php
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'employee' => [
'driver' => 'session',
'provider' => 'employees',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'employee' => [
'driver' => 'eloquent',
'model' => App\Employee::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
],
],
];
ReditectIfAuthenticated
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class RedirectIfAuthenticated
{
/**
* 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 = null)
{
switch ($guard) {
case 'employee':
if (Auth::guard($guard)->check()) {
return redirect('/dashboardemployee');
}
break;
default:
if (Auth::guard($guard)->check()) {
return redirect('/home');
}
break;
}
return $next($request);
}
}
model:
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Employee extends Authenticatable
{
protected $guard = 'employee';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name',
'email',
'password',
'photo',
'status',
'connect_email',
'connect_senha',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
}
The provider is named wrong:
'employee' => [
'driver' => 'eloquent',
'model' => App\Employee::class,
],
should be
'employees' => [
'driver' => 'eloquent',
'model' => App\Employee::class,
],
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)).