Unit testing user login in Laravel - laravel

Users can only log into the system when their account is active. I use a factory to create a user (initially their status is set to 'pending') and then try to post to the login form.
From here, I'm not too sure what assertions I need to do. I have already written the code that does this functionality (I have a custom Validator in the validateLogin form) and then it throws an error message something along the lines of 'your account is not active'.
What's the best way to approach this? What I have so far:
/** #test */
public function it_can_only_login_if_the_status_is_active()
{
$user = factory(User::class)->create();
$this->post('login', ['email' => $user->email, 'password' => $user->password, '_token' => csrf_token()]);
}

With the build in Laravel testing methods you could do this:
$user = factory(User::class)->create();
$this->visit('/login')
->type($user->email, 'email')
->type($user->password, 'password')
->press('Login')
->see('your account is not active');
I can recommend the Codeception library if you need to test some complicated features, because with it you can verify results in the DB, create acceptance and functional tests.

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;
}

Can't get user while testing auth in laravel

I'm writing automated tests for a legacy laravel project, 5.8.38.
I have this test method.
public function testUserReceivesAnEmailWithAPasswordResetLink()
{
Notification::fake();
$user = factory(User::class)->create([
'email' => 'john#example.com',
]);
$this->post($this->passwordEmailPostRoute(), [
'email' => 'john#example.com',
]);
$this->assertNull($token = DB::table('password_resets')->first());
Notification::assertSentTo($user, ResetPassword::class, function ($notification, $channels) use ($token) {
return Hash::check($notification->token, $token->token) === true;
});
}
This always fails because the user cannot be retrieved. The passwordEmailPostRoute() method goes to the src/Illuminate/Auth/Passwords/PasswordBroker.php sendResetLink() method, eventually ending up in src/Illuminate/Auth/EloquentUserProvider.php at retrieveByCredentials() method.
This always returns null.
I tried dumping data and queries, but everything failed. Any ideas appreciated.
This seems to be a very specific issue which I caused for myself.
My user factory generated wrong values for a morph connection field which prevented the return of a valid User object. I had to change the factory and the issue is now resolved.

What's the right way to work with MVC and Laravel

I'm building a CRUD using laravel and I'm not sure about the MVC rules.
I thought that all the functions related to database (Crud) should be done inside the model and not the controller. But I found this inside User's Controller:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
I know it's not persisting to the database, just returning a new instance of the class User. Should I call this function inside the model so then persist it ?
Doesn't make much sense calling this to just make a ->save().
Your example is okay, but if you think your controller's doing too much work that it should not be doing, you can refactor your code to transfer the work.
For example, in your code, the password is being bcrypted, you can create a new function in User model (or another helper class if you want, like UserHelper or UserQuery)
class User ...
{
public static function registerUser($data)
{
$data['password'] = bcrypt($data['password']);
$user = self::create($data);
return $user;
}
}
You can now use this to directly pass user data, it will shoulder the bcrypting of the password.
$new_user = User::registerUser(['username' => 'helloworld', 'password' => 'worldhello']);
I think we should always put in mind if a class/method/any other point of control is doing something that is beyond its purpose, then that's the time we should think of refactoring it to another point of control.

What is the difference between "login" and "attempt" method in Auth

I'm learning Laravel 5.4 and customizing and making my original Auth functionalities.
The below is my "authenticate" method.
public function authenticate(Request $request)
{
$remember_me = (Input::has('remember')) ? true : false;
Auth::guard('web');
$this->validateLogin($request);
$credentials = array(
'username' => trim($request->input('username')),
'password' => trim($request->input('password'))
);
if(Auth::attempt($credentials, $remember_me)){
$user = Auth::guard('web')->user();
Auth::guard('web')->login($user, $remember_me);
return redirect()->route('mypage');
}
return redirect()->back();
}
I have a question about the part of $remember_me argument about both attempt and login methods noted above.
What is the difference between them?
When I saw the documentation, it said similar to, if you want to make "remember me" token, you can set the second boolean argument about both of them.
attempt($credentials, $remember_me) will attempt to log the user in if the login credentials are correct. If they are not, then the user is not logged in. This method returns a boolean so you can check success.
login($user_id, $remember_me) will log the user in, without checking any credentials.
The remember me specifys if the user login should persist across browser sessions without needing to re-auth.
In your example I see your calling login(...) within your attempt(...). This shouldn't be needed. You can remove the login(...) line.
Example:
if(Auth::attempt($credentials, $remember_me)){
return redirect()->route('mypage');
}

Pass user id in laravel after logged in

I would like to pass / submit the user_id of the currently logged on user. How will I do it in laravel 5.2? Please need help
I am not sure on my code on how will I use Auth:user() blah blah. Need help with this. I am new to this.
You can use the login functionality like this and pass the datas to the required pages as per your wish.
public function Dologin()
{
// create our user data for the authentication
$userdata = array(
'email' => Input::get('email'),
'password' => Input::get('password'),
);
if (Auth::attempt($userdata))
{
$user_id=Auth::user()->user_id;// user_id it will change as per the users table in your project.
return redirect('index');
}
else
{
}
}
Make sure you save your password using bcrypt method and for that alone the Auth::check() and Auth::user() will work.
auth()->id() will provide the user id

Resources