I'm currently learning Lumen, I'm running a fresh install of Laravel 6.x .
I'm using RESTClient to stimulate my Lumen API/send http-Requests.
I want to set up some basic authentication with JWT now, and I already accomplished this.
I get a valid JWT and when I pass it through the following code, the firebase middleware (5.x) accepts the token, if valid, and rejects it if invalid.
The code doing the validation resides inside my AuthServiceProvider.php:
<?php
namespace App\Providers;
use App\User;
use Firebase\JWT\JWT;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
//new Code
class AuthServiceProvider extends ServiceProvider
{
public function register()
{
//
}
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request) {
$key = 'pawifjopawiejfpoaiwejfpoji';
$jwt = preg_replace('/^Bearer (.*)/', '$1', $request->header('Authorization'));
$decoded = JWT::decode($jwt, $key, ['HS256']);
//return User::where('email', $decoded->email)->first();
return "Hello From Auth!";
});
}
}
The router calling the middleware and routing its result to the controller resides in web.php and looks like this:
$router->get('myStorage', ['middleware' => 'auth', 'uses' => 'AuthController#myStorage']);
Now, my problem is this:
I've looked into all the depenencies inside providers/AuthServiceProvider.php, ServiceProvider.php insinde vendor/Illuminate/Support, Authenticate.php inside app/http/middleware and also bootstrap/app.php where the registering takes place.
The "auth" key/denominator, as it appears multiple times inside the sourcecode and also references the authentication middleware during routing, remains a mystery to me.
I don't know what object it points to.
I've tested changing it to "auth2" in all the places I found it (see above), but then I get this exception thrown by Lumen:
(2/2) BindingResolutionException
Target class [auth2] does not exist.
So somewhere, this class must have been defined.
Inside Authenticate.php, there is this line in the head of the code
use Illuminate\Contracts\Auth\Factory as Auth;
And I've manipulated it, alongside its occurrences in the whole code of authenticate.php .
But I always get the same error.
I want to know where this "auth" is coming from, because I also want to employ a middleware for AUTHORIZATION, following the AUTHENTICATION.
The routes would then look kinda like this:
$router->get('myStorage', ['middleware' => ['authenticate', 'authorize'], 'uses' => 'AuthController#myStorage']);
To do this, I think I need a better understanding of how and where this (third-party) middleware/its components are defined. Otherwise I will probably run into issues when registering this stuff for example.
Related
I am trying to define some policies in Laravel 8 which I cannot get to work, however I have the same project in Laravel 7 which seemingly works perfectly.
I am using the JSON API Specification package and it comes built in with Authorizers which allow me to run a policy on different methods.
I am trying to add a policy for the 'create' on all routes no matter what.
I have the following code:
public function create($type, $request)
{
$this->authorize('create', $type);
}
In this context and example, $type = 'App\Models\User' if I do a dd before that line I can confirm that I am hitting that method.
Inside of my AuthServiceProvider I have the following:
public function boot()
{
Gate::guessPolicyNamesUsing(function ($modelClass) {
return 'App\\Policies\\' . class_basename($modelClass) . 'Policy';
});
}
Which as said earlier, works perfectly in another project.
The following is my policy, as you can see it's very basic.
<?php
namespace App\Policies;
use App\Models\User;
class UserPolicy
{
public function create(User $user)
{
return true;
}
}
If I make a constructor in the policy class I can confirm that it is getting hit and I am getting inside of the policy which is why this is confusing me so much.
I have tried changing the name of the method in case it was something clashing with the naming convention but nothing seems to agree with it.
I have tried to dump composer just as a double check but again, no luck.
The issue with this is that there wasn't currently an authenticated user and although specified in the method parameters a user it was still failing.
When providing a guest route, you still need to add the parameter to the method but make it optional.
public function create(?User $user)
{
// do logic here
}
The documentation for this can be found at the following link: https://laravel.com/docs/master/authorization#guest-users
I started creating a REST API using the lumen framework and wanted to set up a particular behaviour for my GET /user route. Behaviour is the following:
If the request come from an authenticated user (using auth middleware), the method getAllFields from UserController is called and return all the data from the user
If it's not the case, the method get from UserController is called and return some of the data from the user
It seems logic to me to just write it like that in my web.php using a simple middleware:
<?php
$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/user/{id}', [
'uses' => 'UserController#getAllFields'
]);
});
$router->get('/user/{id}', [
'uses' => 'UserController#get'
]);
But for some reason, even if the middleware is correct, I always get the response of the second route declaration (that call get()). I precise that if I remove the second route declaration, the one in the middleware work as expected.
Have someone an idea how I can achieve something similar that work?
Router will check if your request matches to any declared route. Middleware will run AFTER that match, so You cannot just return to router and try to find another match.
To fallow Laravel and Routes pattern - You should have single route that will point to method inside controller. Then inside that You can check if user is logged or not and execute getAllFields() from that controller. It will be not much to rewrite since You are currently using UserController in both routes anyway.
web.php
$router->get('/user/{id}', 'UserController#get');
UserController.php
public function get()
{
return auth()->check() ? YourMethodForLogged() : YourMethodForNotLogged();
}
Or if there is not much logic You can keep this in single method.
Also it is good idea to fallow Laravels REST standards (so use show instead of get, "users" instead of "user" etc - read more https://laravel.com/docs/7.x/controllers)
web.php
$router->get('/users/{user}', 'UserController#show');
UserController.php
public function show(User $user)
{
if (auth()->check()) {
//
} else {
//
}
}
To summary - for your needs use Auth inside controller instead of middleware.
To check if user is logged You can use Facade Auth::check() or helper auth()->check(), or opposite Auth::guest() or auth()->guest().
If you are actually using Lumen instead of full Laravel then there is not auth helper by default (You can make own or use package like lumen-helpers) or just keep it simple and use just Facades instead (if You have then enabled in Lumen).
Read more https://laravel.com/docs/7.x/authentication and https://lumen.laravel.com/docs/7.x/authentication
This pattern is against the idea of Laravel's routing. Each route should be defined once.
You can define your route without auth middleware enabled and then define your logic in the controller.
I am using subdomain routing heavily within my project, as it's a multi-tenant application with each tenant having their own subdomain.
As a result, all of my routes are wrapped in:
Route::domain('{tenant}.'.config('app.base_url'))->group(function () {
// My routes here!
});
To use the route() helper within my code, I need to pass it all of the route parameters associated with it. Every single route has tenant associated with it, so I constantly found myself repeating code and writing route('my-route-name', ['tenant' => $request->route('tenant')]);
I've created a middleware with the following code in it:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use URL;
class SetTenantFromRequest
{
public function handle(Request $request, Closure $next)
{
list($subdomain) = explode('.', $request->getHost(), 2);
URL::defaults(['tenant' => $subdomain]);
return $next($request);
}
}
And placed it in the HTTP Kernel.php file like so:
protected $middleware = [
TrustProxies::class,
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
WebHeaders::class,
SetLanguage::class,
SetTenantFromRequest::class,
];
// Removed for brevity...
protected $middlewarePriority = [
StartSession::class,
ShareErrorsFromSession::class,
SetTenantFromRequest::class,
Authenticate::class,
ThrottleRequests::class,
AuthenticateSession::class,
SubstituteBindings::class,
Authorize::class,
];
I had to add it to the $middlewarePriority array as it needs to run before the Authenticate middleware. Since Authenticate calls return route('login');, I need that URL parameter available beforehand.
Now with doing all of this, I still get the following error thrown from the Authenticate middleware: Missing required parameters for [Route: login] [URI: login].
If I run ddd(URL::getDefaultParameters()); within the Authenticate middleware, it prints an empty array. However if I run the same ddd(...) within the SetTenantFromRequest middleware, it shows the tenant in there as I expect it to be.
Does anyone have an idea on how I can solve this problem?
Moving this from the global middleware stack to the web middleware stack alleviates the issue.
This forces me to add the tenant parameter to the route(...) call within my Authenticate middleware, but it does allow me to forgo it everywhere else.
In Laravel 5, if basic auth fails for a user then the default message that is returned is an "Invalid Credentials" error string. I am trying to return a custom JSON error when this situation occurs.
I can edit the returned response in vendor/laravel/framework/src/Illuminate/Auth/SessionGuard.php
however I have not seen where you can change the behavior of this message outside of the vendor directory. Is there a way?
Looks like there were some ways to do this through Laravel 4: Laravel 4 Basic Auth custom error
Figured it out, looks like I had to create custom middleware to handle this.
Note that this solution didn't work when calling my API from my browser, only when calling it from a tool like Postman. For some reason when calling it from my browser I always got the error before seeing the basic auth prompt.
In my controller I changed the middleware to my newly created one:
$this->middleware('custom');
In Kernel I added the location for it:
protected $routeMiddleware = [
'auth.basic.once' => \App\Http\Middleware\Custom::class,
]
Then I created the middleware. I used Stateless Basic Auth since I'm creating an API:
<?php
namespace App\Http\Middleware;
use Auth;
use Closure;
use Illuminate\Http\Request as HttpRequest;
use App\Entities\CustomErrorResponse
class Custom
{
public function __construct(CustomErrorResponse $customErrorResponse) {
$this->customErrorResponse = $customErrorResponse
}
public function handle($request, Closure $next)
{
$response = Auth::onceBasic();
if (!$response) {
return $next($request);
}
return $this->customErrorResponse->send();
}
}
I cloned this todstoychev/Laravel5Starter from Github and installed it.
After creating this StaticPagesController controller and updating my routes.php file. The controller does not seem to work. For some reason i keep getting the following error.
ReflectionException in ControllerInspector.php line 32:
Class App\Http\Controllers\StaticPagesController#faq does not exist
My routes.php file
<?php
// Admin routes
Route::group(['prefix' => 'admin', 'namespace' => 'Admin'], function () {
Route::controller('permissions', 'AdminPermissionsController');
Route::controller('settings', 'AdminSettingsController');
Route::controller('roles', 'AdminRolesController');
Route::controller('users', 'AdminUsersController');
Route::controller('/', 'AdminController');
});
// Public and user routes
Route::controller('contacts', 'ContactsController');
Route::controller('users', 'UsersController');
Route::controller('/', 'IndexController');
Route::controller('faq', 'StaticPagesController#faq');
My StaticPagesController.php file
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class StaticPagesController extends Controller
{
public function faq(){
return 'this is faq page';
}
}
I have tried composer update, php artisan acl:update, composer dumpautoload to no avail.
Please help me. Thanks
With this line:
Route::controller('faq', 'StaticPagesController#faq');
You are telling Laravel that the controller for faq shoule be StaticPagesController#faq. The Route::controller method sets an entire controller for a route, it does not specify a method to be used on that route, Laravel handles this internally. Take a look at your error to prove my point:
Class App\Http\Controllers\StaticPagesController#faq does not exist
It is looking for class StaticPagesController#faq not StaticPagesController as you are intending.
Unless you are building an API using REST, you should not use the controller method and instead specify your routes explicitly, i.e.
Route::get('faq', 'StaticPagesController#faq');
This will use the faq method on your controller when the user makes a GET request to the URI faq. If you insist on using the controller method, then remove the #faq from the second argument and you will be good, although I'm pretty sure Laravel expects the methods index, show, create, etc to be in your controller. I suggest taking a look at the Laravel 5 Fundamentals video course to help you get a better understanding.