Issue with Laravel Session / SessionID - php

I've been using the session in laravel to tie down unauthenticated users to quotes. But I'm running into an issue that I cannot get my head around and have a feeling it has something to do with the magic parts of Laravels back end.
Here is my code:
$session_id = session()->getId();
$booking = Booking::create([
'trip_start_date' => $request->pickup_date,
'trip_start_time' => $request->pickup_time,
'token' => $session_id,
'ip' => $request->ip(),
]);
session()->put('at_token', $session_id);
But then when validating the tokens in middleware, the tokens are completely different.. as logged in my file:
AT_TOKEN [pjIGjpuz0tRT0mjLTtdwgzTCDXrdwRCJssgJ1ukE]
BOOKING TOKEN [3fcjAzdKTOv2IGy3Zw7skh2c9PqN9O9G98BVbAO0]
I see the token in the session looks like a session ID but the one from the DB seems to be unlike a session ID... any help would be greatly appreciated.
EDIT: Middleware... Although this is clearly working, the tokens do not match but when storing the session ID to the session and the DB, I use the same variable so how can they not be the same?!
//user not logged in, check session
if (session()->has('at_token')) {
$token = session()->get('at_token');
if ($token == $booking->token) {
//user has the token, give them access
return $next($request);
}else{
Log::info("AT_TOKEN [$token] DOES NOT EQUAL THE BOOKING TOKEN [$booking->token]");
}
}else{
Log::info('NO AT_TOKEN');
}

The solution for your problem is here.
https://laravel.com/docs/5.6/middleware#registering-middleware
also here
https://laravel.com/docs/5.2/routing
The document states that by default the session is used inside middlewareGroups rather than middleware which is not assigned to the routes but rather assigned to web middleware. So the solution is to move the line
\Illuminate\Session\Middleware\StartSession::class,
to
protected $middleware = [
..........
\Illuminate\Session\Middleware\StartSession::class
........
]
Now your session will persist.

Related

Laravel Passport get user Access Token after being issued

I'm using Laravel Passport to issue Bearers to the user once it's logged in and then be able to connect to the API (yes I know Sanctum might fit better my needs but I'm using Passport so don't even mention to switch to Sanctum), then on the Front-end, I store the user email and the Bearer in a cookie to use them later on for other queries and add the bearer to Axios Auth header though now I have a problem with my logic, maybe related to the fact that I don't know how to use Passport correctly in Nuxt.
I have queries for each page where I send the user email in a post request and they return back a mix of global info and user info.
Yes, my endpoints are already behind an auth middleware but I just send a Bearer Token to allow the endpoint to be queried with any data, there is no prevention to ask for User B info from User A.
How can I use prevent the user to send a different email and get another user's info?
This is how I issue an access token:
$token = $user->createToken('Laravel Password Grant Client')->accessToken;
there is a way to do something like this?
$user = User::where('email', $request->email)->first();
// E.g. user#mail.com ...
// Get the user Access Token
$userToken = $user->getAccessToken;
// E.g. someBerareText
// check if the User Access Token match with the one send in the request
// if they don't match throw a 401
if ($userToken !== $request->header('Authorization')) {
return response()->json([ "error" => "Not Authorized" ], 401);
...
// E.g. $request->header('Authorization') it's SomeOtherBearer because he
// requested info for user#mail.com but the $request->header('Authorization')
// belong to otheruser#othermail.com
The user can still send the same request but with User B's email and see other info that doesn't belong to him, so how can I check if the email in the $request belongs to the user that's actually logged in?
some way to decode the access token and check if it really belongs to the user or not?
If Laravel Passport is set up as the guard, it will fetch the user from the bearer token, the logic can be seen in the TokenGuard.php class in Passport. It actually does the same as you want to achieve.
So the authentication works different compared to the guard used. Therefor Passport requires you to change the guard. This is the deciding factor how Laravel differentiate Authentication and for that matter how the Auth::user() is loaded.
'guards' => [
...
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
This means that you can check if the user is the correct authenticated with the model function is() that compare if the models are the same.
User::where('email', $request->email)->first()->is(Auth::user())

Eloquent queries without database (data is retrieved from external API)

I have a Laravel web application (website.nl) AND an Laravel API (api.website.nl).
When the user logs in with the login form on the website. A post request is done to the API. When the login request is successfull a token AND User object is send back in the form of a JSON string. The login method looks like this.
public function login(Request $request)
{
$email = $request->input('email');
$password = $request->input('password');
$response = $this->client->request('POST', self::$apiRootUrl . '/login', [
'headers' => [
'Accept' => 'application/json',
],
'form_params' => [
'email' => $email,
'password' => $password
],
]);
$result = json_decode($response->getBody()->getContents());
if (isset($result->success->token))
{
$user = User::create([
'id' => $result->success->user->id,
'name' => $result->success->user->name,
'email' => $result->success->user->email,
'password' => $password
]);
Auth::login($user);
$this->apiToken = $result->success->token;
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
Where I want this login function to be as stupid as possible, meaning, let the API figure out if this is a valid login request. This method only passes the arguments.
The main website should not have a database. It only uses the API to login, get a token, and do requests with that token to gather data for the user to see on the main website's dashboard.
For this reason my question is this: How can I store the User object retrieved from the API in such a manner that it will be query-able like an Eloquent model. But without the need to store it in a database. Again, the main website shouldn't need a database. It only communicates with the API for it's data.
I hope this question makes sense, if not, please ask for more details.
I don't want to create a duplicate database with migrations on the main website. Because that's the job of the .api subdomain. Also, all the models are used within a session. Never being stored for a longer time, because that's also located in a store method on the .api subdomain.
This would require you to write your own grammar for Eloquent, an ActiveRecord implementation for the API you're using.
In theory it's possible, in practice it's a lot of work.
Take a look here for how Laravel implements the database types it currently supports.
https://laravel.com/api/5.8/Illuminate/Database/Schema/Grammars.html

Laravel vue return 401 error in api routes

so i have a route group in my api.php file like:
Route::group(['middleware' => 'auth:api'], function(){
//routes
});
The problem with this group is that is returning 401 error.
Why?
I guess my cookies/session whatever it is, is expired and it return 401 error (while user is not aware of it).
Issue
The problem here is laravel keeps my user logged in for SESSION_LIFETIME=120 but my api is sett to 60 (not sure about this yet)
'api' => [
'throttle:60,1',
'bindings',
],
Question
What should I do in order to logout user after session has expired
so the next time user visits my site not get into 401 error and
know he/she is already logged out and need to login again?
Update
I've made a middleware in order to check session lifetime and logout user if it's expire, but i can't get lifetime value out of it.
public function handle($request, Closure $next)
{
$bag = Session::all();
$max = config('session.lifetime') * 60; // min to hours conversion
if (($bag && $max < (time() - $bag->getLastUsed()))) {
$request->session()->flush(); // remove all the session data
Auth::logout(); // logout user
}
return $next($request);
}
The auth:api middleware means Laravel will check for a Authorization: Bearer <access token> header with the request, and return a 401 if it's not present or valid.
https://laravel.com/docs/5.6/passport#protecting-routes
Cookies and sessions don't apply here - it's a totally different form of authorization.
Make sure to add this line in the Kernel.php file :
'web' => [
// Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
Source : https://laravel.com/docs/5.8/passport#consuming-your-api-with-javascript

How to limit user actions with Laravel Passport Scopes + Password Grant Type

I have set up the Laravel Passport package for Laravel 5.3 just as described in the official documentation (https://laravel.com/docs/5.3/passport#introduction).
I want the API to be consumed by a mobile application, so I am trying to implement Password Grant Tokens. I have created a password grant client, and the token request process...
$response = $http->post('http://my-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'my#email.com',
'password' => 'my-password',
'scope' => '',
],
]);
...Just works as expected, returning an access-token and a refresh-token for one of my users.
But now I want to define some scopes so I can limit the access of users... Following the documentation again, I have them defined in boot method of AuthServiceProvider.php like:
Passport::tokensCan([
'admin' => 'Perform every action',
'user' => 'Perform only normal user actions',
]);
In this scenario, if a "malicious" normal user requested a token (using the above POST call) specifying 'scope' => 'admin', he or she would get an 'admin' token... and that is not what I want.
Thus, I would like to know how is the workflow in this situation to effectively limit the access to normal users, and where do I have to implement the scope validation logic.
Thanks in advance.
One way to go about this would be to create a middleware
For example if you only want users with an email from example.com to request the admin domain you can do something like this
Example ScopeLogic.php middleware:
if ($request->input('grant_type') === 'password') {
$scope = $request->input('scope');
$username = $request->input('username');
if ($scope === 'admin' && (strpos($username, '#example.com') === false)) {
return response()->json(['message' => "Not authorized to request admin scope"], 401);
}
}
return $next($request);
Of course, you would have to add this scope to your $routeMiddleware array in Kernel.php
protected $routeMiddleware = [
...
'check-scopes' => \App\Http\Middleware\ScopeLogic::class
]
As well as wrap Passport::routes() in AuthServiceProvider.php to check for this middleware
\Route::group(['middleware' => 'check-scopes'], function() {
Passport::routes();
});
Passport will also check that a correct username and passport combination was passed so you don't have to worry about that in the middleware
In my opinion, I think what confuses most people with OAuth and APIs is that scopes are tied to "clients" and not the "resource owner" themselves. Clients should be able to talk to an API using an admin scope or no scopes at all if needed. If they use an admin-ish type scope together with user context (password grant, authorization code grant, etc), then there is no stopping them from making calls that require such a scope against that user in the API. To me, the only person that can truly be classified as malicious would be one who manages to steal an access token containing an admin scope. That is why API implementors are allowed to specify what scopes to grant a client and if it's a first party app that uses something like the Password Grant, then you as a user has no choice but to trust it with your data.
I don't know how one would do this and use the retrieved token inside another's mobile app but if you did try requesting a token manually yourself with an admin scope, then I really don't see anything wrong that (other than you giving the app more control with you set as user context, so it may even be counter productive?)
If you need more control than that, then you need to go past your API and create something like application-level permissions for each user inside your resource server.
I forget where I read it, some Github issues somewhere, but apparently Laravel doesn't have that ability built in, each client is the treated the same equally, out of the box.
A user provided a good solution, and I built upon it here: https://stackoverflow.com/a/55285483/1132557

Slim 3 Session variables not persistent over routes

As the title reads, I can't get my session variables to be persistent over routes.
I call session_start() at the VERY beginning of my index.php.
Root route:
$app->get('/', function (Request $request, Response $response) {
$this->logger->addInfo("session id: " . $_SESSION['cus_id']);
$response = $this->view->render($response, "homepage.phtml", [
'test' => 'testing something',
'logged_in' => isset($_SESSION['cus_id'])
]);
return $response;
});
in my app.log I see that the session variable is non existent.
But this is my code after a successful login: (post to /login)
if ($customer !== false && password_verify($password, $customer['password'])) {
$_SESSION['cus_id'] = $customer['id'];
return $this->view->render($response, "homepage.phtml", [ 'logged_in' => true]);
}
I also used the logger here for testing purposes and it showed me that it saved the right id.
I have also used various extra libraries with the exact same result.
I also checked the official documentation, to no avail.
Question:
How do I get the session variables to be persistent throughout routes?
Or is the another way to save sessions? and or another way to solve this login problem?
I had the same problem after upgrading a server which resulted in a new server setting in the php.ini file.
For me, session.auto_start=1 in the php.ini did the trick.

Categories