Angular and Laravel 7|8 Password Reset for JWT Tutorial

Last updated on by Digamber
In this tutorial, we will learn how to securely reset a password by consuming Laravel 7 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 7 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.

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

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\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
        ]);        
    }
}

Define API Route

Inside the backend folder, head over to resources/routes/api.php and add the following code to create the Laravel API for handling the password reset request.

Route::post('reset-password-request', 'PasswordResetRequestController@sendPasswordResetEmail');

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

Reset Password Request in Laravel

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\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.

Route::post('change-password', 'ChangePasswordController@passwordResetProcess');

Register Password Update API in Service

Now, get inside the frontend/app/shared/auth.service.ts and add the given below code.

resetPassword(data) {
  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) {
      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?.error" class="alert alert-danger mt-3">
          {{ errors?.error }}
      </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 { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { SigninComponent } from './components/signin/signin.component';
import { SignupComponent } from './components/signup/signup.component';
import { UserProfileComponent } from './components/user-profile/user-profile.component';
import { ChangePasswordRequestComponent } from './components/change-password-request/change-password-request.component';
import { ChangePasswordComponent } from './components/change-password/change-password.component';

const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  { path: 'login', component: SigninComponent },
  { path: 'register', component: SignupComponent },
  { path: 'profile', component: UserProfileComponent },
  
  { path: 'reset-password', component: ChangePasswordRequestComponent },
  { path: 'change-password', component: ChangePasswordComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

export class AppRoutingModule { }

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.