Just want to be sure if that's the "right way" and most importantly if it's "secure way". I'm making simple form for someone who want's to reset password (not logged in). I don't want to use Laravel way (no dedicated database for that). Simply I'm sending crypted link, link is valid for 10 minutes.
public function sendConfirmationLink(Request $request)
{
$validatedData = $request->validate([
'email' => 'required|email|exists:users',
]);
$user = User::where('email', $request->get('email'))->firstOrFail();
$parameters = [
'user_id' => $user->id,
'date' => Carbon::now(),
'type' => 'password_reset',
];
$passwordResetLink = Crypt::encrypt($parameters);
SendPasswordResetLink::dispatch($user, $passwordResetLink)->onQueue('high');
}
Later on, I'm decrypting that hash and I'm making password change.
public function resetPassword(Request $request)
{
$validatedData = $request->validate([
'hash' => 'required',
'new_password' => 'required|string|min:6|confirmed',
]);
try {
$decrypted = Crypt::decrypt($request->get('hash'));
$password = Hash::make($request->get('new_password'));
$user = User::where('id', $decrypted['user_id'])->firstOrFail();
$user->password = $password;
$user->save();
} catch (DecryptException $e) {
abort(404);
}
}
If I'm right... that "link" will be extremely difficult to crack and it's valid for only 10 minutes. Is it in any way less secure then native Laravel version with similar code stored in database?
Yes I know that Laravel Auth can do it for me. I want use my method in more then password reset so I'm looking for answear if it's secure this way.
The Crypt library from Laravel uses a combination of OpenSSL and AES-256-CBC with a MAC signing. I think for a 10 min period, that should be safe enough.
A more serious problem is, that you can't "disable" a link after usage. So theoretically everyone with the link can change the account password infinite times (in a 10 min period).
A solution for that problem could be, that you add the current password hash into your link-hash and then compare that. In that case the password change would only work one-time per link.
Related
im using laravel middleware (visits) to create visits record in database like this:
public function handle(Request $request, Closure $next)
{
Visit::create([
'ip' => 'user ip',
'browser' => 'user browser',
'url' => url()->current(),
'referer' => $request->headers->get('referer'),
]);
return $next($request);
}
the problem is here:
If the user refreshes the page quickly, a large number of records will be saved
what can i do to prevent this?
You could create a unique job with some of the parameters that saves the Visit instead of directly saving it, that should work.
I created a user with encrypted password.(bcrypt(password))
In rest api I am getting md5(password) [from app]
How to validate both the password?
\Hash::check($request->password, $user->password)
NOT WORKING
There is no direct way to compare the actual password in Laravel. Laravel never store your password as plain text but a hashed version + salt, so you can check the plain version of the password against the hashed stored version using the Hash::check method:
// original password
$password = 'my-password';
// hashed password
$hashed_password = bcrypt($password);
// something like: "$2y$10$XFs6ocWUaiiB99QvLwTuhOuABIq71D13LmpFdeISh7RsC.SsAthHG";
The hashed version is the one stored.
If you want to check if the validation of a password:
use Illuminate\Support\Facades\Hash;
// ...
$passed = Hash::check($password , $hashed_password); // true
You can not compare passwords encrypted by different ways. You need the plain password. I guess you are trying to make a seamless access for users in your platform in order to avoid the user do two times login (in your system and the api provider system) Am I correct?
In that case, probably you need to think in other way to approach it.
In Laravel 9 i make like this,
first import
use Illuminate\Support\Facades\Hash;
in rules() function
return [
'password' => ['required', function ($attr, $value, $fail) {
$user = User::where('email', $this->request->get('email'))->first();
if ($user && !(Hash::check($value, $user->password))) {
$fail('The ' . $attr . ' is invalid.');
}
}],
];
To create a user without the end-user having to type in the details.
Something akin to
User::classcreate([
'email' => $email,
'password' => $rand_pass,
.....
]);
Thanks for the ideas and feedback in advance :)
The use case is.
The end-user invites another user to use the service by typing in their email and it creates a user with a random password before sending a email to the new user with their created details.
You're almost there with your code. It should look like this:
User::create([
'email' => $email,
'password' => bcrypt($rand_pass),
]);
But if you want to hash the password, you should also send the password to that user through email (which is not very secure). When the users logs in for the first time, you should at least require him to change the password.
You can simply use create() method of your User model:
$userData = [
'email' => 'some#email.com',
'password' => 'somepassword'
];
$newUser = User::create($userData);
You'll also need your password hashed in order for it to work with Laravel's authorization. Add the following to your user model - it will hash password before it's saved to the database:
public function setPasswordAttribute($password)
{
$this->attributes['password'] = Hash::make($password);
}
I need to do some extra checks on a user, I would like to get the user by username and password.
Firstly:
Is there a built in function that gets a user by username and password without authenticating them?
Secondly:
If the above is no, then how do I correctly hash the password, because if I use Hash::make( $password ) and then compare to the database, it is not the same... You would usually use Hash::check but I need to actually get the user by username and password.
In Laravel 5.2
You can use Auth::once($credentials) to validate credentials and thereafter Auth::getUser(); to get the user.
$credentials = Request::only('username', 'password');
if(!Auth::once($credentials)) {
// Invalid user credentials; throw Exception.
}
$user = Auth::getUser();
First:
If you want to check if user data to authentication is correct you can use:
if (Auth::validate($credentials))
{
//
}
But if you want to get user from database with user and password, you can use:
$user = User::whereName($username)->wherePassword(Hash::make($password))->first();
Second
To store password in database you should use Hash::make($password) as you showed and it works without any problems. Using Auth::validate should solve the issue.
Yes, there is a built in function you should use. I recommend you to read the docs. But here's a good example, it's pretty self-evident:
$input = array(
'username' => Input::get('username'),
'password' => Input::get('password'),
);
$remember = (boolean)Input::get('remember'); //'Remember me' checkbox
if (Auth::attempt($input, $remember)) {
return Redirect::intended('dashboard')->with("success", "You're logged in!"); //Redirect the user to the page intended to go to, with the dashboard page as default
}
Registering a user looks something like this:
$input = array(
'username' => Input::get('username'),
'email' => Input::get('email'),
'password' => Hash::make(Input::get('password')) //Encrypt password
);
$user = User::create($input);
I also recommend you to read about input validation. I hope this helps, good luck.
Edit: I didn't read the "without authenticating them" part. You should use Auth::validate($input) as Marcin already explained.
Laravel 5.7
To check a users credentials without logging them in, I had to do this:
$user = User::whereEmail($request->email)->first();
$user = password_verify($request->password, optional($user)->getAuthPassword()) ? $user : false;
Laravel auth validation makes use of https://www.php.net/manual/en/function.password-verify.php
On the password reset form the user supplies current_password, password and password-confirmation. Is there a way to specify in the validation rules that current_password (it's hash value) must match the database value?
Currently I have this:
$rules = array(
'current_password' => 'required',
'password' => 'required|confirmed|min:22'
);
Thank you.
UPDATE
Thanks to #ChrisForrence and #Ben, I came up with the following which works great! Much appreciated. Hope this will help someone else:
Validator::extend('hashmatch', function($attribute, $value, $parameters)
{
return Hash::check($value, Auth::user()->$parameters[0]);
});
$messages = array(
'hashmatch' => 'Your current password must match your account password.'
);
$rules = array(
'current_password' => 'required|hashmatch:password',
'password' => 'required|confirmed|min:4|different:current_password'
);
$validation = Validator::make( Input::all(), $rules, $messages );
You can't, bcrypt hashes are unique (they have their own random salt incorporated) so even if you knew the user's plain text password you would't be able do a hash-to-hash comparison.
What you can do is actually check the plain text password against a bcrypt hash by doing Hash::check('plain text password', 'bcrypt hash') on your controller.