Laravel looses Session ( Authentication) - php

I have a problem with a specific route on Laravel. Every second time (and sometimes on the first time) when ill call a specific route, ill get an 401 error, returned from the Authentication Middleware.
File Middleware/Authenticate.php
class Authenticate
{
/**
* 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)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401); // THIS IS CALLED
} else {
return redirect()->guest('login');
}
}
return $next($request);
}
From that route:
Route::group(['middleware' => 'auth'], function () {
.........
Route::get('events', 'TaskController#events');
});
TaskController.php
public function events(Request $request) {
$time_from = $request->start;
$time_to = $request->end;
$events = array();
$user_id = Auth::user()->id;
.....
return response()->json($events, 200);
}
All called from a JQuery $.get Request. I dont know why Laravel thinks i am a guest, and then looses the Session?

When you are doing ajax/api requests laravel thinks you're guest because session based authentications doesn't apply to this type of calls. Whenever you use auth middleware you'll get 401 on ajax, even if you're authenticated.
You need some type of token based authentication for the ajax/api calls, that sends Authentications header on requests and new middleware that handles authentications for it.

Related

Returning response when user is unauthorised in Laravel

When a user is not authenticated, Laravel redirects all request made to routes using auth::api middleware to the default laravel login page. However since I am building the API only, I want to return a 401 unauthorised response to any app that will be consuming my api. This should be simple but for some reason, I haven't been able to do just that. Here's what my code looks like
public function show(User $user)
{
if ($user->id == auth()->user()->id) {
// do something here
} else {
return response()->json([ 'status_message' => 'Unauthorised'], 401);
}
}
public function update(Request $request, User $user)
{
if ($user->id == auth()->user()->id) {
// do something here
} else {
return response()->json(['status_message' => 'Unathorised'], 401);
}
}
When I hit the endpoints calling these methods and the user isn't authenticated, I am redirected to the default laravel login page. How can I override this default behavior?
Call api.auth middleware in your routes as follows
$api->group(['middleware' => 'api.auth'], function ($api){
//your routes
});
Create a file Authenticate.php inside Middleware folder
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate {
/**
* The authentication guard factory instance.
*
* #var \Illuminate\Contracts\Auth\Factory
*/
protected $auth;
/**
* Create a new middleware instance.
*
* #param \Illuminate\Contracts\Auth\Factory $auth
* #return void
*/
public function __construct(Auth $auth)
{
$this->auth = $auth;
}
/**
* 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)
{
if ($this->auth->guard($guard)->guest()) {
return response('Unauthorized.', 401);
}
return $next($request);
}
}
and in kernel.php which is inside http folder include the following
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
];
Problem solved guys. Here's how I did it, in the Handle.php file in app\Exceptions\ I added this code to the render function
if ($exception instanceof AuthenticationException) {
return response()->json(['status_message' => 'Unauthorised'], 401);
}
And that was it.
Note: this worked in laravel 5.8

Laravel redirect()->intended() is not working in custom login controller

I have a custom login controller and its using return redirect()->intended(route('home')) , as per the documentation this should send the user redirect the user to the URL they were attempting to access before being intercepted by the authentication middleware.
But for my case every time it is redirecting to home route.I am sure i have done correctly or at least i think i have done correctly. Can anyone please tell me where i am doing this wrong ??
My logincontroller is like this:
public function __construct()
{
$this->middleware('guest');
}
public function login(Request $request)
{
$validatedData = $request->validate([
'email' => 'required|email|max:255',
'password' => 'required|max:255',
]);
try {
$response = HelperFunctions::fetchData('post', 'login/login', [
'loginId' => $request->get('email'),
'password' => md5($request->get('password'))
]);
if ($response['code'] == 200 && $response['success']) {
session([
'api_token' => $response['sessionId'],
'user_data' => $response['data']['profile']
]);
return redirect()->intended(route('home'));
} else {
return redirect()->back()->with('error', 'Please provide valid credentials');
}
} catch (\Exception $e) {
return redirect()->back()->with('error', 'Opps!! Something is wrong. We are trying to fix it');
}
My authentication checking middleware
class checkAuthentication
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
//check if user is logged in
if(Session::has('api_token') && Session::has('user_data')) {
return $next($request);
}
return redirect(route('login'));
}
}
I also tried to dd() Session::get('url.intended') but it returns empty .
I did tried looking for reference on google, laracast & stackoverflow i found some reference , but those did not help me. Can anyone please help me thank you.
Some of the reference i have checked & tried:
https://laracasts.com/discuss/channels/laravel/redirect-intended-not-working-after-login?page=1
Laravel 5 - redirect()->intended() after authentication not going to intended
https://laravel.io/forum/11-24-2014-problems-with-redirectintended-and-auth
The simplest way would be to redirect()->guest() in your Auth middleware instead of the redirect you currently have:
return redirect()->guest(route('login'));
Using the guest() method on the redirector is what adds the url.intended value to the session.
Alternatively, instead of returning from the middleware if they're not authenticated you could throw an AuthenticationException:
public function handle($request, Closure $next)
{
//check if user is logged in
if(Session::has('api_token') && Session::has('user_data')) {
return $next($request);
}
throw new AuthenticationException('Unauthenticated.');
}
This will allow you to take advantage of Laravel's ExceptionHandler if you need to. You will also need to add use Illuminate\Auth\AuthenticationException; to the top of your class.
I also tried to dd() Session::get('url.intended') but it returns empty .
In my case, this key existed in my session, but for some weird reason, something funny happens after redirect()->intended() returns the redirect response. Maybe the response is discarded in transit, or intercepted by some other caller on the stack.
What I did to work around it was to manually obtain the intended url from the session and hand it over to the redirect (instead of relying on the underlying protocols of intended()).
By adding this
redirect(Session::get('url.intended', $this->redirectPath()));
at the bottom of LoginController#showLoginResponse, once user is authenticated, Laravel is able to remember intended url and redirect to it
I have also faced this problem and the best way I come up with is using the authenticated method.
So Override the authenticated method in your controller and use the intended route there.
/**
* The user has been authenticated.
*
* #param \Illuminate\Http\Request $request
* #param mixed $user
* #return mixed
*/
protected function authenticated(Request $request, $user)
{
if ($user->role == 'admin') {
return redirect()->intended('/admin');
} elseif ($user->role == 'user') {
return redirect()->intended('/');
}
}
also sometimes changing the RedirectIfAuthenticated middleware also helps.
so go to App\Http\Middleware\RedirectIfAuthenticated.php and edit it as well.
<?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)
{
if (Auth::guard($guard)->check()) {
if ($request->user()->role == 'admin') {
return redirect()->intended('/admin');
} elseif ($request->user()->role == 'user') {
return redirect()->intended('/');
}
// return redirect('/home');
}
return $next($request);
}
}
Do this:
return redirect()->intended('home');
Instead of this:
return redirect()->intended(route('home'));

Ajax Auth redirect on Laravel 5.6

I have a Like button that fires an Ajax Post, this route is protected by auth:api middleware:
myproject/routes/api.php
Route::group(['middleware' => ['auth:api']], function () {
Route::post('/v1/like', 'APIController#set_like');
});
When an authenticated user clicks the like button, no problem at all, everything works smoothly. But when guests click the button, I redirected them to login page via Javascript and after authentication they are redirected to the page specified in RedirectIfAuthenticated middleware, so usually to /home.
I modified that middleware as follows:
myproject/app/Http/Middleware/RedirectIfAuthenticated.php
<?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)
{
if (Auth::guard($guard)->check()) {
return redirect()->intended('/home');
}
return $next($request);
}
}
My Ajax call is this:
var toggleLike = function(){
var token = USER_TOKEN; //javascript variable
var current_type = $(thisLikeable).attr("data-type");
var current_id = $(thisLikeable).attr("data-id");
var jqxhr = $.post( APP_URL + "/api/v1/like", {api_token: token, type: current_type, id: current_id}, function(data) {
setLikeAppearance(data.message);
})
.fail(function(xhr, status, error){
if (xhr.status == 401) {
window.location = APP_URL + "/login" ;
}
});
};
The problem here's the intended() function, which for Ajax calls is not storing the correct session variable and I am not figuring out how to set it properly.
I am clearly missing something obvious, can anyone help?
Cheers!
EDIT
What I want to achieve is this:
GUEST is in //mysite/blabla
clicks Like button
gets redirected to login
logs in (or register)
gets redirected to //mysite/blabla with the Like already triggered on
What's happening is that in APIs sessions are not managed or in other words it's stateless. So the session middleware is not implemented on Laravel framework for API requests. Though you can manually add, it's not idle to use. So if the API does not use sessions and uses the redirect, fronted does not know about it, as API and frontend work as two separate apps. SO you need to send the frontend the status of the response and let the frontend handle the redirect as you have done with ajax. Just remove the redirect if unauthenticated and let the API throw unauthorized exception. Then, from the handler, handle the unauthorized exception.
Here is how to do it.
Add this to app/Exceptions/Handler.php
/**
* Convert an authentication exception into an unauthenticated response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Illuminate\Http\Response
*/
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest('login');
}
this will send the user a 401 with message Unauthenticated if the request was json(api request), else(if web request) will redirect to login
check the render method below or check it from source to understand what's happening. when an unauthorized exception is thrown we are telling to check the request type and if it's from an API request, we are sending a json response with 401 status code. So know from frontend we could redirect the user to login page after seeing the 401 status code.
From source
/**
* Render an exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Symfony\Component\HttpFoundation\Response
*/
public function render($request, Exception $e)
{
if (method_exists($e, 'render') && $response = $e->render($request)) {
return Router::toResponse($request, $response);
} elseif ($e instanceof Responsable) {
return $e->toResponse($request);
}
$e = $this->prepareException($e);
if ($e instanceof HttpResponseException) {
return $e->getResponse();
} elseif ($e instanceof AuthenticationException) {
return $this->unauthenticated($request, $e);
} elseif ($e instanceof ValidationException) {
return $this->convertValidationExceptionToResponse($e, $request);
}
return $request->expectsJson()
? $this->prepareJsonResponse($request, $e)
: $this->prepareResponse($request, $e);
}
AFTER EDIT
The intended method() is only for web routes as it uses session to extract the intended route or manually passed value. Here is the intended() method.
/**
* Create a new redirect response to the previously intended location.
*
* #param string $default
* #param int $status
* #param array $headers
* #param bool $secure
* #return \Illuminate\Http\RedirectResponse
*/
public function intended($default = '/', $status = 302, $headers = [], $secure = null)
{
$path = $this->session->pull('url.intended', $default);
return $this->to($path, $status, $headers, $secure);
}
To achive the redirect to the page the user is comming from you can
1 - Manually pass some queries with url like
/login?redirect=like(not the url, just a mapping for /comments/like url)&value=true(true is liked, false is unliked)
and handle it manually.
2 - Get and check the query parameters from url
3 - Use to() method to pass the intended url instead of using intended(). here is the to() method. (see 4 to see the recommended way)
/**
* Create a new redirect response to the given path.
*
* #param string $path
* #param int $status
* #param array $headers
* #param bool $secure
* #return \Illuminate\Http\RedirectResponse
*/
public function to($path, $status = 302, $headers = [], $secure = null)
{
return $this->createRedirect($this->generator->to($path, [], $secure), $status, $headers);
}
4 - But, I would recommend sending redirect url (I mean the mapping ex: like) as a response to frontend and let the frontend handle the redirecting. As API redirecting will work if the API is used by websites only. Suppose if you are using this same api for a mobile app, wonder how API redirect will work. It's not a work of API to redirect, unless if it's for things like OAuth Authentication, which would have a redirect url specified.
Remember to sanitize the url params to block XSS like stuff. Better
Send some values and map it to the urls. Like
[
//like is mapping
//comments/like is the actual url
'like' => 'comments/like'
]
Then, get the mapping url from array or use frontend mappings.
You can make changes in your RedirectIfAuthenticated.php to distinguish between Ajax call & normal login like this:
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)
{
if (Auth::guard($guard)->check()) {
if ($request->has('api_token')) {
// do whatever you want
return response()->json('some response');
}
return redirect()->intended('/home');
}
return $next($request);
}
}
Update:
Another solution is to remove Auth middleware from your route. In APIController#set_like function manually login user, trigger like & return json response.
If a button is not for a guest, then you shouldn't render it on page.
Instead, you should render a link to login, then if the user logs in you will redirect him back to where he was before. Now, user can see and click the button.

Laravel 5.2 Authentication Token

I'm trying to implement a Token based authentication in Laravel 5.2. What I've done is:
Routes.php
Route::group([
'middleware' => ['auth:api']
], function() {
Route::get('/api/reservation', 'apiController#get');
});
I've modified the User model and added the api_token field and added a new user with a random string via seeders:
Migration
Schema::table('users', function (Blueprint $table) {
$table->string('api_token', 60)->unique()->nullable()->default(null);
});
Seeder
User::create([
...,
'api_token' => str_random(60),
]);
Controller
class apiController extends Controller
{
function get(Request $request) {
return Reserva::all();
}
}
Then, In Postman I try to GET the url adding /api/reservation?api_token=xxxxx with the token I have in the database but I always get Unauthorized. Something weird is if I do a dd($this->auth) on the authentication middleware I get a TokenGuard object with the name='web'. Isn't It supposed to be api?
Maybe I'm misunderstanding something, can you guys give me a hint? Thank you
The auth:api middleware you are using uses Laravel Passport. You cannot use it if you want a custom token based authentication like you do where you create your own tokens.
If you want to use Passport, do this:
Keep your routes like that. The routes that require authentication must be inside of the auth:api middleware.
You can remove the api_token field of your users table. The $table->rememberToken() function in the migration is completely different than the API token you think of. In fact, tokens are not stored in the database at all. The token you see in the oauth_access_token table in the database is not the token you use for you HTTP requests.
Do NOT create a custom token like you do. Check that the login/password couple of the user is valid, generate a token and return it to the consumer of the API like that:
if (Auth::attempt(['login' => $req->login, 'password' => $req->password])) {
$user = Auth::user();
$token = $user->createToken('ToutelaBreizh')->accessToken;
return response()->json(['token' => $token],200);
}
Beware to place the login/register routes OUTSIDE of the auth:api middleware, otherwise you would need to give a token to a route that is supposed to give you this token - it makes no sense.
Next, make sure you send the token to the API in the Authorization header of the request, and not in the request parameters like you do. Add in Postman the following header:
Authorization: Bearer your_token_retrieved_before
Now you're pretty much done, you can use your API with Postman.
In case someone else face this problem. My problem was In the Authenticate middleware. I had an old version somehow.
I had this Authenticate middleware:
class Authenticate
{
/**
* The Guard implementation.
*
* #var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* #param Guard $auth
* #return void
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('login');
}
}
return $next($request);
}
}
And I solved it by changing it with this:
class Authenticate
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if (Auth::guard($guard)->guest()) {
if ($request->ajax() || $request->wantsJson()) {
return response('Unauthorized.', 401);
} else {
return rediresct()->guest('login');
}
}
return $next($request);
}
}

Laravel $request->all() returning null inside the middleware handle function

My Auth middleware in Laravel 5.2 is returning null when I am trying to use $request->all() inside the handle() function.
Here is my route declaration:
Route::group(['middleware' => ['web', 'auth.origin']], function () {
Route::get('/', function () {
return view('welcome');
});
Route::post('identity', ['uses' => 'IdentityController#create']);
});
/**
* Handle an incoming request. Authenticates a request to make sure
* it's origin matches the allowed request origins.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle ($request, Closure $next)
{
dd($request->all()); die;
return $next($request);
$accountId = $request->input('accountId');
if( in_array($accountId, self::$origin) ) {
if( in_array($request->getHttpHost(), self::$origin[$accountId]) ) {
return $next($request);
}
}
else {
return response('Unauthorized', 401);
}
}
However, When I am calling the $request->all() method inside my controller, it is returning the array if values.
I am sending a JSON request from the client.
Please help.
Try this to access to parameters:
$request->accountId

Categories