Every Laravel project has a built-in the password reset process. It works perfectly with the web, but when it comes to mobile apps, this method will cause problems because it requires users to go to a web page to reset their password! I think this is not the best way to do such a thing, so the code password reset is pretty useful when you have mobile apps.
You will find the full code with better code written at the end of the post.
Prerequisites:
• A Laravel project
• An Account in Mailtrap & get credentials and put it in the .env
file.
Step 1: Create a new reset code table
Each Laravel project comes with a password_resets
table, I will leave this table(If your Laravel app just API for mobile apps then use password_resets) for web users and create another table for mobile app users as follows:
1- run this command in the terminal
php artisan make:model ResetCodePassword -m
2- open the reset_code_passwords_table
migration and it will be something like this:
Schema::create('reset_code_passwords', function (Blueprint $table) {
$table->string('email')->index();
$table->string('code');
$table->timestamp('created_at')->nullable();
});
Now migrate the table with php artisan migrate
.
3- open ResetCodePassword
then fill the $fillable
with columns names:
protected $fillable = [
'email',
'code',
'created_at',
];
Note: feel free to change anything you need
Step 2: Create files
- Create a new directory in
controllers
calledApi
then create these three controllers:
ForgotPasswordController.php // step 1
CodeCheckController.php // step 2
ResetPasswordController.php // step 3
- Create a mail class for email:
php artisan make:mail SendCodeResetPassword
Now let's fill these files, The first one ForgotPasswordController.php
will be something like this:
class ForgotPasswordController extends Controller
{
public function __invoke(Request $request)
{
$data = $request->validate([
'email' => 'required|email|exists:users',
]);
// Delete all old code that the user sent before.
ResetCodePassword::where('email', $request->email)->delete();
// Generate random code
$data['code'] = mt_rand(100000, 999999);
// Create a new code
$codeData = ResetCodePassword::create($data);
// Send email to user
Mail::to($request->email)->send(new SendCodeResetPassword($codeData->code));
return response(['message' => trans('passwords.sent')], 200);
}
}
in CodeCheckController.php
:
class CodeCheckController extends Controller
{
public function __invoke(Request $request)
{
$request->validate([
'code' => 'required|string|exists:reset_code_passwords',
]);
// find the code
$passwordReset = ResetCodePassword::firstWhere('code', $request->code);
//Check if it has not expired: the time is one hour
if ($passwordReset->created_at > now()->addHour()) {
$passwordReset->delete();
return response(['message' => trans('passwords.code_is_expire')], 422);
}
return response([
'code' => $passwordReset->code,
'message' => trans('passwords.code_is_valid')
], 200);
}
}
in CodeCheckController.php
:
class CodeCheckController extends Controller
{
public function __invoke(Request $request)
{
$request->validate([
'code' => 'required|string|exists:reset_code_passwords',
'password' => 'required|string|min:6|confirmed',
]);
// find the code
$passwordReset = ResetCodePassword::firstWhere('code', $request->code);
//Check if it has not expired: the time is one hour
if ($passwordReset->created_at > now()->addHour()) {
$passwordReset->delete();
return response(['message' => trans('passwords.code_is_expire')], 422);
}
// find user's email
$user = User::firstWhere('email', $passwordReset->email);
// update user password
$user->update($request->only('password'));
// delete current code
$passwordReset->delete();
return response(['message' =>'password has been successfully reset'], 200);
}
}
Step 3: Handel Mail and blade file
In SendCodeResetPassword.php
mail:
class SendCodeResetPassword extends Mailable implements ShouldQueue
{
use Queueable, SerializesModels;
public $code;
public function __construct($code)
{
$this->code = $code;
}
public function build()
{
return $this->markdown('emails.send-code-reset-password');
}
}
In send-code-reset-password.blade.php
@component('mail::message')
<h1>We have received your request to reset your account password</h1>
<p>You can use the following code to recover your account:</p>
@component('mail::panel')
{{ $code }}
@endcomponent
<p>The allowed duration of the code is one hour from the time the message was sent</p>
@endcomponent
Step 4: Create routes
In routes/api.php
create links for your controllers:
Route::post('password/email', ForgotPasswordController::class);
Route::post('password/code/check', CodeCheckController::class);
Route::post('password/reset', ResetPasswordController::class);
Now you can open Postman and try these links:
http://your-domain.com/api/password/email => Step 1
http://your-domain.com/api/password/code/check => Step 2
http://your-domain.com/api/password/reset => Step 3
Source code on Github
Top comments (28)
I got confused! We created 3 controllers at the beginning of the tutorial
But we are using only 2 of them, the ResetPasswordController controller is not being used
There will not be a name change in the tutorial. I say:
When referring to the 1st controller (CodeCheckController) wouldn't it be ResetPasswordController?
Can you help me with this question?
Try to check on his Github reporsitory, maybe he forgot to write the Step 3..
Thanks Muhammad
I'll see how you did
Yes, he hasn't updated the tutorial, if you follow it completely, you will definitely get an error.
Try to clone the repository from his Github, and follow the path of where he puts the files, where he writes the functions, I'm sure you'll understand completely..
I've had success implementing it, although there is some code that needs to be changed.
Want me to tell you here?
What I understand here is, the authentication process that Laravel has in the process on the Web, we can't fully utilize in the Mobile API process.
But we have to almost do it manually
Hey Muhammand
I already cloned the GitHub repository, but I couldn't solve this problem. Can you help me, because I've tried several ways to get the user and save the password, but I'm not getting it.
Is there any way to debug when we are using API so I can track what is happening?
As you can see in the image, I try to save the password but without success
I'm using this tutorial to learn about Laravel API, I'm new to Laravel. If you can help me I will be very grateful
Sorry for the late reply because I didn't check my email in a few days.
To understand how the authentication process provided by Laravel, let alone using the library they have prepared (in this case Sanctum), is a bit difficult.
If only for initial learning, and to understand how the authentication process provided by Laravel, you can try using a Laravel-made library called Laravel Breeze, you can read and try it here
Back in the initial discussion, if we use the library for the authentication process on the website, it will be very easy, because it is automatic and suitable.
But since we are using this library for processes on mobile, then we need to create some of the processes ourselves.
For some of the improvements I made, well... quite a lot, hehe.
I'll try to explain some basic things you should at least pay attention to and configure in the next comment
So, some of the things you can do are
A. Make sure the configuration in the .env is correct. If it is correct, when you run the
php artisan migrate
command, this command should successfully create the initial database in your database.B. Change the
route\api.php
code from the sample code. Because maybe I'm using the latest Laravel version 9, so this may be different from the example in the tutorial, but it can cause errors. Change it to like this:password/email
= url address to go to in API endpointForgotPasswordController
= name of your controller file__invoke
= the name of the method contained in the controller file (no matter what the name is)C. Fix the code logic contained in the
Models\ResetCodePassword.php
file, in the section:change to
D. Make sure that in each file that ends in
Requests\Auth\
, if there is anexists:users
code, make sure the name is the same as the one in your database. You can try to learn in Laravel ValidationWell maybe it's not very complete because it's going to be very long, but those are some of the crucial things I'm changing until the code from this tutorial works..
Keep trying, if it fails, try to browse for the error, you can do it
I hope this helps
@muhammadfaisal hey man, thanks for the explanation! I'm trying to implement, as @drummonddev. Tried to follow the github repo as well, and i'm stucked in one thing: The recovery code on the first endpoint came empty. I saw the
__invoke
method onForgotPasswordController
have the$codeData
calling aPasswordReset::create
function, but this function never existed on this tutorial or in the repo as well. Tried to build on my own, but without success. Can you help me with that?I've tried like this:
`public static function create($data)
{
$code = rand(100000, 999999);
}`
Okay you're welcome, happy to help.
First, what do you mean by
you mean the function doesn't generate the token code, right?
So to explain it better, maybe I'll try to help explain some of the functions of this code one by one through the flow, ok:
We access the function in our Controller via the Route that we have defined before, for example we have a Route like this:
Route::post('password/email', [ForgotPasswordController::class, '__invoke']);
, then you can access via Postman like thishttp://192.168.88.27:8000/api/password/email
(make sure you runipconfig
in cmd to find out the local ip and adjust the host url, if you are developing on a local machine)After we access the
__invoke
method in the Controller, here is an explanation of some of the code:ResetCodePassword::where('email', $request->email)->delete();
: To delete the record in thereset_code_passwords
db that you created viamigration
, so it doesn't duplicated$codeData = ResetCodePassword::create($request->data());
Serves to input token code toreset_code_passwords
db.::create()
is Laravel's Eloquent ORM default function, you can read about it here$request->data()
is obtained from the method for generating the token code in theForgotPasswordRequest.php
file, you can check it. It's just to randomize token numbersMail::to($request->email)->send(new SendCodeResetPassword($codeData->code));
: to send to email.SendCodeResetPassword()
functions to store the generatedcode from $codeData
, and send it to the view with the nameemails.send-code-reset-password
$codeData->code
, obtained from existing data in the database with columncode
..Hope you understand and this helps
@muhammadfaisal thanks for the fast reply! :) hope you have a great day!
about the recovery code, i mean, i've received the email, and also the data was recorded into the DB. But, the code recorded was null by some reason. I think i figured it out while i'm texting you (i'm using the default reset database table, so maybe the $code must be called as $token instead). I'll try and answer you asap! Thanks for clarify!
as i talked, it worked! :)
but the second endpoint (/password/code/check) throws me an error:
Call to a member function addHour() on string
.Its about this one - this addHour function doesnt exist?
if (now() > $this->created_at->addHour()) {
$this->delete();
return true;
}
i've casted the created_at as datetime in the model, and worked too! :)
You're welcome, I'm happy if I can help people.. :D
Ok, I see where the problem is now..
I think you should debug one by one the variable, to see the value you expect to get.
For example when I want to make sure that
isExpire()
method will return the right value and logic, first usually I will do these things:echo now();
echo $this->created_at;
echo $this->created_at->addHour();
Make sure these echoes print the right values.
Then make sure the logic works:
If current time is greater than the db time added 1 hour time, then it'll return true =
The time is expired.
Example:
Now: 18:00
Db time: 17:30
Added 1 hour: 18:30
Then it's not expired.
But
Now: 18:00
Db time: 16:55
Added 1 hour: 17:55
Then it's expired, the method should return true..
Yes, it worked after i've cast the created_at inside the PasswordReset model :) as the value are passed as string, it throws the error! But now, all the flow are working as expected! Thank you, for real!
Glad to hear it works, you're welcome 😃
Many thanks to the author for such a detailed and detailed review, and most importantly, the code in the repository on GitHub!
Thanks to you, it was possible to implement password reset and recovery for the Api very simply and quickly!
Hey Muhammad
I replicated the GitHub project as is and it's almost all working fine.
Only when I'm going to update the password that it's not working.
The password is not changed and the output is HTML in Postman.
To make sure I wasn't doing anything wrong, I cloned the GitHub project repository and the same thing happens.
Can you help me find where the problem is?
In Postman I passed the generated code and the new password.
Have you tried to add
Accept
and the value isapplication/json
in Postman Header section?I met this problem too, and to change the way it show the problem, this value we add to the Header can help
Hi muhammad,
I already passed this option in the header.
But so far I have not been able to solve it, whenever I try to change the password the result is the same. an html
Ok, let's solve it one by one to see where it's come from..
So, you're not succeed yet, in the 1st step?
Maybe you get an error that can lead us to the source of the problem?
If you curious, the html response you get, it is just a login page code of Laravel default view..
Try to copy paste it in html code viewer
We need to know what action you did that makes the html error come
Hi Muhammad
I got the system up and running!!! Couldn't do it without your tip on Accept and some changes to the GitHub codebase. I'll share the working project on GitHub, in case other people have difficulties.
github.com/Drummond-Dev/Laravel-AP...
Thanks for your help
A tip, check how the dates and times of the systems are configured, both in Laravel and MySQL server
Alright, glad to hear you succeed! :D
how to fix this?
Great Technique thanks
Your tutorial have a loto of errors, dude!
Amazing, thank's...