Laravel 8 Angular JWT Password Reset with Mailtrap Example

Throughout this comprehensive tutorial, you will learn how to reset a forgotten password manually in the Laravel 8 JWT Angular auth application.

Resetting passwords is a foundational feature in every application; almost every web application offers its users a way to reset the forgotten passwords. Laravel offers a no-nonsense way to reset forgotten passwords; further, this can be done by sending a password reset mail link with the help of Mailtrap to the registered users’ email to reset the password securely.

Throughout this tutorial, you will learn how to create a reset password email template in laravel, how to generate a password reset token, equally important, how to send a reset password link on a user’s email in laravel angular from scratch.

Clone Laravel JWT Auth Project

In the previous tutorial, we understood the entire concept on how to create JWT in the Laravel 8 Angular app; we will continue from the same spot we left previously. This time, we will focus on creating a laravel forgot password api.

git clone https://github.com/remotestack377/laravel-angular-jwt-auth.git

After cloning the project, move inside the project then also get into the backend folder:

cd backend

Start the server, and you can use either MAMP or XAMPP.

Run the following commands one after another to install required dependencies, run migrations, to generate key over and above that to start the laravel development server:

composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan serve

Read more: Create Token-Based JWT Authentication in Laravel Angular

Setting Up Mailtrap

Follow the steps to get the mailtrap credentials:

  • Create an account at Mailtrap
  • Signin to your account
  • Create inbox project
  • Create on the settings icon
  • Go to the SMTP/POP3 section
  • Choose Laravel from the integrations dropdown
  • Copy the Laravel mailer details

Open and update the mailtrap credentials in .env:

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= // sender email
MAIL_FROM_NAME="${APP_NAME}"

Create Request Helper

Now, you need to create a request helper using the make request command, open the terminal, and run the below command:

php artisan make:request RequestHelper

Next up, turn return true in authorize() function, also set email and password props in app/Http/Requests/RequestHelper.php:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RequestHelper 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 Controllers

In this step you have to create two controllers in backend project:

php artisan make:controller ResetPwdReqController

Update below code in app/Http/Controllers/ResetPwdReqController.php:

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;

use Carbon\Carbon;
use App\Models\User;
use App\Mail\SendMail;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Symfony\Component\HttpFoundation\Response;

class ResetPwdReqController extends Controller
{
    public function reqForgotPassword(Request $request){
        if(!$this->validEmail($request->email)) {
            return response()->json([
                'message' => 'Email not found.'
            ], Response::HTTP_NOT_FOUND);
        } else {
            $this->sendEmail($request->email);
            return response()->json([
                'message' => 'Password reset mail has been sent.'
            ], Response::HTTP_OK);            
        }
    }


    public function sendEmail($email){
        $token = $this->createToken($email);
        Mail::to($email)->send(new SendMail($token));
    }

    public function validEmail($email) {
       return !!User::where('email', $email)->first();
    }

    public function createToken($email){
      $isToken = DB::table('password_resets')->where('email', $email)->first();

      if($isToken) {
        return $isToken->token;
      }

      $token = Str::random(80);;
      $this->saveToken($token, $email);
      return $token;
    }

    public function saveToken($token, $email){
        DB::table('password_resets')->insert([
            'email' => $email,
            'token' => $token,
            'created_at' => Carbon::now()            
        ]);
    }
}

Create another controller for password change:

php artisan make:controller UpdatePwdController

Update code in app/Http/Controllers/UpdatePwdController.php:

<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;

use App\Models\User;
use Illuminate\Support\Facades\DB;
use App\Http\Requests\RequestHelper;
use Symfony\Component\HttpFoundation\Response;


class UpdatePwdController extends Controller
{
    public function updatePassword(RequestHelper $request){
        return $this->validateToken($request)->count() > 0 ? $this->changePassword($request) : $this->noToken();
    }

    private function validateToken($request){
        return DB::table('password_resets')->where([
            'email' => $request->email,
            'token' => $request->passwordToken
        ]);
    }

    private function noToken() {
        return response()->json([
          'error' => 'Email or token does not exist.'
        ],Response::HTTP_UNPROCESSABLE_ENTITY);
    }

    private function changePassword($request) {
        $user = User::whereEmail($request->email)->first();
        $user->update([
          'password'=>bcrypt($request->password)
        ]);
        $this->validateToken($request)->delete();
        return response()->json([
          'data' => 'Password changed successfully.'
        ],Response::HTTP_CREATED);
    }  
}

Create Routes

Head over to resources/routes/api.php and define routes:

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

use App\Http\Controllers\JwtAuthController;
use App\Http\Controllers\ResetPwdReqController;
use App\Http\Controllers\UpdatePwdController;


/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
*/

Route::group([
    'middleware' => 'api',
    'prefix' => 'auth'
], function ($router) {
    Route::post('/signup', [JwtAuthController::class, 'register']);
    Route::post('/signin', [JwtAuthController::class, 'login']);
    Route::get('/user', [JwtAuthController::class, 'user']);
    Route::post('/token-refresh', [JwtAuthController::class, 'refresh']);
    Route::post('/signout', [JwtAuthController::class, 'signout']);

    Route::post('/req-password-reset', [ResetPwdReqController::class, 'reqForgotPassword']);
    Route::post('/update-password', [UpdatePwdController::class, 'updatePassword']);
});

Create Mail Template

You have to create a password reset mail template, which will be sent through the mail to the registered user’s email id:

php artisan make:mail SendMail --markdown=Email.forgotPassword

Update below code in App/Mail/SendMail.php:

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

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.forgotPassword')->with([
            'token' => $this->token
        ]);
    }
}

Further, you need to go to email template view file, and update the following code in app/resources/views/Email/forgotPassword.blade.php file:

@component('mail::message')
# Change Password

@component('mail::button', ['url' => 'http://localhost:4200/update-password?token='.$token])
Reset Password
@endcomponent

Thanks,<br>
{{ config('app.name') }}
@endcomponent

Make Password Reset Request

In this step you have to create reset password component:

ng g c components/reset-password

Update code in reset-password.component.ts:

import { Component, OnInit } from '@angular/core';
import { JwtService } from '../../shared/jwt.service';
import { FormBuilder, FormGroup, Validators } from "@angular/forms";

@Component({
  selector: 'app-reset-password',
  templateUrl: './reset-password.component.html',
  styleUrls: ['./reset-password.component.scss']
})

export class ResetPasswordComponent implements OnInit {

  myForm: FormGroup;
  err = null;
  msg = null;

  constructor(
    public fb: FormBuilder,
    public jwtService: JwtService
  ) {
    this.myForm = this.fb.group({
      email: ['', [Validators.required, Validators.email]]
    })
  }

  ngOnInit(): void { }

  onSubmit(){
    this.jwtService.reqPasswordReset(this.myForm.value).subscribe(
      (res) => {
        this.msg = res;
      },(error) => {
        this.err = error.error.message;
      })
  }

}

Update code in reset-password.component.html:

<div class="container">
  <form [formGroup]="myForm" (ngSubmit)="onSubmit()">

    <h3>Reset Password</h3>

    <div *ngIf="err != null" class="alert alert-danger mt-2">
      {{ err }}
    </div>

    <div *ngIf="msg != null" class="alert alert-success mt-2">
      {{ msg?.message }}
    </div>

    <div class="form-group">
      <label>Email</label>
      <input type="email" class="form-control" formControlName="email">
    </div>

    <button type="submit" class="btn btn-primary">
      Reset My Password
    </button>

  </form>
</div>

Set Up New Password

Next, you have to create the new password angular component, so use command to generate a new component:

ng g c components/update-password

Update below code in update-password.component.ts file:

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from "@angular/forms";
import { throwError } from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { JwtService } from '../../shared/jwt.service';

@Component({
  selector: 'app-update-password',
  templateUrl: './update-password.component.html',
  styleUrls: ['./update-password.component.scss']
})

export class UpdatePasswordComponent implements OnInit {

  updatePwd: FormGroup;
  errors = null;

  constructor(
    public fb: FormBuilder,
    public activatedRoute: ActivatedRoute,
    public authService: JwtService,
  ) {
    this.updatePwd = this.fb.group({
      email: [''],
      password: [''],
      password_confirmation: [''],
      passwordToken: ['']
    })
    activatedRoute.queryParams.subscribe((params) => {
      this.updatePwd.controls['passwordToken'].setValue(params['token']);
    })
  }

  ngOnInit(): void { }

  onSubmit(){
    this.authService.updatePassword(this.updatePwd.value).subscribe(
      result => {
        alert('Password updated successfully');
        console.log(this.updatePwd.value)
        this.updatePwd.reset();
      },
      error => {
        this.handleError(error);
      }
    );
  }

  handleError(error) {
      let errorMsg = '';
      if (error.error instanceof ErrorEvent) {
          errorMsg = `Error: ${error.error.message}`;
      } else {
          errorMsg = `Error Code: ${error.status}\nMessage: ${error.message}`;
      }
      return throwError(errorMsg);
  }

}

Update code in update-password.component.html file:

<div class="container">
  <form [formGroup]="updatePwd" (ngSubmit)="onSubmit()">
    <h3>Change Password</h3>

    <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 Password</label>
      <input type="password" class="form-control" formControlName="password_confirmation">
    </div>

    <button type="submit" class="btn btn-success">Update Password</button>
  </form>
</div>

You have to define two methods in angular project’s app/shared/jwt.service.ts file so open the file and update the following code:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export class User {
  name: String;
  email: String;
  password: String;
  password_confirmation: String
}

@Injectable({
  providedIn: 'root'
})

export class JwtService {

  constructor(private http: HttpClient) { }

  signUp(user: User): Observable<any> {
    return this.http.post('http://127.0.0.1:8000/api/auth/signup', user);
  }

  logIn(user: User): Observable<any> {
    return this.http.post<any>('http://127.0.0.1:8000/api/auth/signin', user);
  }

  profile(): Observable<any> {
    return this.http.get('http://127.0.0.1:8000/api/auth/user');
  }

  // req-password-reset
  reqPasswordReset(data) {
    return this.http.post('http://127.0.0.1:8000/api/auth/req-password-reset', data)
  }  

  // update password
  updatePassword(data) {
    return this.http.post('http://127.0.0.1:8000/api/auth/update-password', data)
  }

}

Create Angular Routes

Lastly, add components and create password reset routes in app-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { LoginComponent } from './components/login/login.component';
import { RegisterComponent } from './components/register/register.component';
import { ProfileComponent } from './components/profile/profile.component';

import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { UpdatePasswordComponent } from './components/update-password/update-password.component';

const routes: Routes = [
  { path: '', redirectTo: '/signin', pathMatch: 'full' },
  { path: 'signin', component: LoginComponent },
  { path: 'signup', component: RegisterComponent },
  { path: 'user-profile', component: ProfileComponent },
  
  { path: 'reset-password', component: ResetPasswordComponent },
  { path: 'update-password', component: UpdatePasswordComponent }  
];

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

export class AppRoutingModule { }

Run Angular Project
Open another terminal and execute the commands:

cd frontend && ng serve --open

You can use the below URL to reset the password:

http://localhost:4200/reset-password

Conclusion

The Laravel Angular password reset tutorial is over. This tutorial has revealed the step by step process to manually create a custom forgot password feature with JWT and Mailtrap in the laravel angular application.

We tried to explain everything related to topic in this tutorial nevertheless if you miss something we would suggest you should download the code from GitHub.