Pass user object to controller through request from jwt middleware? - php

I have a simple case and I need your advice. I am using tymon jwt package. I have JWT middleware and this is the part of it's code:
$user = JWTAuth::parseToken()->authenticate();
if(!$user){
return response()->json(['message'=>trans("responseMessages.user_not_exists")], 403);
}
$request->request->set('user', $user);
what this middleware does, is that it tries to create $user from given jwt token, if it succeeds, user is good to continue. so here is my question, in this code (final line) I pass user object to controller through request, so I can directly have access to user model in controller. I am just interested, is this a good idea? or maybe this will be problematic?
other option is to write $user = JWTAuth::toUser(JWTAuth::getToken()) in controller function or pass user id through request instead of whole model. but in these cases I communicate with database twice, in middleware and in controller in order to get user object.
also I tried to do something like that in controller constructor : $this->user = JWTAuth::toUser(JWTAuth::getToken()), but controller constructor executes before middleware so this one was problematic. so provide me with your ideas and advices if passing user model is good idea or not.

This is an opinionated question, so don't take my answer as your final solution.
I use Slim and made an authenticate-middleware that adds the user object to the request attributes. This is essentially what you are doing.
Keep in mind the folllowing problems though (at least with immutables Request/Response objects like with PSR7):
when you have middlewares BEFORE your authentication middleware (like catching Exceptions), the request does NOT have the user object, because the middlewares work in layers.
Vice versa: if you have middleware that first executes all other middleware and than executes itself
It's just pseudo-code, but you get the idea.
middlewarefunction($request, $response, $nextmiddleware)
{
$nextmiddleware->do($request, $response);
// from here on the $request has nothing set to the request by the $nextMiddleware
// because it is immutable
}
// Edit
If you look at other middlewares, they are setting the request attribute with the decoded JWT token too:
https://github.com/DASPRiD/Helios/blob/master/src/IdentityMiddleware.php
https://github.com/tuupola/slim-jwt-auth/blob/3.x/src/JwtAuthentication.php

Related

Laravel API verification/protection on subsequent requests: no login / logout and no "users" table

TLDR; see image below 3 - is that possible and how?
I read about API protection - Sanctum & Passport, but none of these seems what I can accomplish with my app since it's a little specific and simplified in a way.
For example, Sanctum's way of authenticating sounds like something I'd like, but without the /login part (i have a custom /auth part, see below.): https://laravel.com/docs/8.x/sanctum#spa-authenticating.
If the login request is successful, you will be authenticated and
subsequent requests to your API routes will automatically be
authenticated via the session cookie that the Laravel backend issued
to your client.
My app has no login per se - we log-in the user if they have a specified cookie token verified by the 3rd party API (i know token-auth is not the best way to go, but it is quite a specific application/use). It's on /auth, so Sanctum's description above could work, I guess if I knew where to fiddle with it. Our logic:
VueJS: a mobile device sends an encrypted cookie token - app reads it in JS, sends it to my Laravel API for verification.
Get the token in Laravel API, decrypt, send to 2nd API (not in my control), verifying the token, and sends back an OK or NOT OK response with some data.
If the response was OK, the user is "logged-in."
The user can navigate the app, and additional API responses occur - how do I verify it's him and not an imposter or some1 accessing the API directly in the browser?
I guess the session could work for that, but it's my 1st time using Laravel, and nothing seemed to work as expected. Also, sessions stored in files or DB are not something I'm looking forward to if required.
For example, I tried setting a simple session parameter when step 3 above happened and sending it back, but the session store was not set up, yet it seemed at that point. Then I could check that session value to make sure he's the same user that was just verified.
For an easier understanding of what I'm trying to accomplish and if it's even feasible:
The main question is, what is the easiest way to have basic API protection/authentication/verification whilst sending the token for authentication to 3rd party API only on 1st request (and if the app is reopened/refreshed of course) - keeping in mind, that no actual users exist on my Laravel API.
Or would it be best to do the token-auth to the 3rd party API on each request?
If I understand your case correctly there's no real User model involved, right? If so, you'll not be able to use any of Laravel's built-in authentication methods as they all rely on the existence of such a model.
In that case you'll need one endpoint and a custom authentication Middleware that you'll need to create yourself in Laravel in order to handle everything:
The endpoint definition:
Route::post('authenticate', [TokenController::class, 'login']);
The controller:
class TokenController extends Controller
{
public function login(Request $request)
{
// First read the token and decrypt it.
// Here you'll need to replace "some_decryption()" with the required decrypter based on how your VueJS app encrypts the token.
$token = some_decryption( $request->input('token') );
// Then make the request to the verification API, for example using Guzzle.
$isTokenOk = Http::post('http://your-endpoint.net', [
'token' => $token,
])->successful();
// Now issue a Laravel API token only if the verification succeeded.
if (! $isTokenOk) {
abort(400, 'Verification failed');
}
// In order to not store any token in a database, I've chosen something arbitrary and reversibly encrypted.
return response()->json([
'api-token' => Crypt::encrypt('authenticated'),
]);
}
}
Subsequent requests should pass the api token in the Authorization header as a Bearer token. And then in the Middleware you'll check for Bearer token and check if it matches our expected value:
class AuthTokenAuthenticationMiddleware
{
public function handle($request, Closure $next)
{
$authToken = $request->bearerToken();
if (! $authToken || ! Crypt::decrypt($authToken) === 'authenticated') {
abort(401, 'Unauthenticated');
}
return $next($request);
}
}
The Middleware needs to be registered in app/Http/Kernel.php:
protected $routeMiddleware = [
...
'auth-token' => AuthTokenAuthenticationMiddleware::class,
];
And finally apply this new middleware to any of your routes that should be authenticated:
Route::middleware('auth-token')->get('some/api/route', SomeController::class);
Warning: this authentication mechanism relies on reversible encryption. Anyone able to decrypt or in possession of your APP_KEY will ultimately be able to access your protected endpoints!
Of course this is one way to deal with custom userless authentication and there are many more. You could for example insert an expiration date in the encrypted token instead of the string "authenticated" and verify if it's expired in the middleware. But you get the gist of the steps to be followed...
If you do have a User model in place, then you could use Laravel Sanctum and issue an API token after User retrieval instead of forging a custom encrypted token. See https://laravel.com/docs/8.x/sanctum#issuing-mobile-api-tokens
// Fetch the corresponding user...
$user = User::where('token', $request->input('token'))->first();
return $user->createToken('vuejs_app')->plainTextToken;
Subsequent requests should pass the token in the Authorization header as a Bearer token.
Protect routes using the middleware provided by Sanctum:
Route::middleware('auth:sanctum')->get('some/api/route', SomeController::class);

Laravel access route parameter in route web.php file

The parameter is posted to some_name like this:
{{ route('some_name', $id = '1'}}
How can I access it in the if condition?
Route::group(['prefix' => '/'], function()
{
if ( condition )
{
Route::get('/route/{id}', 'ControllerA#methodA')->name('some_name');
} else{
Route::get('/route{id}', 'ControllerB#methodB')->name('some_name');;
}
});
How can I use the {id} parameter in the if (condition)?
I tried
Route::group(['prefix' => '/'], function($id)
{
if ( $id == 1)
And it's not working.
I think the best thing you can do is a Middleware, for example:
public function handle($request, Closure $next)
{
if ($request->id == 'some_value') {
redirect action('ControllerA#methodA');
}
else {
redirect action('ControllerB#methodB');
}
return $next($request);
}
Check the docs, personally i've never done an if inside my routes folder, besides that, it's really dangerous to practice stuff like that, make everything happen in the views, if you are messing up with user logged in or not, do auth::check() or something like that, but never play with the routes web.php to ensure security in your app, everything else is made on the controllers and views.
I don't think it's a good practice to validate the id in the route file to redirect to different controllers, and heres why:
You'll send a request to that endpoint and send an ID.
Is that ID valid? How do you know?
Is the ID an integer or a string?
Does ID exists in the request?
and with these 3 questions, you'll end up having validations + redirect to different methods and if it's an ID of interest to a database query, you'll have database code in there aswell.
The normal procedure I like to think is when it hits the route, it should hit Authorization and Authentication (middleware as Bak87 said). In there, you can validate if he's authenticated, if he's a certain user, whatever you'd like.
Afterwards this initial validation, you can redirect it to a certain method in a certain controller depending on your needs, however, I wouldn't advise as a class should have a single purpose according to some standards (but in the end, you can build the application how you want it).
I believe a route or a group of routes should have an middleware (for whatever primary validation you require of the person making the request), and each route should point to a single method in a controller. Once it reaches the controller, instead of having (Request $request) as the parameters for the method, you can have your own custom FormRequest, where you can validate the ID if you'd like.
If FormRequest isn't of interest, you can use Eloquent (if the ID you're looking for is related to it) FindOrFail to validate if it exists (if it doesn't, returns a 404 error not found, if you have a 404.blade.php file). This way, by the time it reaches the controller's method, it has been validated by sections, where routes are then protected by the main Authorization and Authentication, FormRequest to do the input's validation and you can specifically return whatever you'd like from the controller's method.
Obviously we don't know what is the view your returning but if slighty differs from each other, consider refactoring it in order to return only 1 view, composed of other blades

Laravel cookies comes with encryption, why?

I got the following serivce provider:
class CartServiceProvider extends ServiceProvider {
public function boot() {
}
public function register() {
$this->app->singleton(\Alexxosipov\Cart\Cart::class, function($app) {
return new \Alexxosipov\Cart\Cart($app['request']);
});
}
}
And my Cart class, where I got this:
$this->id = (request()->cookie('cart_id')) ? request()->cookie('cart_id') : false;
But request()->cookie('cart_id') returns encrypted string. If I will do it in any controller, it works fine. Why? What should I do to use it in Cart class?Laravel 5.5
The sequences of Laravel bootstrapping a request is this (v5.6): (CMIIW)
index.php is called
\Illuminate\Foundation\Application created
HttpKernel registered. This register your middlewares
Console Kernel registered. This defines console commands
Exception Handler registered. This defines exception handlers
HttpKernel instantiated. This instantiate all middlewares, plus boot/register all your service providers
Create global request instance. Pass it to HttpKernel to handle incoming request.
Middleware EncryptCookies called, cookies decrypted
Sending request to other middlewares to process
Send request to router, router dispatch to controller
...
Before sending response to browser, cookies encrypted back in EncryptCookies
Cookie is remained encrypted in Step 1 - Step 7. Your CartServiceProvider is trying to obtain a cookie that is yet to be decrypted at Step 6, which is not possible. Consider either
Decrypt the cookie by yourself (using just decrypt), or
Make a middleware to instantiate the cart after EncryptCookies. It's a little bit too early to instantiate cart at the bootstrapping service providers phase.
Edit: Add singleton suggestion
I think you could do this:
Create a new method named loadCartFromRequest($request) in your Cart::class. This method help you load a cart instance from request during the middleware phase.
In your CartServiceProvider, you register a singleton of Cart::class as usual, but no need to read the request here.
Create a middleware, called CartMiddleware. This middleware call app(Cart::class)->loadCartFromRequest($request).
Then at any other places that you need the cart instance, you can access your cart model from app(Cart::class).
I hope I understand your requirement correctly :)
Why? Cookie encryption protects data stored in the client's browser.
The How: Laravel uses the EncryptCookies middleware, this middleware would not yet be processed when your service provider is registered but would be processed for the controller since middlewares are in the routing stack before the request is passed to the controller.
Since I don't know about your cart class and its logic, I can't really recommend what you should do. Perhaps you need to think about when you are passing the Request object to your class.

Check user access with middleware string

I have a String with a middleware rule (like in routes):
$middleware = "can:index,App\Models\Order";
Is there any possiblity to check if a given user has access with this middleware rule?
That is how laravel policies define authorization rules in middleware. See here: https://laravel.com/docs/5.5/authorization#via-middleware
Create a policy class for the model you are authorizing then register the policy to model.
It is possible to see if a given user is authorized to carry out a certain action in many different ways. One is by using the middleware in your question and attaching it to a route or group. Here are some other ways:
Using the can method on the User object. The can method is inherited from the Authorizable trait (So it's not limited just to users):
if ($user->can('index', 'App\Models\Order')) {
// User is allowed to index orders.
}
Using the authorize method on a controller. The authorize method is inherited from the AuthorizesRequests trait (So this is not limited to just controller). This method throws an exception if authorization fails:
$this->authorize('index', 'App\Models\Order');
In a view, it is possible to use the #can Blade directive to see if a user is authorized to carry out the given action:
#can('index', 'App\Models\Order')
This user can index orders.
#endcan
If you have that specific string, you could do a little bit of manipulation to extract the bits you need and then pass it to one of the above methods:
$middleware = "can:index,App\Models\Order";
list($rule, $parameters) = explode(':', $middleware);
list($ability, $model) = explode(',', $parameters);
if ($user->can($ability, $model)) {
// User can index orders.
}
Of course it would be wise to do more error checking etc.

Return custom object from Slim 3 middleware

I'm writing an API in Slim 3 that integrates with a legacy system. The client sends a token to my API in the querystring. I want to write a middleware that will authenticate the token and return an object that contains the necessary internal login credentials (which are different from the token) that are used by the legacy system.
I can authenticate the token now, but my problem is that Slim 3 requires that the middleware return a \Psr\Http\Message\ResponseInterface instance. I also want it to return a custom object back to the application.
I think I can achieve this by re-verifying the token outside of the middleware, and only use the middleware as a way to authenticate the token and return an error if it fails. I tend to think this kludgy way could be avoided so I just have to use the token once in the middleware and return the custom object at the same time so I don't have to use the token twice.
I have searched around for solutions, but all of the example middlewares I can find are similar to https://github.com/julionc/slim-basic-auth-middleware, where they are simply authenticating in the middleware but do not have the requirement to return a custom object. The documentation at http://www.slimframework.com/docs/concepts/middleware.html doesn't seem to help much either with this custom requirement.
Any ideas?
You could include a callback in your middleware which you can use to store the custom object somewhere. For example with slim-jwt-auth you can use callback to store the decoded contents of JWT using a callback.
$app->add(new \Slim\Middleware\JwtAuthentication([
"secret" => "supersecretkeyyoushouldnotcommittogithub",
"callback" => function ($request, $response, $arguments) use ($app) {
$app->jwt = $arguments["decoded"];
}
]));
Note that this kind of callback is not a Slim 3 feature. Just something this middleware happens to use.

Categories