laravel: how to actually login users (ajax login) - ajax

Can't...Log users... in... Angry.
Basically, I'm running users through the regular login, with my own authenticated method defined to return a json object instead of being redirected. For the moment, I'm returning the results of this function
Auth::check();
Which returns true when all is said and done.
The problem is that my logins don't appear to exist beyond this single call. If I make anymore ajax calls, Auth::check() fails every time. If I refresh the page, auth::check() fails.
Please god help me.
PS. My session driver is current set to cookies.
//EDIT TO SHOW THE PEOPLE I'M NOT CRAZY
public function ajaxLogin(Request $request)
{
if(Auth::check()){
return response()
->json('loggedIn');
}
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $lockedOut = $this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
if (Auth::guard($this->getGuard())->attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
if ($throttles && ! $lockedOut) {
$this->incrementLoginAttempts($request);
}
return response()
->json([
$this->loginUsername() => $this->getFailedLoginMessage(),
]);
}
protected function authenticated($request, $user){
Auth::login($user);
return response()->json( 'yo');
}
If I send the right credentials, I get back yo. If I send the right credentials again (another ajax call) I get yo. I can never get logged in.
From what I can tell, for some reason, my sessions are being destroyed with every request? I don't know why, but this does appear to be why I'm never logged in.
//EDIT ADD SITUATIONAL CLARITY
laravel -v 5.2
this is all through the auth controller, so just the default:
Route::group(['middleware' => 'web'], function () {
Route::auth();
Route::get('/home', 'HomeController#index');
});

Related

Create session on consuming login with api on laravel

I have an api that has a method to start and I am calling it from a frontend project.
In the front end project I use Guzzle to make the call via post to the api and login, from which I get back a json with the user data and a jwt token.
But when I receive the token as I manage the session, I must create a session and save the token, since the laravel to authenticate I need a model user and have a database, which of course I do not have in this backend because I call the api to log in, which brings a token and user data, then as I manage it from the backend, I'm a little lost there.
$api = new Api();
$response = $api->loginapi(['user'=>'wings#test.com','password'=>'123']);
Because here I could not do Auth::login($user) to generate the session.
Because I don't have here the database because the login is done from the api.
There I call the api, of which the answer is the token, but how do I manage it from here, creating a session? saving the token?
thanks for your help.
With api, you don't usually manage a session. usually, you'd call something like
Auth::attempt([
'email' => 'me#example.com',
'password' => 'myPassword'
]);
If the credentials are correct, laravel will include a Set-Cookie header in response, and, that is how you authenticate with api. Via an auth cookie. You don't need to do anything else.
Let's show you how:
//AuthController.php
public function login(Request $request) {
$validatedData = $request->validate([
'email' => 'required|email',
'password' => 'required'
]);
if(Auth::attempt($validatedData)){
return ['success' => 'true'];
}
else{
return ['success' => false, 'message' => 'Email or password Invalid'];
}
}
public function currentUser (){
return Auth::user();
}
Now, the APi file
Route::post('/login', ['App\Http\Controllers\AuthController', 'login']);
Route::get('/current_user', ['App\Http\Controllers\AuthController', 'currentUser']);
Now if you make a call to /api/current_user initially, you'll get null response since you're not currently logged in. But once you make request to /api/login and you get a successful response, you are now logged in. Now if you go to /api/current_user, you should see that you're already logged in.
Important ::
If you are using fetch, you need to include credentials if you're using something other than fetch, check out how to use credentials with that library or api
You want to use the API to authenticate and then use the SessionGuard to create session including the remember_me handling.
This is the default login controller endpoint for logging in. You don't want to change this, as it makes sure that user's do not have endless login attempts (protects for brut-force attacks) and redirects to your current location.
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
if ($request->hasSession()) {
$request->session()->put('auth.password_confirmed_at', time());
}
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
The core happens when we try to "attemptLogin" at
protected function attemptLogin(Request $request)
{
return $this->guard()->attempt(
$this->credentials($request), $request->boolean('remember')
);
}
When using the SessioGurad (which is default) the method attemptLogin fires a couple of events, checks if the user has valid credentials (by hashing the password and matching it with db) and then logs the user in, including the remember me functionality.
Now, if you don't care about events, you can just check from your API if the credentials match and then use the login method from the guard. This will also handle the remember me functionality. Something like this:
protected function attemptLogin(Request $request)
{
$username = $request->input($this->username());
$password = $request->input('password');
$result = \Illuminate\Support\Facades\Http::post(env('YOUR_API_DOMAIN') . '/api/v0/login' , [
'username' => $username,
'password' => $password
])->json();
if(empty($result['success'])){
return false;
}
// Maybe you need to create the user here if the login is for the first time?
$user = User::where('username', '=', $username)->first();
$this->guard()->login(
$user, $request->boolean('remember')
);
return true;
}

Laravel Inertia (Vue) - Authenticate without Redirect

I'm making a normal Inertia post to a base Laravel login route:
submit() {
this.$inertia.post("/login", {
email: this.emailAddress,
password: this.password,
}, {
preserveState: true,
preserveScroll: true,
});
}
I'm able to catch validation errors as expected, but what I'm trying to avoid is the redirect after a successful user authentication, and instead proceed in the "logged in" state (update header to show user info, etc).
The Laravel AuthenticatesUsers trait contains this contains two key methods that gets called as part of the out-of-the-box login flow
public function login(Request $request)
{
$this->validateLogin($request);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
if (method_exists($this, 'hasTooManyLoginAttempts') &&
$this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if ($this->attemptLogin($request)) {
return $this->sendLoginResponse($request);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
and
protected function sendLoginResponse(Request $request)
{
$request->session()->regenerate();
$this->clearLoginAttempts($request);
if ($response = $this->authenticated($request, $this->guard()->user())) {
return $response;
}
return $request->wantsJson()
? new Response('', 204)
: redirect()->intended($this->redirectPath());
}
I'm struggling to figure out if it's even possible to authenticate a user without redirecting this way.
You need to utilize the javascript frontend, not Inertia::post() . One way to do this is to use Axios:
submit() {
const data = {...this.form.data()};
axios.post('/auth/login', data, {
headers: {
'Content-Type': 'application/json',
},
})
.then(res => {
console.log('login success!', res);
});
Check your form and the way you submit - do you prevent the default behavior of the form submit? It seems like you are sending a POST but the native form behavior is also triggered.
You can also set a $redirectTo in your LoginController, also check RouteServiceProvider there is a public const HOME = '/' which triggered the redirect if nothing else is given.
This are my two cents...
A few days ago I was struggling with passing the result of the script to Vue without redirecting, using Inertia visits instead of Axios.
The solution I adopted was the following:
In vue:
this.$inertia.visit(`URL`, {
method: "post",
data: { //Email and password },
preserveState: false,
preserveScroll: false,
onError: (errors) => { // do what ever is needed if validation fails },
onSuccess: () => { // do what ever is needed if validation succeeds }
});
In Laravel:
// If validation fails:
return redirect()->back()->withErrors([
'login' => 'Validation fail details.'
]);
// If validation succeeds:
return redirect()->back()->with('login', 'Success message!');
This way the page does not redirect and the user can continue exactly wherever he is.
What i'm not sure is if it's possible to pass the user info over the success redirect message. Maybe returning a array like it's done in withErrors. If not possible it's always possible to make an additional request to the server to retrieve the desired information.
Hope it's usefull.

Redirect user to homepage if he is not authorized to access page its not working properly

If a user creates a conference with id "2" he should be allowed to access "proj.test/conference/manage/2".
But a user that did not create the conference with id "2" should be redirected to the login page if he is not authenticated. If he is authenticated should be redirected to the homepage.
But its not working properly, if the user created the conference with id 2 he can access "proj.test/conference/manage/2" but other user that did not create this conference if accesses "proj.test/conference/manage/2" it appears an error:
This action is unauthorized.
So instead of redirecting the user to the homepage it shows this error. Do you know why is not working?
I have the store method, after storing the conference the user is redirected to the management area to manage that specific conference, for example, to manage the conference with id 2 the user is redirected to "proj.test/conference/manage/2".
Store method:
public function store(Request $request)
{
$this->validate($request, [
'conference_name' => 'required|max:255|string',
...
]);
$conference = Conference::create([
'name' => $request->conference_name,
...
]);
}
Then in the AuthServiceProvider I add:
public function boot(GateContract $gate)
{
$this->registerPolicies();
$gate->define('access-management-area', function($user, $conference)
{
return $user->id == $conference->conference_creator_id;
});
}
And in the manage method I have:
public function manage($id){
$conference = Conference::findOrFail($id);
if($this->authorize('access-management-area', $conference)){
return view('conferences.manage')->with('myconference',$conference);
}
else{
return redirect('/home');
}
}
Do not use $this->authorize as it does not work the same as Gate::allows()/denies().
The authorize method will throw an exception if it fails, it will not return false for the sake of conditional comparison.
From the docs:
If the action is not authorized, the authorize method will throw an Illuminate\Auth\Access\AuthorizationException, which the default Laravel exception handler will convert to an HTTP response with a 403 status code.
So, instead, use Gate::denies for comparisons.
if(Gate::allows('access-management-area', $conference)) {
return view('conferences.manage')->with('myconference',$conference);
} else {
return redirect('/home');
}

Laravel issue with loginUsingId (Manual Authentication)

I am trying to implement a single signon on multiple domains. The concept is pretty simple i.e to send unique user tokens and then verify these tokens to find the user and then log him in.
Now after verifying the token and then grabbing the user, i do something like this
$loggedInUser = Auth::loginUsingId($user->id, true);
Now i have a custom middleware where it first checks for a logged in user, i.e
Auth::Check()
The above works fine for the first time. But on refresh Auth::check() is not validated. I have also tried using all different session drivers but still doesn't work.
I used a similar code on laravel 5.2, and it did work. But on laravel 5.3 its not validating on persistent requests.
Edit: Let me show you my Code
I have not modified AuthServiceProvider or any other guard. I do have the user model inside a directory but i have modified the path in auth.php.
Here is the route that domain1 points to:
http://domain2.com/{{$role}}/{{$route}}/singlesignon/{{$token}}
This is then picked up by verifySingleSignOn method inside the loginController which takes in the role, route that the user came in from other domain and the token. The user is then redirected to the same routes, but on domain2. Here i can successfully recieve the user id before manually logging in.
public function verifySingleSignOn($role, $route, $token)
{
// Fetch Single Signon
$userRepository = new UserRepository();
$user = $userRepository->checkForSingleSignOnToken($token, ['id']);
// Check if Token Exists
if (isset($user->id) && is_int($user->id) && $user->id != 0) {
// Manually Logging a user (Here is successfully recieve the user id)
$loggedInUser = Auth::loginUsingId($user->id);
if (!$loggedInUser) {
// If User not logged in, then Throw exception
throw new Exception('Single SignOn: User Cannot be Signed In');
}
$redirectTo = $role . '/' . $route;
return redirect($redirectTo);
} else {
return Auth::logout();
}
}
Then i have this GlobalAdminAuth middleware
// Check if logged in
if( Auth::Check() ){
$user = Auth::User();
// Check if user is active and is a globaladmin
if( !$user->isGlobalAdmin() || !$user->isActive() ){
return redirect()->guest('login');
}
}else{
return redirect()->guest('login');
}
return $next($request);
Now the first time everything works fine and the user moves through the middleware successfully . but the second time the else statement is triggered.
Edit: Code for checkForSingleSignOnToken
public function checkForSingleSignOnToken($token, $columns = array('*'))
{
return User::where('single_signon', $token)->first($columns);
}
try
Auth::login($user);
instead of
Auth::loginUsingId($user->id, true);
Cookies are restricted domain-wise. Your application on domain1.com wont be able to grab cookies set by domain2.com.
You should be customizing the guard to use some other mechanism than cookies. Maybe use a token in the query parameters.
add this to your protected $middleware array in app\Http\Kernel.php
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class
I think it has to do with an update in the framework
no using auth:check in middleware
using request->user() or auth::user()
Please review bellow code structure, i had made manual authentication
in laravel 5.0.
routes.php
Route::get('login_user_by_id/{id?}', ['as' => 'login_user_by_id', 'uses' => 'UsersController#login_user_by_id']);
Route::post('user_login_post_for_admin',['as'=>'user_login_post_for_admin','uses'=>'LoginController#user_login_post_for_admin']);
Route::get('user_logout', ['as' => 'user_logout', 'uses' => 'UsersController#user_logout']);
LoginController.php
public function user_login_post_for_admin(){
$this->set_email($_POST['email']);
$this->set_password($_POST['password']);
$this->set_login_requested_role(['Admin','Moderator']);
return $this->user_login_post();
}
public function user_login_post(){
$User = new User();
if(isset($this->email) && !empty($this->email)){
$User->set_email(trim($this->email));
$User->set_password(Hash::make(trim($this->password)));
$user_login_data = $User->check_email_password_for_login();
if(isset($user_login_data) && !empty($user_login_data)){
if (Hash::check(trim($this->password), $user_login_data[0]->password)) {
$response['user_id']=$user_login_data[0]->id;
$response['name']=$user_login_data[0]->name;
$response['surname']=$user_login_data[0]->surname;
$response['profile_picture']=$user_login_data[0]->profile_picture;
$response['SUCCESS']='True';
$response['MESSAGE']='Login Success.';
return Redirect::route('login_user_by_id',[$user_login_data[0]->id]);
}else{
Session::put('SUCCESS','FALSE');
Session::put('MESSAGE', 'Invalid Credential.');
return redirect()->back();
}
}else{
Session::put('SUCCESS','FALSE');
Session::put('MESSAGE', 'Invalid Credential.');
return redirect()->back();
}
}else{
Session::put('SUCCESS','FALSE');
Session::put('MESSAGE', 'Invalid Credential.');
return redirect()->back();
}
}
UsersController.php
public function login_user_by_id($id=''){
if(isset($_GET['id'])&&!empty($_GET['id'])){
$id = $_GET['id'];
}
$User = new User();
$Log=new Log();
$user_for_auth = $User->find($id);
Auth::login($user_for_auth, true);
$User->id=AUTH::user()->id;
$auth_user_role=$User->auth_user_role();
$rl_title=$auth_user_role[0]->rl_title;
return Redirect::route('admin_home');
}
public function user_logout(User $user){
$User=new User();
$login_user_id = AUTH::user()->id;
$User->id=AUTH::user()->id;
$auth_user_role=$User->auth_user_role();
$login_user_role=$auth_user_role[0]->rl_title;
$response['user_id']=$login_user_id;
$response['SUCCESS']='TRUE';
$response['MESSAGE']='Successfully Logout.';
Auth::logout();
return Redirect::route('admin_login');
}

laravel TokenMismatchExceptions on login

I'm getting this TokenMismatchException with Laravel 4. It happens to me if the browser sits on the login page for a while. For example a lot of times when I come back to work on my project the next day, if my browser has the login page open in a tab, when I try to log in I get the TokenMismatchException. If I'm logging in and out throughout the day while working, I never see it. It's like the token expires or something.
Route.php
// route to show the admin login form
Route::get('login', array('uses' => 'AdminController#showLogin'));
// route to process the admin login form
Route::post('login', array('uses' => 'AdminController#doLogin'));
AdminController.php
public function showLogin()
{
// show the login form
return View::make('admin.login');
}
public function doLogin()
{
// validate the info, create rules for the inputs
$rules = array('username' => 'required','password' => 'required' );
// run the validation rules on the inputs from the form
$validator = Validator::make(Input::all(), $rules);
// if the validator fails, redirect back to the form
if ($validator->fails()) {
return Redirect::to('login')
->withErrors($validator) // send back all errors to the login form
->withInput(Input::except('password')); // send back the input (not the password) so that we can repopulate the form
} else {
// create our user data for the authentication
$userdata = array('my_username'=> Input::get('username'),'password'=> Input::get('password'));
// attempt to do the login
if (Auth::attempt($userdata)) {
return Redirect::intended('dashboard');
} else {
// Authentication not successful, send back to form
return Redirect::to('login')->with('message', 'Your username/password combination was incorrect');
}
}
}
Please, help is needed...
That's normal, session will expire if you get idle for too long. It's a security measure, so you just need to make sure you redirect your user to login when the token expires. Add this to your global.php file or create a exceptions.php file to it:
App::error(function(\Illuminate\Session\TokenMismatchException $exception)
{
return Redirect::route('login')->with('message','Your session has expired. Please try logging in again.');
});

Resources