In this post, we are going to cover the following:
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
In Angular:
- Send Password Reset Request Emails
- Confirm Password Reset Code and Set New Password
- Confirm Email Address
Prerequisite
- A Firebase Project – How to create a new Firebase Project.
- Create a new Angular App – How to Install and Create a new Angular App.
- 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.And then, under sign-in providers, under the
Email/Password
provider, hover to reveal the edit icon.And finally, click toggle enable switch and save the changes.
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.Then, under
Email address Verification
, click on the edit icon next to thefrom
email address.And then, click on the
customize domain
link.After that, you will be prompted for the domain name, enter the name and click next.
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.
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.Click on the
Customize Action URL
link, below the Action URL Text Field and Just Above the Save Button.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.
NB: Keep in mind that both Password Reset, Email address change and Verification will share the same Action URL. A query parameter
mode
, alongside theoobCode
parameter is appended to the action URL. Themode
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.
%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) {}
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]]
});
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
}
);
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>
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) {}
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'];
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>
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) {}
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]]
});
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;
}
And then get the oobCode
from the URL, using the ActivateRoute
service.
const code = this.route.snapshot.queryParams['oobCode'];
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
});
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>
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
});
Remember to inject both AngularFireAuth
and ActivatedRoute
services:
constructor(private afAuth: AngularFireAuth, private activateRoute: ActivatedRoute) {}
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' }
}
]
}
];
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;
}
}