Implementing Password Reset with Firebase and Angular

Implementing Password Reset can be a tricky but inevitable task. Luckily Firebase gives us the tools to easily control this process. This article demonstrates how to implement password reset using Firebase and a dedicated page inside of your Angular app.

NOTE: This post assumes you have already implemented Login on your site and have created an account and are using AngularFire2.

Create the component

Because we can use the same page for other management purposes, create a component called UserManagementComponent and add the component to a Router within your angular app. Below we added it to the Auth module so the route would be `/auth/userMgmt`

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 { UserManagementComponent } from './components/user-management/user-management.component';
const routes: Routes = [ 
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{ path: 'login', component: LoginComponent },
{ path: 'register', component: RegisterComponent },
{ path: 'userMgmt', component: UserManagementComponent }
];
@NgModule({ 
imports: [RouterModule.forChild(routes)],
exports: [],
declarations: []
})
export class AuthRoutingModule { }

Firebase

After you add this and know what route UserManagementComponent is on, open your project in Firebase and go to Authentication -> Templates -> Password Reset. This is where you can customize the message users will receive in their email and, more importantly, the link that they need to visit to reset their password.

Click the pencil to edit the information then click the blue link at the bottom to customize action URL. For now, we will point to localhost for testing. When you release your site you’ll need to change this to your site URL. You can also use this page to customize the message your users will receive in their password reset email.

Auth Service

Now that we have everything set up in Firebase, let’s go back to our project. Let’s start out by creating the auth functions you will need from AngularFireAuth. If you don’t already have a service set up for auth, you should create one and inject AngularFireAuth into it. Here is an example containing the functions that we will need.

import { Injectable } from '@angular/core'; 
import { AngularFireAuth } from "@angular/fire/auth";
import * as firebase from "firebase";
@Injectable({ 
providedIn: 'root'
})
export class AuthService {
  constructor(private afAuth: AngularFireAuth) { } 
  getAuth() { 
return this.afAuth.auth;
}
  /** 
* Initiate the password reset process for this user
* @param email email of the user
*/
resetPasswordInit(email: string) {
return this.afAuth.auth.sendPasswordResetEmail(
email,
{ url: 'http://localhost:4200/auth' });
}
  ...other authentication functions 
}

As you can see there are only 2 functions that we need. The getAuth function simply returns the auth API that AngularFireAuth wraps.

The second function sends the password reset request to Firebase which will construct the email to that we configured in the dashboard to send to the user. We provide the email along with an ActionCodeSettings object.

The URL that is passed is called the continue URL and is the location the user should return to after successfully changing their password. This mainly applies if you are using firebase’s default password reset page or a page at a URL that is different from where your app is located. Because we configured our password reset email to take us to our own custom page, this URL is not needed and you can safely remove the second parameter from sendPasswordResetEmail.

Login Component

Now with our AuthService injected into our LoginComponent, create a Reset Password button that initiates the password reset process when clicked.

resetPassword() { 
if (!this.email) {
alert('Type in your email first');
}
  this.auth.resetPasswordInit(this.email) 
.then(
() => alert('A password reset link has been sent to your email
address'),
(rejectionReason) => alert(rejectionReason))
.catch(e => alert('An error occurred while attempting to reset
your password'));
}

Now when you enter an email and click the button, you should see an alert like this.

User Management Component

Finally, let’s shift focus to the most important part of the process, the UserManagementComponent we created. We named this component UserManagementComponent instead of PasswordResetComponent because we can handle email recovery and verification in a similar way. You will see some blank code in place for these features in case you need to implement them in the future.

At this point in the process, the user has received an email with a password reset link. When they click the link, Firebase will forward them to the URL we provided and append a few query parameters to the end of it. Some of them are for our conveniences, such as apiKey, continueUrl, and lang. We aren’t going to be using those, so, for now, we can ignore them.

The other 2 query parameters are the ones we absolutely need.

  • mode — The type of management action being performed. Can be either resetPassword, recoverEmail, or verifyEmail
  • oobCode — A one-time code, used to identify and verify a request

The first thing we want to do is inject our AuthService, Router, and ActivatedRoute into our project. Then when the component is initialized, we check for the parameters we need and decide what action is being requested. If it is password rest, then we verify the action code with Firebase and set actionCodeChecked to true. This will enable us to display the correct template layout.

constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private authService: AuthService
) { }

ngOnInit() {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(params => {
// if we didn't receive any parameters,
// we can't do anything
if (!params) this.router.navigate(['/home']);

this.mode = params['mode'];
this.actionCode = params['oobCode'];

switch (params['mode']) {
case UserManagementActions.resetPassword: {
// Verify the password reset code is valid.
this.authService.getAuth().verifyPasswordResetCode(this.actionCode).then(email => {
this.actionCodeChecked = true;
}).catch(e => {
// Invalid or expired action code. Ask user to try to reset the password
// again.
alert(e);
this.router.navigate(['/auth/login']);
});
} break
case UserManagementActions.recoverEmail: {

} break
case UserManagementActions.verifyEmail: {

} break
default: {
console.log('query parameters are missing');
this.router.navigate(['/auth/login']);
}
}
})
}

With the auth code checked, we can now display the form that corresponds to the action being performed. In our case, we will show a password reset form.

<ng-container *ngIf="actionCodeChecked" [ngSwitch]="mode"> 
<!-- Reset Password --> 
<ng-container *ngSwitchCase="'resetPassword'">
<input [(ngModel)]="oldPassword" type="text" id="oldPassword"
name="oldPassword" placeholder="Old Password"> <br>
<input [(ngModel)]="newPassword" type="text" id="newPassword"
name="newPassword" placeholder="New Password"> <br>
<input [(ngModel)]="confirmPassword" type="text"
id="confirmPassword" name="confirmPassword" placeholder="Confirm
Password">
<button (click)="handleResetPassword()">Confirm</button>
</ng-container>
  <!-- Recover Email --> 
<ng-container *ngSwitchCase="'recoverEmail'">
</ng-container>

<!-- Verify Email -->
<ng-container *ngSwitchCase="'verifyEmail'">
</ng-container>
</ng-container>

Once the user has completed the form, we can verify the information and reset the user’s password.

/** 
* Attempt to confirm the password reset with firebase and
* navigate user back to home.
*/
handleResetPassword() {
if (this.newPassword != this.confirmPassword) {
alert('New Password and Confirm Password do not match');
return;
}
  // Save the new password.   
this.authService.getAuth().confirmPasswordReset(
this.actionCode,
this.newPassword
).then(resp => {
// Password reset has been confirmed and new password updated.
alert('New password has been saved');
this.router.navigate(['/auth/login']); }).catch(e => {
// Error occurred during confirmation. The code might have
// expired or the password is too weak. alert(e);
});
}

Here is the full code for UserManagementComponent

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { UserManagementActions } from '../../../services/enums';
import { AuthService } from '../../../services/auth/auth.service';

@Component({
selector: 'app-user-management',
templateUrl: './user-management.component.html',
styleUrls: ['./user-management.component.css']
})
export class UserManagementComponent implements OnInit, OnDestroy {

ngUnsubscribe: Subject<any> = new Subject<any>();
actions = UserManagementActions;

// The user management actoin to be completed
mode: string;
// Just a code Firebase uses to prove that
// this is a real password reset.
actionCode: string;

oldPassword: string;
newPassword: string;
confirmPassword: string;

actionCodeChecked: boolean;

constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private authService: AuthService
) { }

ngOnInit() {
this.activatedRoute.queryParams
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(params => {
// if we didn't receive any parameters,
// we can't do anything
if (!params) this.router.navigate(['/home']);

this.mode = params['mode'];
this.actionCode = params['oobCode'];

switch (params['mode']) {
case UserManagementActions.resetPassword: {
// Verify the password reset code is valid.
this.authService
.getAuth()
.verifyPasswordResetCode(this.actionCode)
.then(email => {
this.actionCodeChecked = true;
}).catch(e => {
// Invalid or expired action code. Ask user to try to
// reset the password again.
alert(e);
this.router.navigate(['/auth/login']);
});
} break
case UserManagementActions.recoverEmail: {

} break
case UserManagementActions.verifyEmail: {

} break
default: {
console.log('query parameters are missing');
this.router.navigate(['/auth/login']);
}
}
})
}

ngOnDestroy() {
// End all subscriptions listening to ngUnsubscribe
// to avoid memory leaks.
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
}

/**
* Attempt to confirm the password reset with firebase and
* navigate user back to home.
*/
handleResetPassword() {
if (this.newPassword != this.confirmPassword) {
alert('New Password and Confirm Password do not match');
return;
}
// Save the new password.
this.authService.getAuth().confirmPasswordReset(
this.actionCode,
this.newPassword
)
.then(resp => {
// Password reset has been confirmed and new password updated.
alert('New password has been saved');
this.router.navigate(['/auth/login']);
}).catch(e => {
// Error occurred during confirmation. The code might have
// expired or the password is too weak.
alert(e);
});
}

}

Check Us Out

We regularly host meetups about the technology we like to use, so stop by our Meetup page or Eventbrite and sign up. If you’re interested in talking or hearing about a topic, let us know!

Meetup

Eventbrite

Blast From The Past

We have blog posts for other past meetups. Read them!

Text to Speech and Speech to Text in the Ionic Framework

Easy Virtual Reality with A-Frame

Getting Started with Unit Testing in Ionic 2+ Apps

NgRx State Management with Inclusive Innovation Incubator