This tutorial walks you through on how to create a complete Angular Firebase Authentication system from scratch.
We will implement and use the Firebase auth platform in Angular application to build a robust Login and Signup system.
Our user authentication application covers the following functionalities:
- Sign-in with Google
- Sign-in with username/password
- Register with username/password
- Email verification
- Forgot password
- Guard Routes with canActivate
- Prevent user to access sign in and sign up URL when a user is already logged in
- Handle logged-in user state with Local Storage
Create Angular Authentication Project
Install Angular CLI in your development system.
npm install -g @angular/cli
Execute command to install a fresh Angular application.
ng new angular-firebase-authentication
Move to the project root:
cd angular-firebase-authentication
Disable Strict Angular TypeStrict Errors
The latest version of Angular comes with strict mode, you have to manually disable the strict mode you can set “strict”: false
, "noImplicitReturns": false
and "strictTemplates": false
inside the compilerOptions and angularCompilerOptions in tsconfig.json file.
Install Bootstrap package in Angular.
npm install bootstrap
Add bootstrap CSS in angular.json
.
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
]
Add Firebase in Angular
Adding Firebase in Angular is easy, it can be done with a single command. However, you must create a Firebase project for making the interaction between server and client.
npm install firebase @angular/fire
Open environment.ts file, add your firebase credentials inside the file.
export const environment = {
production: false,
firebaseConfig: {
apiKey: "xxxxxxxxxxxxxx",
authDomain: "xxxxxxxxxxxxxxxxxxxx",
databaseURL: "xxxxxxxxxxxxxxxxxxxxx",
projectId: "xxxxxxx",
storageBucket: "xxxxxxx",
messagingSenderId: "xxxxxx",
appId: "xxxxx",
measurementId: "xxxxxxxxxxxxxx"
}
};
AngularFire is a Firebase package and has been installed, now add the given below code in app.module.ts.
import { AngularFireModule } from '@angular/fire/compat';
import { AngularFireAuthModule } from '@angular/fire/compat/auth';
import { AngularFirestoreModule } from '@angular/fire/compat/firestore';
import { environment } from '../environments/environment';
@NgModule({
imports: [
AngularFireModule.initializeApp(environment.firebaseConfig),
AngularFireAuthModule,
AngularFirestoreModule,
]
})
Create Angular Components
Generate following components to deal with User Registration, Signin, Forget Password and Email Verification:
ng g c components/dashboard
ng g c components/sign-in
ng g c components/sign-up
ng g c components/forgot-password
ng g c components/verify-email
Add Routing
Next, we create routes for angular authentication app in app-routing.module.ts
file.
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { SignInComponent } from './components/sign-in/sign-in.component';
import { SignUpComponent } from './components/sign-up/sign-up.component';
import { DashboardComponent } from './components/dashboard/dashboard.component';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { VerifyEmailComponent } from './components/verify-email/verify-email.component';
const routes: Routes = [
{ path: '', redirectTo: '/sign-in', pathMatch: 'full' },
{ path: 'sign-in', component: SignInComponent },
{ path: 'sign-up', component: SignUpComponent },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'forgot-password', component: ForgotPasswordComponent },
{ path: 'email-verification', component: VerifyEmailComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Thereafter, add router-outlet in app.component.html file.
<router-outlet></router-outlet>
Create Authentication Service
To create Angular Authentication System we need to create central service with Firebase API.
Place following code in ng-auth.service.ts file.
import { Injectable, NgZone } from '@angular/core';
import * as auth from 'firebase/auth';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import {
AngularFirestore,
AngularFirestoreDocument,
} from '@angular/fire/compat/firestore';
import { Router } from '@angular/router';
export interface User {
uid: string;
email: string;
displayName: string;
photoURL: string;
emailVerified: boolean;
}
@Injectable({
providedIn: 'root',
})
export class NgAuthService {
userState: any;
constructor(
public afs: AngularFirestore,
public afAuth: AngularFireAuth,
public router: Router,
public ngZone: NgZone
) {
this.afAuth.authState.subscribe((user) => {
if (user) {
this.userState = user;
localStorage.setItem('user', JSON.stringify(this.userState));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
SignIn(email, password) {
return this.afAuth
.signInWithEmailAndPassword(email, password)
.then((result) => {
this.ngZone.run(() => {
this.router.navigate(['dashboard']);
});
this.SetUserData(result.user);
})
.catch((error) => {
window.alert(error.message);
});
}
SignUp(email, password) {
return this.afAuth
.createUserWithEmailAndPassword(email, password)
.then((result) => {
this.SendVerificationMail();
this.SetUserData(result.user);
})
.catch((error) => {
window.alert(error.message);
});
}
SendVerificationMail() {
return this.afAuth.currentUser
.then((u) => u.sendEmailVerification())
.then(() => {
this.router.navigate(['email-verification']);
});
}
ForgotPassword(passwordResetEmail) {
return this.afAuth
.sendPasswordResetEmail(passwordResetEmail)
.then(() => {
window.alert('Password reset email sent, check your inbox.');
})
.catch((error) => {
window.alert(error);
});
}
get isLoggedIn(): boolean {
const user = JSON.parse(localStorage.getItem('user'));
return user !== null && user.emailVerified !== false ? true : false;
}
GoogleAuth() {
return this.AuthLogin(new auth.GoogleAuthProvider());
}
AuthLogin(provider) {
return this.afAuth
.signInWithPopup(provider)
.then((result) => {
this.ngZone.run(() => {
this.router.navigate(['dashboard']);
});
this.SetUserData(result.user);
})
.catch((error) => {
window.alert(error);
});
}
SetUserData(user) {
const userRef: AngularFirestoreDocument<any> = this.afs.doc(
`users/${user.uid}`
);
const userState: User = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
emailVerified: user.emailVerified,
};
return userRef.set(userState, {
merge: true,
});
}
SignOut() {
return this.afAuth.signOut().then(() => {
localStorage.removeItem('user');
this.router.navigate(['sign-in']);
});
}
}
- User model contains the data that needs to be communicated with the server.
- Import all the Firebase auth services and inject them in the constructor.
- Define a variable userState which sustain the user authentication state.
Build Angular Login with Firebase
Use Angular NgAuthService service to create:
- Firebase login with username and password
- Firebase login with Gmail
- Firebase Sign in with Facebook
Add code in sign-in.component.ts file.
import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";
@Component({
selector: 'app-sign-in',
templateUrl: './sign-in.component.html',
styleUrls: ['./sign-in.component.css']
})
export class SignInComponent implements OnInit {
constructor(
public ngAuthService: NgAuthService
) { }
ngOnInit() { }
}
Place code in sign-in.component.html.
<div class="displayTable">
<div class="displayTableCell">
<div class="authBlock">
<h3>Signin</h3>
<div class="formGroup">
<input type="text" class="form-control" placeholder="Username" #userName required>
</div>
<div class="formGroup">
<input type="password" class="form-control" placeholder="Password" #userPassword required>
</div>
<div class="formGroup">
<input type="button" class="btn btn-primary btn-block" value="Sign in"
(click)="ngAuthService.SignIn(userName.value, userPassword.value)">
</div>
<div class="formGroup">
<button type="button" class="btn btn-danger btn-block" (click)="ngAuthService.GoogleAuth()">
Log in with Google
</button>
</div>
<div class="forgotPassword">
<span routerLink="/forgot-password">Forgot Password?</span>
</div>
</div>
<div class="redirectToLogin">
<span>Not registered yet?<span class="redirect" routerLink="/sign-up"> Register</span></span>
</div>
</div>
</div>
Build Firebase User Registration
Add code in sign-up.component.ts to create user registration with Firebase.
import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";
@Component({
selector: 'app-sign-up',
templateUrl: './sign-up.component.html',
styleUrls: ['./sign-up.component.css']
})
export class SignUpComponent implements OnInit {
constructor(public ngAuthService: NgAuthService) { }
ngOnInit(): void {
}
}
Place code in sign-up.component.html.
<div class="displayTable">
<div class="displayTableCell">
<div class="authBlock">
<h3>Register User</h3>
<div class="formGroup">
<input type="email" class="form-control" placeholder="Email Address" #userEmail required>
</div>
<div class="formGroup">
<input type="password" class="form-control" placeholder="Password" #userPwd required>
</div>
<div class="formGroup">
<input type="button" class="btn btn-block btn-primary" value="Sign Up"
(click)="ngAuthService.SignUp(userEmail.value, userPwd.value)">
</div>
<div class="formGroup">
<button type="button" class="btn btn-block btn-danger" (click)="ngAuthService.GoogleAuth()">
Continue with Google
</button>
</div>
</div>
<div class="redirectToLogin">
<span>Already have an account? <span class="redirect" routerLink="/sign-in">Log In</span></span>
</div>
</div>
</div>
Build Forgot Password in Angular Firebase
Place code in forgot-password.component.ts to create Forgot Password in Angular Firebase.
import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";
@Component({
selector: 'app-forgot-password',
templateUrl: './forgot-password.component.html',
styleUrls: ['./forgot-password.component.css']
})
export class ForgotPasswordComponent implements OnInit {
constructor(public ngAuthService: NgAuthService) { }
ngOnInit(): void {
}
}
Paste code in forgot-password.component.html.
<div class="displayTable">
<div class="displayTableCell">
<div class="authBlock">
<h3>Forgot Password</h3>
<div class="formGroup">
<input type="email" class="form-control" placeholder="Email Address" #passwordResetEmail required>
</div>
<div class="formGroup">
<input type="submit" class="btn btn-primary btn-block" value="Reset Password"
(click)="ngAuthService.ForgotPassword(passwordResetEmail.value)">
</div>
</div>
</div>
</div>
Send Verification Email
Incorporate code in verify-email.component.ts to build sending verification email to newly registered user with Firebase and Angular.
import { Component, OnInit } from '@angular/core';
import { NgAuthService } from "../../ng-auth.service";
@Component({
selector: 'app-verify-email',
templateUrl: './verify-email.component.html',
styleUrls: ['./verify-email.component.css']
})
export class VerifyEmailComponent implements OnInit {
constructor(public ngAuthService: NgAuthService) { }
ngOnInit(): void {
}
}
Add code inside verify-email.component.html.
<div class="displayTable">
<div class="displayTableCell">
<div class="authBlock">
<h3>Thank You for Signing up</h3>
<div class="formGroup" *ngIf="ngAuthService.userState as user">
<p class="text-center">Confirmation mail has been sent to <strong>{{user.email}}</strong>.</p>
</div>
<div class="formGroup">
<button type="button" class="btn btn-block btn-success" (click)="ngAuthService.SendVerificationMail()">
Resend Verification Email
</button>
</div>
</div>
</div>
</div>
Protect Routes with Angular 13 Route Guard
To secure routes in Angular, we need to use the CanActivate interface.
Interface that a class can implement to be a guard deciding if a route can be activated. If all guards return true, navigation continues. If any guard returns false, navigation is cancelled.
Create route guard service with following command.
ng generate guard auth
Add code in auth.guard.ts.
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { NgAuthService } from "./ng-auth.service";
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(
public ngAuthService: NgAuthService,
public router: Router
){ }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
if(this.ngAuthService.isLoggedIn !== true) {
this.router.navigate(['sign-in'])
}
return true;
}
}
Register route guard with component, add code in app-routing.module.ts.
import { AuthGuard } from "./auth.guard";
const routes: Routes = [
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
];
Store User State with Local Storage
In this step we will learn to keep Logged-in user state with localStorage object in Angular.
Add code in ng-auth.service.ts.
import { Injectable, NgZone } from '@angular/core';
import * as auth from 'firebase/auth';
import { AngularFireAuth } from '@angular/fire/compat/auth';
import {
AngularFirestore,
AngularFirestoreDocument,
} from '@angular/fire/compat/firestore';
import { Router } from '@angular/router';
export interface User {
uid: string;
email: string;
displayName: string;
photoURL: string;
emailVerified: boolean;
}
@Injectable({
providedIn: 'root',
})
export class NgAuthService {
userState: any;
constructor(
public afs: AngularFirestore,
public afAuth: AngularFireAuth,
public router: Router,
public ngZone: NgZone
) {
this.afAuth.authState.subscribe((user) => {
if (user) {
this.userState = user;
localStorage.setItem('user', JSON.stringify(this.userState));
JSON.parse(localStorage.getItem('user'));
} else {
localStorage.setItem('user', null);
JSON.parse(localStorage.getItem('user'));
}
});
}
SignIn(email, password) {
return this.afAuth
.signInWithEmailAndPassword(email, password)
.then((result) => {
this.ngZone.run(() => {
this.router.navigate(['dashboard']);
});
this.SetUserData(result.user);
})
.catch((error) => {
window.alert(error.message);
});
}
SignUp(email, password) {
return this.afAuth
.createUserWithEmailAndPassword(email, password)
.then((result) => {
this.SendVerificationMail();
this.SetUserData(result.user);
})
.catch((error) => {
window.alert(error.message);
});
}
SendVerificationMail() {
return this.afAuth.currentUser
.then((u) => u.sendEmailVerification())
.then(() => {
this.router.navigate(['email-verification']);
});
}
ForgotPassword(passwordResetEmail) {
return this.afAuth
.sendPasswordResetEmail(passwordResetEmail)
.then(() => {
window.alert('Password reset email sent, check your inbox.');
})
.catch((error) => {
window.alert(error);
});
}
get isLoggedIn(): boolean {
const user = JSON.parse(localStorage.getItem('user'));
return user !== null && user.emailVerified !== false ? true : false;
}
GoogleAuth() {
return this.AuthLogin(new auth.GoogleAuthProvider());
}
AuthLogin(provider) {
return this.afAuth
.signInWithPopup(provider)
.then((result) => {
this.ngZone.run(() => {
this.router.navigate(['dashboard']);
});
this.SetUserData(result.user);
})
.catch((error) => {
window.alert(error);
});
}
SetUserData(user) {
const userRef: AngularFirestoreDocument<any> = this.afs.doc(
`users/${user.uid}`
);
const userState: User = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL,
emailVerified: user.emailVerified,
};
return userRef.set(userState, {
merge: true,
});
}
SignOut() {
return this.afAuth.signOut().then(() => {
localStorage.removeItem('user');
this.router.navigate(['sign-in']);
});
}
}
Place code in dashboard.component.html.
<div class="container mt-5" style="max-width: 600px">
<div class="row" *ngIf="ngAuthService.userState as user">
<div class="media">
<img class="align-self-start mr-5 img-thumbnail rounded-circle"
src="{{(user.photoURL) ? user.photoURL : '/assets/dummy.jpg'}}" alt="{{user.displayName}}">
<div class="media-body">
<h2>Hi: <strong>{{(user.displayName) ? user.displayName : 'User'}}</strong></h2>
<p>User ID: <strong>{{user.uid}}</strong></p>
<p>Email: <strong>{{user.email}}</strong></p>
</div>
</div>
</div>
<a class="btn btn-block btn-primary" (click)="ngAuthService.SignOut()">
Sign Out
</a>
</div>
Now, open dashboard.component.ts file, import and inject the NgAuthService.
import { Component, OnInit } from '@angular/core';
import { NgAuthService } from '../../ng-auth.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css'],
})
export class DashboardComponent implements OnInit {
constructor(public ngAuthService: NgAuthService) {}
ngOnInit(): void {}
}
To style the firebase auth app, add the given css in src/styles.css file.
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
font-weight: 400;
width: 100%;
height: 100%;
}
.feather {
width: 16px;
height: 16px;
vertical-align: text-bottom;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
}
.media-body h1 {
font-weight: 300;
margin-bottom: 20px;
}
.media-body h1 strong {
font-weight: 600;
}
.media-body p {
margin-bottom: 10px;
font-weight: 300;
}
.media-body p strong {
margin-bottom: 10px;
font-weight: 600;
}
.px-logo {
display: block;
clear: both;
margin: 0 auto 20px;
width: 220px;
}
.px-logo a img {
width: 100%;
}
.displayTable {
display: table;
width: 100%;
height: 100%;
background: #fc466b; /* fallback for old browsers */
background: -webkit-linear-gradient(
to right,
#3f5efb,
#fc466b
); /* Chrome 10-25, Safari 5.1-6 */
background: linear-gradient(
to right,
#3f5efb,
#fc466b
); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
}
.displayTableCell {
display: table-cell;
vertical-align: middle;
width: 100%;
height: 100%;
}
h3 {
text-align: center;
font-size: 22px;
margin: 0 0 20px;
}
.authBlock {
margin: 0 auto;
max-width: 400px;
background: white;
padding: 30px 40px 10px;
overflow: hidden;
-webkit-box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.04);
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.04);
}
.formGroup {
margin-bottom: 20px;
float: left;
width: 100%;
}
.halfWidth {
width: 48.5%;
}
.left {
float: left;
}
.right {
float: right;
}
.forgotPassword {
text-align: center;
margin: -12px 0 15px 0;
float: left;
width: 100%;
}
.forgotPassword span {
color: #3c89ef;
font-size: 14px;
font-weight: 400;
cursor: pointer;
display: inline-block;
padding-top: 20px;
}
.redirectToLogin {
padding: 15px 0 0;
text-align: center;
font-size: 14px;
font-weight: 400;
display: block;
color: rgba(255, 255, 255, 0.6);
}
.redirectToLogin .redirect {
cursor: pointer;
color: #ffffff;
text-decoration: underline;
}
/* * Sidebar */
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100;
/* Behind the navbar */
padding: 48px 0 0;
/* Height of navbar */
box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.1);
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: 0.5rem;
overflow-x: hidden;
overflow-y: auto;
}
@supports ((position: -webkit-sticky) or (position: sticky)) {
.sidebar-sticky {
position: -webkit-sticky;
position: sticky;
}
}
.sidebar .nav-link {
font-weight: 500;
color: #333;
}
.sidebar .nav-link .feather {
margin-right: 4px;
color: #999;
}
.sidebar-heading {
font-size: 0.75rem;
text-transform: uppercase;
}
.nav-link {
padding: 1.5rem 1rem;
border-bottom: 1px solid #dde0e2;
cursor: pointer;
}
.sidebar .nav-link.active,
.sidebar a:hover,
a:not([href]):not([tabindex]):focus,
a:not([href]):not([tabindex]):hover {
color: #e91e63;
background: #efefef;
}
/* * Content */
[role="main"] {
padding-top: 48px;
}
.dasboard-text {
border-left: 1px solid rgb(255, 255, 255, 0.3);
color: rgb(255, 255, 255, 0.5);
display: inline-block;
padding: 0 0 0 14px;
font-size: 15px;
margin-left: 15px;
position: relative;
top: -1px;
}
/* * Navbar */
.navbar-brand {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
.navbar .form-control {
padding: 0.75rem 1rem;
border-width: 0;
border-radius: 0;
}
.form-control-dark {
color: #fff;
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.1);
}
.form-control-dark:focus {
border-color: transparent;
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.25);
}
.form-control:focus {
border-color: #00bcd4;
box-shadow: none;
}
.form-control {
font-size: 14px;
}
.bg-dark {
background-color: #3f51b5 !important;
}
.gap-right {
margin-right: 10px;
}
i {
width: 22px;
text-align: center;
margin-right: 5px;
}
.inner-adjust {
padding: 0 20px;
}
.action-block {
cursor: pointer;
}
.action-block .fa-edit:hover {
color: #009688;
}
.action-block .fa-trash-alt:hover {
color: #e91e63;
}
.btn-primary.focus,
.btn-primary:focus {
box-shadow: none;
}
/* Pagination */
body pagination-template {
padding: 0;
margin: 8px 0 0;
float: left;
width: 100%;
text-align: right;
}
body .ngx-pagination li:last-child {
margin: 0;
}
body .ngx-pagination .current {
background: #055af9;
}
.ngx-pagination a:hover,
.ngx-pagination button:hover {
text-decoration: none;
}
/* Error */
.error {
color: red;
margin-top: 5px;
}
input.ng-invalid.ng-touched {
border: 1px solid red;
}
.btn-success.disabled,
.btn-success:disabled {
cursor: not-allowed;
}
/* Nav */
body .navbar {
padding: 6px 0 !important;
}
body .navbar-brand {
background: none;
}
.brand-logo {
max-width: 85%;
}
.pt-3,
.py-3 {
padding-top: 2.4rem !important;
}
.sidebar-sticky {
padding-top: 1.2rem !important;
}
/* Form */
label {
font-weight: 500;
}
.form-control {
padding: 1.375rem 0.75rem;
}
/* Misc */
.no-data img {
max-width: 420px;
margin: 20px auto 0;
}
.nodata-msg {
margin: 25px 0 15px;
font-size: 28px;
color: #a9a6c5;
font-weight: 300;
letter-spacing: 0.2px;
}
[role="main"] {
padding-top: 65px;
}
.preloader {
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
margin-top: -15px;
}
.custom-text {
font-size: 15px;
color: #5f5f5f;
letter-spacing: 0.2px;
}
.navbar-dark .navbar-brand {
margin-left: 6px;
}
.custom-text strong {
color: #3a3a3a;
}
.mb-3,
.my-3 {
margin-bottom: 1.4rem !important;
}
.user-image {
width: 42px;
height: 42px;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
margin-right: 7px;
background-size: cover;
background-repeat: no-repeat;
background-position: 0 0;
}
body .table thead th {
background: #f3f5ff;
}
.userImage {
max-width: 125px;
}
.rounded-circle {
max-width: 150px;
}
To start the application execute ng serve --open
command.
You can download the full code of this example app from GitHub.