Resetting Firebase Auth Passwords in Angular (Email Based Accounts)

mainawycliffe

Maina Wycliffe

Posted on September 16, 2019

Resetting Firebase Auth Passwords in Angular (Email Based Accounts)

In this post, we are going to cover the following:

  1. In Firebase Auth, we will customize:

    • Password Reset Email Content/Message
    • Add a Custom Domain for use by email address
    • And Password Reset/Email Verification URL
  2. In Angular:

    • Send Password Reset Request Emails
    • Confirm Password Reset Code and Set New Password
    • Confirm Email Address

Prerequisite

  1. A Firebase Project – How to create a new Firebase Project.
  2. Create a new Angular App – How to Install and Create a new Angular App.
  3. Install and Setup @angular/fire (AngularFire2) – How to Install and Setup Firebase in Angular.

Firebase Auth

Enable Email/Password Verification

First, we are going to enable email password authentication.

  • In your Firebase project home page, go to Authentication, on the side nav under Develop menu item. And then, select the sign-in method tab.
    Alt Text

  • And then, under sign-in providers, under the Email/Password provider, hover to reveal the edit icon.
    Alt Text

  • And finally, click toggle enable switch and save the changes.
    Alt Text

Customize Email Address Domain

Next, let’s customize the email domain Firebase uses to send password reset requests and send user email verification requests to our users.

  • Under authentication, select the Templates tab.
    Alt Text

  • Then, under Email address Verification, click on the edit icon next to the from email address.
    Alt Text

  • And then, click on the customize domain link.
    Alt Text

  • After that, you will be prompted for the domain name, enter the name and click next.
    Alt Text

  • Here, you will be prompted to verify your domain name. This is done by adding the DNS records provided by firebase on the verification window to your domains DNS records. This process varies for different domain registrars but it’s also straightforward.

  • Once you have added the DNS records, click on the verify button. This can take time to verify the records – up to 48 hours on rare occasions – so don’t panic. Once the verification is done, all email sent by Firebase auth will use your domain name.
    Alt Text

Customize the Password Reset Action URL

Next, let's customize the password reset URL. By default, Firebase provides a preset URL with a generic UI that handles password resets. We are going to change this so that we can point it to our angular app which will handle confirmation and setting of the new password. This will be configured under the Templates Tab in Firebase Authentication.

  • On the sidebar, select Password Reset and scroll to the bottom.
    Alt Text

  • Click on the Customize Action URL link, below the Action URL Text Field and Just Above the Save Button.
    Alt Text

  • A modal window titled Action URL will pop up, prompting you to enter a Custom Action URL. Enter a Custom Action URL of your choice – something like http://localhost:4200/auth/email/action and save.
    Alt Text

NB: Keep in mind that both Password Reset, Email address change and Verification will share the same Action URL. A query parameter mode, alongside the oobCode parameter is appended to the action URL. The mode parameter holds the action type the user is performing i.e. password reset etc.

Customize Password Reset Message

This is not entirely necessary, but you might want to have a custom message for your app’s users. From the same page as above, you should see the message field, you can customize that by using HTML. Use the following placeholder strings, to inject dynamic data into the template.

Alt Text

  • %DISPLAY_NAME% - The recipient's display name.

  • %APP_NAME% - The name of your app. You can set this value by editing the Public-facing name field on the Settings page.

  • %LINK% - The URL that the recipient must visit to complete the account management task. See Customize the action link URL.

  • %EMAIL% - The recipient's email address.

  • %NEW_EMAIL% - The new email address to set as the recipient's primary address. Used only in the Email Address Change template.

NB: You can also customize the Subject of the email too on the same page.

Angular Application

Assuming you already have created an Angular App and installed @angular/fire – check the prerequisite section above if you haven’t – let’s dive into sending a password reset request.

Sending Password Resets Requests Component

This component will collect user email addresses and request Firebase to send a password request if the email exists. Firebase authentication servers are going to determine is an Email exists or not.

We are going to start by creating a Reactive Form, with one FormControl. First, we are going to inject both FormBuilder and AngularFireAuth services into our component.

constructor(private afAuth: AngularFireAuth, private fb: FormBuilder) {}
Enter fullscreen mode Exit fullscreen mode

And then, we are going to declare frmResetPassword property of type FormGroup, then set it to a new instance of FormGroup, with one form field named email. The form control is going to have validators for both required and obviously email.

frmPasswordReset: FormGroup = this.fb.group({
  email: [null, [Validators.required, Validators.email]]
});
Enter fullscreen mode Exit fullscreen mode

Next, let's add a method to be called on from submit, which is going to send a request to Firebase to Send A Password Reset Request to the provided email if it exists.

const email = this.frmPasswordReset.controls['email'].value;

this.afAuth.auth.sendPasswordResetEmail(email).then(
  () => {
    // success, show some message
  },
  err => {
    // handle errors
  }
);
Enter fullscreen mode Exit fullscreen mode

When the above process completes successfully, an email address is sent to the user with the password reset link.

To handle errors, please check for a code property in the returned error. You can find a list of error codes for Firebase Auth here. I have attached a simple Firebase error parser at the bottom of this post.

And finally, here is the template for this component:

<form [formGroup]="frmPasswordReset" (submit)="sendPasswordResetRequest()">
  <div class="field has-text-left">
    <label class="label">Email Address: </label>
    <div class="control has-icons-left">
     <input formControlName="email" class="input is-focused" type="email">
      <span class="icon is-small is-left">
        <fa-icon [icon]="faEnvelope"></fa-icon>
      </span>
    </div>

    <div class="has-text-danger">
      Email is required!
    </div>

    <div class="has-text-danger" >
      A valid email is required!
    </div>
  </div>

  <div class="field">
    <button [disabled]="frmPasswordReset.invalid" class="button is-block is-primary has-text-white is-fullwidth ">
      <fa-icon [icon]="faSigninIcon"></fa-icon>
      Send Request
    </button>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

Confirming Email and Setting a New Password for the User

The password reset link and email confirmation/verification link are the same, with the only variants being oobCode and mode. The mode will determine whether a user is resetting a password or confirming their email address. It can either be resetPassword or verifyEmail.

This means that these two actions are going to be redirected to the same route in our Angular Application. As such, we are going to need 3 components – One for switching between the modes, one for resetting password and another for confirming/verifying email addresses.

Switching Between Reset Password and Verify Email Address

Let’s start with the Component for switching between Reset Password and Confirm Email Address. We will start by injecting ActivatedRoute service into our Component.

constructor(private activatedActivated: ActivatedRoute) {}
Enter fullscreen mode Exit fullscreen mode

And then, let's declare a property named mode and assign it the value of Query Parameter mode from the URL.

mode = this.activatedActivated.snapshot.queryParams['mode'];
Enter fullscreen mode Exit fullscreen mode

And finally, in our template, we can use the value of the mode property to either display the Reset Password Component or the Verify Email Address Component using ng-switch.

<ng-container [ngSwitch]="action">
  <!-- password reset -->
  <ng-container *ngSwitchCase="'resetPassword'">
    <app-confirm-password-reset></app-confirm-password-reset>
  </ng-container>

  <!-- verify email address -->
  <ng-container *ngSwitchCase="'verifyEmail'">
    <app-confirm-email-address></app-confirm-email-address>
  </ng-container>

  <!-- default action -->
  <ng-container *ngSwitchDefault>
    <!—show an error message -->
  </ng-container>
</ng-container>
Enter fullscreen mode Exit fullscreen mode

Confirm Password Reset Code Component

In this component, we will do two things, first is to request the user to provide enter a new password. And then we will use this password and oobCode to save the new password. If the oobCode is valid, the password will be updated successfully, otherwise, it will fail.

We will start by injecting FormBuilder, ActivatedRoute, Router and AngularFireAuth services into our Component.

constructor(private afAuth: AngularFireAuth, private fb: FormBuilder, private route: ActivatedRoute, private router: Router) {}
Enter fullscreen mode Exit fullscreen mode

And then we will declare a FormGroup property named frmSetNewPassword, with new and confirm password form fields:

frmSetNewPassword = this.fb.group({
  password: [null, [Validators.required]],
  confirmPassword: [null, [Validators.required]]
});
Enter fullscreen mode Exit fullscreen mode

And finally, inside our submit method, we will check if the two passwords match:

const password = this.frmSetNewPassword.controls['password'].value;
const confirmPassword = this.frmSetNewPassword.controls['confirmPassword'].value;

if (password !== confirmPassword) {
  // react to error
  return;
}
Enter fullscreen mode Exit fullscreen mode

And then get the oobCode from the URL, using the ActivateRoute service.

const code = this.route.snapshot.queryParams['oobCode'];
Enter fullscreen mode Exit fullscreen mode

And then finally send call the confirmPasswordReset, passing both the code and new password to update the users password.

this.afAuth.auth
  .confirmPasswordReset(code, password)
  .then(() => this.router.navigate(['signin']))
  .catch(err => {
   const errorMessage = FirebaseErrors.Parse(err.code); // check this helper class at the bottom
  });
Enter fullscreen mode Exit fullscreen mode

If successful, we will redirect the user to the sign-in page where they can sign in with their new password, otherwise we will show the error.

And here is the template for the above form:

<form [formGroup]="frmSetNewPassword" (ngSubmit)="setPassword()">
  <h3 class="title is-5 has-text-black">
    Set a new password
  </h3>
  <h4 class="subtitle is-6 has-text-grey">Please enter and confirm your new password</h4>
  <div class="field">
    <label class="label">Password: </label>
    <div class="control">
      <input class="input " type="password" name="password" formControlName="password">
    </div>
  </div>
  <div class="field">
    <label class="label">Confirm Password: </label>
    <div class="control">
      <input class="input " type="password" name="confirmPassword" formControlName="confirmPassword">
     <div class="has-text-danger" >
        Confirm password is required!
      </div>
    </div>
  </div>

  <div class="field">
    <div class="control">
      <button class="button is-fullwidth is-primary" type="submit" [disabled]="!frmSetNewPassword.valid">
        <span class="icon">
          <fa-icon [icon]="saveIcon"></fa-icon>
        </span>
        <span>Reset</span>
      </button>
    </div>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

Verify Email Address

Verifying email addresses is out of the scope for this post, so I won’t go into details. Here is the code snippet to verify user email address:

const code = this.activateRoute.snapshot.queryParams['oobCode'];

this.afAuth.auth
  .applyActionCode(code)
  .then(() => {
    // do something after successful verification
  })
  .catch(err => {
    // show error message
  });
Enter fullscreen mode Exit fullscreen mode

Remember to inject both AngularFireAuth and ActivatedRoute services:

constructor(private afAuth: AngularFireAuth, private activateRoute: ActivatedRoute) {}
Enter fullscreen mode Exit fullscreen mode

NB: If you don’t require any user action, you can run the above code OnInit and show a loading animation.

Configure Router

And finally, all that is remaining is to configure our Angular Router:

const routes: Routes = [
  {
    path: 'auth',
    children: [
      {
        //... Auth Guards For UnAuthenticated Users Here
        children: [
         // ...
          {
            path: 'forgot-password',
            component: PasswordResetRequestComponent,
            data: { title: 'Forgot Password' }
          }
        ]
      },
      {
        path: 'email/action',
        component: EmailConfirmationComponent,
        data: { title: 'Confirm Email Address' }
      }
    ]
  }
];
Enter fullscreen mode Exit fullscreen mode

Bonus - Firebase Auth Errors Parser

Here is a simple helper method for checking errors returned by Firebase Auth:

export class FirebaseErrors {
  static Parse(errorCode: string): string {

    let message: string;

    switch (errorCode) {
      case 'auth/wrong-password':
        message = 'Invalid login credentials.';
        break;
      case 'auth/network-request-failed':
        message = 'Please check your internet connection';
        break;
      case 'auth/too-many-requests':
        message =
          'We have detected too many requests from your device. Take a break please!';
        break;
      case 'auth/user-disabled':
        message =
          'Your account has been disabled or deleted. Please contact the system administrator.';
        break;
      case 'auth/requires-recent-login':
        message = 'Please login again and try again!';
        break;
      case 'auth/email-already-exists':
        message = 'Email address is already in use by an existing user.';
        break;
      case 'auth/user-not-found':
        message =
          'We could not find user account associated with the email address or phone number.';
        break;
      case 'auth/phone-number-already-exists':
        message = 'The phone number is already in use by an existing user.';
        break;
      case 'auth/invalid-phone-number':
        message = 'The phone number is not a valid phone number!';
        break;
      case 'auth/invalid-email  ':
        message = 'The email address is not a valid email address!';
        break;
      case 'auth/cannot-delete-own-user-account':
        message = 'You cannot delete your own user account.';
        break;
       default:
        message = 'Oops! Something went wrong. Try again later.';
        break;
    }

    return message;
  }
}
Enter fullscreen mode Exit fullscreen mode
💖 💪 🙅 🚩
mainawycliffe
Maina Wycliffe

Posted on September 16, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related