In this tutorial, we will learn how to securely reset a password by consuming Laravel JWT auth API with Angular.This is the second tutorial of Laravel JWT authentication, and preferably we will learn how to create a password reset functionality in Angular using Laravel REST API.
Reset password is ah act of cancelling the existing password for a specific account at a website, service, or device, and then generating a new one.
Tutorial Objective
- Create two laravel APIs, for making a reset password request other for resetting the password.
- Handle password reset request with the token, to restrain redundancy.
- Send password reset mail via mailtrap.io in laravel with a valid token.
- Handle laravel API through angular service with full-on consensus between backend and frontend.
Table of Contents
Clone Laravel Token-Based Authentication Repo
To comprehend or even start working on the pre-built repo, kindly clone the repo using the following command:
git clone https://github.com/SinghDigamber/laravel-angular-jwt-auth.git
The project simultaneously contains backend (Laravel) and frontend (Angular) folders, write your code accordingly.
A quick recap about the topics that we have covered in laravel authentication project.
- User login and signup in laravel and angular.
- Building secure laravel API for secure user authentication using JWT token.
- Password hashing to store the password securely.
- Consuming the laravel API in an angular app.
- Set JWT token in headers while logging in.
- Refresh token at a specific interval to add the security.
- Logout from laravel app.
To assimilate the entire authentication process, please read this whole article:
JWT Authentication in Laravel with Angular: User Authentication & Registration
Configure Mailtrap
To test the mails sending across the development environment, create the Mailtrap account.
Go to mailtrap dashboard, click on the small gear icon, copy the username and password from the SMTP Settings tab.
Then, open .env
and add the mailtrap details.
MAIL_MAILER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME= // mailtrap username
MAIL_PASSWORD= // mailtrap password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS= // email from address
If you are using MAMP local server in macOs; make sure to append UNIX_SOCKET and DB_SOCKET below database credentials in .env file.
UNIX_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock
DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock
Password Reset Request
Generically, we need to send password reset request thorough the valid email. An email which has already registered with existing account.
Create a controller in the backend folder to manage password reset request.
php artisan make:controller PasswordResetRequestController
Open PasswordResetRequestController.php and place the following code.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use App\Models\User;
use App\Mail\SendMail;
use Illuminate\Support\Facades\Mail;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class PasswordResetRequestController extends Controller {
public function sendPasswordResetEmail(Request $request){
// If email does not exist
if(!$this->validEmail($request->email)) {
return response()->json([
'message' => 'Email does not exist.'
], Response::HTTP_NOT_FOUND);
} else {
// If email exists
$this->sendMail($request->email);
return response()->json([
'message' => 'Check your inbox, we have sent a link to reset email.'
], Response::HTTP_OK);
}
}
public function sendMail($email){
$token = $this->generateToken($email);
Mail::to($email)->send(new SendMail($token));
}
public function validEmail($email) {
return !!User::where('email', $email)->first();
}
public function generateToken($email){
$isOtherToken = DB::table('recover_password')->where('email', $email)->first();
if($isOtherToken) {
return $isOtherToken->token;
}
$token = Str::random(80);;
$this->storeToken($token, $email);
return $token;
}
public function storeToken($token, $email){
DB::table('recover_password')->insert([
'email' => $email,
'token' => $token,
'created' => Carbon::now()
]);
}
}
To manage this scenario, we are looking for the old token of the previously made request, if found old token in the database then go for it otherwise create a new token for the new password update request.
Create the mailable class to create mail template and sending the mail.
php artisan make:mail SendMail --markdown=Email.resetPassword
Open App/Mail/SendMail.php and insert the given below code.
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class SendMail extends Mailable
{
use Queueable, SerializesModels;
public $token;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($token)
{
$this->token = $token;
}
/**
* Build the message.
*
* @return $this
*/
public function build(){
return $this->markdown('Email.resetPassword')->with([
'token' => $this->token
]);
}
}
Consume Password Reset Request API
Now, get inside the frontend/app/shared/auth.service.ts and add the given below code.
sendResetPasswordLink(data) {
return this.http.post('http://127.0.0.1:8000/api/auth/reset-password-request', data)
}
Next, we need an angular component which will process API and allow user to make the password reset request from frontend.
Create Reset Password Form
Run the following command to create the component.
ng g c components/change-password-request
Inside the frontend folder, add the following code in change-password-request.component.ts file.
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { AuthService } from '../../shared/auth.service';
@Component({
selector: 'app-change-password-request',
templateUrl: './change-password-request.component.html',
styleUrls: ['./change-password-request.component.scss']
})
export class ChangePasswordRequestComponent implements OnInit {
resetForm: FormGroup;
errors = null;
successMsg = null;
constructor(
public fb: FormBuilder,
public authService: AuthService
) {
this.resetForm = this.fb.group({
email: ['', [Validators.required, Validators.email]]
})
}
ngOnInit(): void { }
onSubmit(){
this.authService.sendResetPasswordLink(this.resetForm.value).subscribe(
(result) => {
this.successMsg = result;
},(error) => {
this.errors = error.error.message;
})
}
}
Place the following code in change-password-request.component.html file.
<div class="auth-wrapper">
<form class="form-signin" [formGroup]="resetForm" (ngSubmit)="onSubmit()">
<h3 class="h3 mb-3 font-weight-normal text-center">Reset Password</h3>
<p class="mb-4">Provide valid email id to reset your account's password.</p>
<!-- Error -->
<div *ngIf="errors != null" class="alert alert-danger mt-3">
{{ errors }}
</div>
<div *ngIf="successMsg != null" class="alert alert-primary mt-3">
{{ successMsg?.message }}
</div>
<!-- Login -->
<div class="form-group">
<label>Email</label>
<input type="email" class="form-control" formControlName="email">
</div>
<button type="submit" class="btn btn-block btn-primary" >
Reset Password
</button>
</form>
</div>
Create Password Reset Mail Template
In the backend folder, go to views/Email/resetPassword.blade.php and place the code. It creates the mailer template.
@component('mail::message')
# Reset Password
Reset or change your password.
@component('mail::button', ['url' => 'http://localhost:4200/change-password?token='.$token])
Change Password
@endcomponent
Thanks,<br>
{{ config('app.name') }}
@endcomponent
Laravel Password Reset Example
Create a laravel request helper for extracting the email and password from the API call.
php artisan make:request UpdatePasswordRequest
Set the authorize
to true and define email and password fields inside the backend folder’s
app/Http/Requests/UpdatePasswordRequest.php.
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePasswordRequest extends FormRequest {
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'email' => 'required|email',
'password' => 'required|confirmed'
];
}
}
Create change password controller inside the backend folder.
php artisan make:controller ChangePasswordController
Open ChangePasswordController.php and incorporate the following code.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests\UpdatePasswordRequest;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\DB;
use App\Models\User;
class ChangePasswordController extends Controller {
public function passwordResetProcess(UpdatePasswordRequest $request){
return $this->updatePasswordRow($request)->count() > 0 ? $this->resetPassword($request) : $this->tokenNotFoundError();
}
// Verify if token is valid
private function updatePasswordRow($request){
return DB::table('recover_password')->where([
'email' => $request->email,
'token' => $request->passwordToken
]);
}
// Token not found response
private function tokenNotFoundError() {
return response()->json([
'error' => 'Either your email or token is wrong.'
],Response::HTTP_UNPROCESSABLE_ENTITY);
}
// Reset password
private function resetPassword($request) {
// find email
$userData = User::whereEmail($request->email)->first();
// update password
$userData->update([
'password'=>bcrypt($request->password)
]);
// remove verification data from db
$this->updatePasswordRow($request)->delete();
// reset password response
return response()->json([
'data'=>'Password has been updated.'
],Response::HTTP_CREATED);
}
}
Inside backend directory, open resources/routes/api.php and add the following code.
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PasswordResetRequestController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::post('/reset-password-request', [PasswordResetRequestController::class, 'sendPasswordResetEmail']);
Route::post('/change-password', [PasswordResetRequestController::class, 'passwordResetProcess']);
Register Password Update API in Service
Now, generate the service file using below command:
ng g service shared/auth
Now, get inside the frontend/app/shared/auth.service.ts and add the given below code.
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class AuthService {
constructor(private http: HttpClient) {}
resetPassword(data: any) {
return this.http.post(
'http://127.0.0.1:8000/api/auth/change-password',
data
);
}
}
Create Password Update Form
Run the following command to create the component.
ng g c components/change-password
Inside the frontend folder, add the following code in change-password.component.ts file.
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { AuthService } from './../../shared/auth.service';
import { throwError } from 'rxjs';
@Component({
selector: 'app-change-password',
templateUrl: './change-password.component.html',
styleUrls: ['./change-password.component.scss'],
})
export class ChangePasswordComponent implements OnInit {
changePasswordForm: FormGroup;
errors = null;
constructor(
public fb: FormBuilder,
route: ActivatedRoute,
public authService: AuthService
) {
this.changePasswordForm = this.fb.group({
email: [''],
password: ['admin123'],
password_confirmation: ['admin123'],
passwordToken: [''],
});
route.queryParams.subscribe((params) => {
this.changePasswordForm.controls['passwordToken'].setValue(
params['token']
);
});
}
ngOnInit(): void {}
onSubmit() {
this.authService.resetPassword(this.changePasswordForm.value).subscribe(
(result) => {
alert('Password has been updated');
this.changePasswordForm.reset();
},
(error) => {
this.handleError(error);
}
);
}
handleError(error: any) {
let errorMessage = '';
if (error.error instanceof ErrorEvent) {
// client-side error
errorMessage = `Error: ${error.error.message}`;
} else {
// server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
console.log(errorMessage);
return throwError(() => {
errorMessage;
});
}
}
Inside the frontend folder, add the following code in change-password.component.html file.
<div class="auth-wrapper">
<form
class="form-signin"
[formGroup]="changePasswordForm"
(ngSubmit)="onSubmit()"
>
<h3 class="h3 mb-3 font-weight-normal text-center">Change Password</h3>
<!-- Errors -->
<div *ngIf="errors" class="alert alert-danger mt-3">
{{ errors }}
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" class="form-control" formControlName="email" />
</div>
<div class="form-group">
<label>New Password</label>
<input type="password" class="form-control" formControlName="password" />
</div>
<div class="form-group">
<label>Confirm New Password</label>
<input
type="password"
class="form-control"
formControlName="password_confirmation"
/>
</div>
<button type="submit" class="btn btn-block btn-primary">
Reset Password
</button>
</form>
</div>
Define Angular Routes
Finally, let us define the angular routes inside the app-routing.module.ts file.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { ChangePasswordComponent } from './components/change-password/change-password.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [AppComponent, ChangePasswordComponent],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Next, you have to open the app.module.ts file and import the FormsModule, ReactiveFormsModule, HttpClientModule, AppRoutingModule and ChangePasswordComponent.
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { ChangePasswordComponent } from './components/change-password/change-password.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [AppComponent, ChangePasswordComponent],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
Run Application
Start Laravel App
Start the local web server, get inside the laravel project folder and run the app.
cd backend && php artisan serve
Run Angular App
Head over to angular project folder and run the app.
cd frontend && ng serve --open
You can download the complete code of this tutorial on GitHub.