Laravel Common Api Routes for Different Authentication Guards - laravel

I have several authentication guards on my app like below:
'api' => [
'driver' => 'passport',
'provider' => 'users',
'hash' => false,
],
'api-customer' => [
'driver' => 'passport',
'provider' => 'customers',
'hash' => false,
],
'api-manufacturer' => [
'driver' => 'passport',
'provider' => 'manufacturers',
'hash' => false,
],
I have some common routes for api and api-manufacturer and I created my routes like below
Route::middleware('auth:api', 'verified')->group( function () {
Route::post('bids/job/update-quote', 'BidController#updateOrCreateQuote');
}
Route::middleware('auth:api-manufacturer')->group(function () {
Route::post('bids/job/update-quote', 'BidController#updateOrCreateQuote');
}
in this case only second route definition is working, I mean for api-manufacturer guard. I tried to add both middleware like middleware(['auth:api, auth:api-manufacturer']).... but it didn't work also. I think this checks for both auth guards..
How to do that in proper way.. I will need same approach while creating admin user..

The auth (Authenticate) middleware takes a list of possible guards to check. You can pass multiple guards to it and it will spin through them until one of them returns a user.
middleware('auth:api,api-manufacturer')
If a guard returns a user then it will also set that as the default guard.

Your solution is checking one of two guards not both, like if one of the guards passed then it pass to the route.
you have two options:
First: simplest, create a new route that redirects to the same controller method like:
Route::middleware('auth:api', 'verified')->group( function () {
Route::post('bids/job/update-quote', 'BidController#updateOrCreateQuote');
}
Route::middleware('auth:api-manufacturer')->group(function () {
Route::post('bids/job/manufacturer/update-quote', 'BidController#updateOrCreateQuote');
}
Second: add a new middle ware that check if one of the two guard passed, then it pass to route, and implemented to the route:
public function handle($request, Closure $next, $guard = null)
{
if (!Auth::guard('api')->check() && !Auth::guard('api-manufacturer')->check()) {
return redirect('/login'); // or any un auth response
}
return $next($request);
}

Related

Mapping different column and table names for Laravel Authentication without re-writing all of the auth classes

We store our authentication information in a different table and column names than Laravel uses by default. It's still stored in MySQL. When doing research, in the documentation it says that we have to write completely different authentication handlers.
Is there really not any way to just remap the table and column names in a central place?
If not is there a better way to handle this? Should we just create a new table using the authentication information?
You can change your table/model name for authentication purposes inside the config\auth.php file.
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
Now, when it comes to changing column name by default Laravel using email field which you can change by putting a function username() which will return the field to be used for authentication inside LoginController.php.
public function username()
{
return 'username';
}
Hopefully this helps.
The Model associated with the login process can be modified in:
config/auth.php
under the 'providers' section:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\Models\MyOwnUsersTable::class,
],
],
However, the login process is a bit more tricky. the LoginController uses the AuthenticatesUsers trait, where you may override the required methods. For example the login method
class LoginController extends Controller
{
use AuthenticatesUsers;
public function login(Request $request)
{
//Do whatever you have to do
return $this->sendLoginResponse($request);
}
}
So, basically, I encourage you to study the
Illuminate\Foundation\Auth\AuthenticatesUsers
and reuse as much as possible from that class, and only override the methods you need to.

Strange error on JWT Laravel when using Multi Tenant Application

I'm using this package for "jwt-auth" for my Laravel backend project, here is the link for the package:
https://jwt-auth.readthedocs.io/en/develop/
I have 2 middleware that I put the names Tenant and JWT, when my user tries to log in to the app he must send the company code, so my middleware Tenant picks the information from the specific connection database client and all is working fine.
But when I use 2 middlewares together, he gives me an error that I don't have a user table. He is right because when I made a searching from the problem I found that the package executes a function before all my 2 middlewares that is this:
/**
* Get the currently authenticated user.
*
* #return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
// MODIFICAÇÃO CUSTOMIZADA DA FUNÇÃO USER
if ($this->user !== null) {
return $this->user;
}
if ($this->jwt->setRequest($this->request)->getToken() &&
($payload = $this->jwt->check(true)) &&
$this->validateSubject()
) {
return $this->user = $this->provider->retrieveById($payload['sub']);
}
}
The problem was solved if I comment this line "return $this->user = $this->provider->retrieveById($payload['sub']);", but this is not a good practice. The main reason for this error is that this function is executed before my Tenant middleware that doesn't have a user table in the database that Tenant middleware tries to connect.
The file name is **JWT_GUARD.php in tymon\jwt-auth\src**, i'm think is something about my configuration that i must change in
config/auth.php from laravel
'defaults' => [
'guard' => 'api',
'passwords' => 'users',
],
And here:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
I comment on this part "return $this->user = $this->provider->retrieveById($payload['sub']);" and waiting for a better solution

Laravel: Set a cookie on successful login using a custom guard/attempt method

I am using a third party database for authentication. Everything is working great but now would like to set a cookie when a user has logged in.
As stated in the Laravel Docs:
The attempt method will return true if authentication was successful. Otherwise, false will be returned.
This is what I am doing in my controller:
MyLoginController.php
$user = Auth::guard('foo')->attempt(['userid' => $request->username, 'password' => $request->password], $request->remember);
dd($user);
...
return redirect()->intended(route('home'));
Everything here is great. I'm getting true or false back as expected.
What I am trying to do is if the login is successful, set a cookie on the response. I need the user object back to get a value from. Something like this:
MyLoginController.php
$user = Auth::guard('foo')->attempt(['userid' => $request->username, 'password' => $request->password], $request->remember);
if ($user) {
switch (App::environment()) {
case 'local':
$cookie = cookie('localCookieName', $user->token, 480);
break;
case 'development':
$cookie = cookie('devCookieName', $user->token, 480);
break;
case 'production':
$cookie = cookie('cookieName', $user->token, 480);
break;
default:
//
break;
}
return redirect()->intended(route('home'))->cookie($cookie);
}
return redirect()->intended(route('home'));
I am using a custom User Provider to authenticate my users - everything there is working great as well. I am getting the user, and saving any data to my local db if needed. I thought I might be able to just set the cookie in the UserProvider, but without doing ->cookie($cookie) nothing is getting set.
The value of $user->token is coming back from my 3rd party authentication. So that's why I need to be able to access that value.
Reading the docs, it looks like I need to be setting cookie(s) on the response ->cookie($cookie) or withCookies($cookies).
This leads me to believe I need to set the cookie on my controller, but I'm not sure how to get the user object back since the attempt method only returns true or false.
How can I get the user object from within the attempt method? Maybe I am making thins incredibly difficult for myself and there is an easier way to set the cookie?
Thank you for any suggestions!
EDIT
Here is my config/auth.php file:
...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'foo' => [
'driver' => 'session',
'provider' => 'foo',
],
],
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'foo' => [
'driver' => 'foo', // Using a 3rd party for auth.
'model' => App\MyUser::class, // User model for auth.
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
attempt does a login if the credentials are valid and correct for a User. So you can just get the user from the Request or the Auth guard, since they are logged in:
$user = $request->user();
$user = $request->auth('foo')->user();
$user = Auth::guard('foo')->user();
...
If you know that attempt passed, the User is also available via getLastAttempted on the session guard:
$user = Auth::guard('foo')->getLastAttempted();
Although you can use that I would not, as you have to check that attempt actually returned true before trusting this value. This holds the last user retrieved by credentials, which could not have been authenticated potentially, attempt returned false.
You do not have to directly be adding a cookie to the Response. In the Cookie section of the docs should be information about "queue"ing a cookie to automatically be attached to the outgoing Response:
Cookie::queue('name', 'value', $minutes);
Laravel 6.x Docs - Responses - Attaching Cookies to Responses

Auth::user(); doesn't returns users, using passport

I have situation about returning users from DB. In my controller I am trying it like below:
UPDATED:
NOTE: for clear misunderstanding. Actually I am logged in as a user. No problem with that part. But it looks like auth:: doesn't understand that and when I try to retrieve users. it's redirecting me to login's endpoint...
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Auth;
class UsersController extends Controller
{
public function getUser(){
$users = Auth::user();
dd($users);
}
}
And about the api route:
Route::group(['middleware' => 'auth:api'], function() {
Route::post("logout", "Api\AuthController#logout");
/* User */
Route::get('/user', 'Api\UsersController#getUser');
});
Route::group(["prefix" => "v1"], function(){
/* Auth */
Route::post("login", "Api\AuthController#login")->name("login");
Route::post("register", "Api\AuthController#register");
});
Here is the thing. If I use my UserController route outside the middleware:api then endpoint is returns null. And if use it inside the middleware it redirects me to my login's endpoint. Because of the "->name('login')"
In the end I can't return the users. Additionally this is what config/auth looks like.
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
By the way before asked. I tried to change guard's web to api but nothing is changed.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
Is there anyone have better understanding on this situation. How can I return users with using passport? Do I missing something here?
Apparently, the problem is with the request header. Only a logged in user can call /api/user endpoint with an access_token in the request header.
Request header will have this pair
Authorization: Bearer eyJ0eXAiOiJKV1..........
Nothing to do in laravel part, as it's working as expected.
If you are using Laravel Passport. Let's read and make your step same in document: https://laravel.com/docs/5.8/passport
From your API request, need to pass the access_token to backend.
Hoping you can resolve that issue!

Laravel: How to use Gates with multiple Guards

I have a traditional web application that has a number of different user types, and each user type has its own Authentication guard.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'admin' => [
'driver' => 'session',
'provider' => 'admin',
],
'timekeeper' => [
'driver' => 'session',
'provider' => 'timekeeper',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
],
],
Most my users authenticate using the 'web' guard, however administrators and timekeepers each use their own guard, which is attached to an appropriate user provider.
This is fine until I try to use authentication gates. If I authenticate a user against the system's default guard (e.g. 'web'), then the gates work as expected. If I authenticate against any other guard however, then all Gate::allows(...) calls are DENIED.
Even the following ability is denied:
Gate::define('read', function ($user) {
return true;
});
Presumably this is due to line 284-286 in Illuminate\Auth\Access\Gate:
if (! $user = $this->resolveUser()) {
return false;
}
As far as I can see, my options are to:
Go back to using a single 'web' guard, with a user provider that can locate any type of user (but I'm not sure how that would work if I start using an API in parallel)
Somehow set the default guard at run time, depending on the type of the current user. (It is currently set in the config file)
Somehow inject a different user resolver in to the Gate facade (again, depending on the type of the current user)
None of these seems intuitive however. Am I missing something?
It's not the most elegant solution because it requires a lot of extra boilerplate code, but you can use Gate::forUser($user)->allows() instead of just Gate::allows() where $user comes from Auth::guard().
I had the same problem and I didn't really like this solution. After quite a lot of research I came up with this way to make your own user resolver in the Gate:
public function register()
{
$this->app->singleton(GateContract::class, function ($app) {
return new \Illuminate\Auth\Access\Gate($app, function () use($app) {
$user = call_user_func($app['auth']->userResolver());
if (is_null($user)) {
// Implement your own logic for resolving the user
}
return $user;
});
});
}
I put this in my AuthServiceProvider.

Resources