Version
Angular CLI: 15.1.6
Node: 18.12.1
Package Manager: npm 9.5.0
It’s a good day to dive deep into Firebase authentication with Angular!
Streamline your front-end journey by selecting the ideal platform from Angular, React, and Vue.
To authenticate users for our Angular project, Firebase offers backend services, simple SDKs, and ready-to-use UI libraries. First, let’s look at the providers.
Providers
Passwords, phone numbers, well-known federated identity providers like Google, Facebook, and Twitter, among other methods, are all supported for authentication by Firebase.
Fig 1: Get started with Firebase Auth
npm Packages
Note: Skip these steps if you want to add Firebase authentication to your existing Angular app.
Now, how do we create Angular from scratch?
Creating Angular from Scratch
Make sure Node JS is installed on your local development machine before continuing.
Install Angular CLI; disregard if previously done so.
npm install -g angular/cli
Creating Angular app
ng new angular-firebase-authentication
Once the packages are downloaded and ready to use, get into the directory.
Let’s add Prime NG library to enhance our UI design.
npm install primeng primeicons
Add these css files to your angular.json file under “styles”
... "styles": [ "node_modules/primeicons/primeicons.css", "node_modules/primeng/resources/themes/lara-light-blue/theme.css", "node_modules/primeng/resources/primeng.min.css", ... ]
Now, let’s install Firebase.
Installing Firebase
To use Firebase in our app, we will be using Firebase and @angular/fire.
Once the packages are installed, we can add the firebase configurations in our environment.ts file or get details from vault as per the security of the app.
If you do not see any environments file/folder, this might be as Angular 15 simply doesn’t ship environment files anymore by default.
You can still generate them using the command:
ng generate environments
You can get the configuration details from your Firebase account.
Once you have created your Project on the Firebase console you can go to:
Fig 2: Installing Firebase
Project Overview >> Setting icon >> Project Settings
Once you are in Project settings under General, you can find your app which will have a SDK setup and configurations.
Fig 3: SDK setup and configuration
Scroll down and you will find the firebaseConfigurations.
firebaseConfig: { apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxxxx", authDomain: "app-name.firebaseapp.com", projectId: "app-name", storageBucket: "app-name.appspot.com", messagingSenderId: "000000000000", appId: "1:000000000000:web:012121212121212121", measurementId: "G-XXXXXXXX" }
Once you have imported the configurations in your environment file, you need to Import and register Firebase modules in app.module.ts.
imports: [ AngularFireModule.initializeApp(environment.firebaseConfig), AngularFireAuthModule, AngularFirestoreModule, AngularFireStorageModule, AngularFireDatabaseModule, ]
Now it’s time to create components and routes.
Create Angular Components and Routes
Once you have imported and initialized the firebase module, let us create some components, routes and Authentication service.
To generate components:
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 the corresponding routes in your app.routing.module.ts or the specific module where you have created the components. It is better to use a separate module for authentication and authorization services. Now for simplicity we will add the routes in our app.routing.module.ts file.
const routes: Route = [ { path: '', redirectTo: '/sign-in', pathMatch: 'full' }, { path: 'sign-in', component: SignInComponent }, { path: 'register-user', component: SignUpComponent }, { path: 'dashboard', component: DashboardComponent }, { path: 'forgot-password', component: ForgotPasswordComponent }, { path: 'verify-email-address', component: VerifyEmailComponent }, ];
Time for the Firebase Authentication Service!
Firebase Authentication Service
ng g i shared/services/user ng g s shared/services/auth
To the User interface, add the Object which is the users Schema.
export interface User { uid: string; email: string; displayName: string; photoURL: string; emailVerified: boolean; }
The created auth service holds the logic of our Authentication system. We will integrate Firebase’s Google Authentication, similarly other providers can be configured.
The auth service will take care of the canActivate auth guard mechanism for route protection as well as sign-in with username/password, sign-up with email/password, password reset, and email verification.
We now need to Inject Firestore service, Firebase auth service, NgZone to remove outside scope warnings.
In the auth service configure these functions to sign in the user, signup, send the verification email, to reset the forgotten password, get user logged in status, Google auth, auth login to use providers, set users data, and sign out.
import { Injectable, NgZone } from '@angular/core'; import { AngularFireAuth } from '@angular/fire/compat/auth'; import { AngularFirestore, AngularFirestoreDocument, } from '@angular/fire/compat/firestore'; import { Router } from '@angular/router'; import { User } from './user'; import { GoogleAuthProvider } from 'firebase/auth'; @Injectable({ providedIn: 'root', }) export class AuthService { userData: User; constructor( public afAuth: AngularFireAuth, public afs: AngularFirestore, public ngZone: NgZone, public router: Router ) { /* Saving user data in localstorage when logged in and setting up null when logged out */ this.afAuth.authState.subscribe((user) => { if (user) { this.userData = user; localStorage.setItem('user', JSON.stringify(this.userData)); JSON.parse(localStorage.getItem('user')!); } else { localStorage.setItem('user', 'null'); JSON.parse(localStorage.getItem('user')!); } }); } // Sign in with email/password SignIn(email: string, password: string) { return this.afAuth .signInWithEmailAndPassword(email, password) .then((result) => { this.SetUserData(result.user); this.afAuth.authState.subscribe((user) => { if (user) { this.router.navigate(['dashboard']); } }); }) .catch((error) => { window.alert(error.message); }); } // Sign up with email/password SignUp(email: string, password: string) { return this.afAuth .createUserWithEmailAndPassword(email, password) .then((result) => { /* Call the SendVerificaitonMail() function when new user sign up and returns promise */ this.SendVerificationMail(); this.SetUserData(result.user); }) .catch((error) => { window.alert(error.message); }); } // Send email verfificaiton when new user sign up SendVerificationMail() { return this.afAuth.currentUser .then((u: any) => u.sendEmailVerification()) .then(() => { this.router.navigate(['verify-email-address']); }); } // Reset Forggot password ForgotPassword(passwordResetEmail: string) { return this.afAuth .sendPasswordResetEmail(passwordResetEmail) .then(() => { window.alert('Password reset email sent, check your inbox.'); }) .catch((error) => { window.alert(error); }); } // Returns true when user is looged in and email is verified get isLoggedIn(): boolean { const user = JSON.parse(localStorage.getItem('user')!); return user !== null && user.emailVerified !== false ? true : false; } // Sign in with Google GoogleAuth() { return this.AuthLogin(new GoogleAuthProvider()).then((res: any) => { this.router.navigate(['dashboard']); }); } // Auth logic to run auth providers AuthLogin(provider: any) { return this.afAuth .signInWithPopup(provider) .then((result) => { this.router.navigate(['dashboard']); this.SetUserData(result.user); }) .catch((error) => { window.alert(error); }); } /* Setting up user data when sign in with username/password, sign up with username/password and sign in with social auth provider in Firestore database using AngularFirestore + AngularFirestoreDocument service */ SetUserData(user: any) { const userRef: AngularFirestoreDocument<any> = this.afs.doc( `users/${user.uid}` ); const userData: User = { uid: user.uid, email: user.email, displayName: user.displayName, photoURL: user.photoURL, emailVerified: user.emailVerified, }; return userRef.set(userData, { merge: true, }); } // Sign out SignOut() { return this.afAuth.signOut().then(() => { localStorage.removeItem('user'); this.router.navigate(['sign-in']); }); } }
Once the code is added, go to your app.module.ts file and import authentication service so that it will be accessible throughout the application.
providers:[ AuthService ]
Now, let’s figure out the login component.
Login
Let’s use our AuthService in the Login Component.
Add this code to your Login ts file, in this file we have implemented a function from our Authservice which takes email and password as the arguments.
export class LoginComponent implements OnInit { loginForm: FormGroup; constructor(private router: Router, private authService: AuthService){ } ngOnInit(): void { this.createLoginForm(); } createLoginForm(){ this.loginForm = new FormGroup({ email: new FormControl('',Validators.required), password: new FormControl('',Validators.required) }) } login(){ if(this.loginForm.valid){ this.authService.SignIn(this.loginForm.value.email, this.loginForm.value.password) } } }
Add this code in your HTML file.
Here we have added the formgroup and the required fields.
<form [formGroup]="loginForm"> <div class="grid"> <div class="col-12"> <div class="card flex justify-content-center"> <p-card header="Login" [style]="{ width: '360px' }"> <!-- <ng-template pTemplate="header"> </ng-template> --> <ng-template pTemplate="content"> <div> <label for="username">Email</label> <div> <input class="w-100" pInputText formControlName="email" type="text" placeholder="Email" /> </div> </div> <div class="mt-3"> <label for="password">Password</label> <div> <input class="w-100" pInputText type="password" formControlName="password" placeholder="Password" /> </div> </div> </ng-template> <ng-template pTemplate="footer"> <div class="flex justify-content-center align-items-center"> <button pButton pRipple label="Log In" (click)="login()"></button> <p-button label="Forgot Password?" styleClass="p-button-link" routerLink="/reset-password" ></p-button> </div> <div class="mt-2 flex justify-content-center align-items-center"> <p-button label="Don't have an account?" styleClass="p-button-link" routerLink="/sign-up" ></p-button> </div> </ng-template> </p-card> </div> </div> </div> </form>
Signup
Now to sign up a new user we will have to similarly inject the AuthService.
We have added a signup function which calls the signup function from our AuthService file.
export class SignUpComponent implements OnInit { signupForm: FormGroup; constructor(private authService: AuthService) {} ngOnInit(): void { this.createForm(); } createForm() { this.signupForm = new FormGroup({ email: new FormControl('', Validators.required), password: new FormControl('', Validators.required), confirmPassword: new FormControl('', Validators.required), displayName: new FormControl('', Validators.required), }); } signup() { if (this.signupForm.valid) { this.authService.SignUp( this.signupForm.value.email, this.signupForm.value.password ); } } }
HTML code to show sign up form
<form [formGroup]="signupForm"> <div class="grid"> <div class="col-12"> <div class="card flex justify-content-center"> <p-card header="Sign up" [style]="{ width: '360px' }"> <!-- <ng-template pTemplate="header"> </ng-template> --> <ng-template pTemplate="content"> <div> <label for="username">Name</label> <div> <input class="w-100" pInputText formControlName="displayName" type="text" placeholder="Name" /> </div> </div> <div class="mt-3"> <label for="username">Email</label> <div> <input class="w-100" pInputText formControlName="email" type="text" placeholder="Email" /> </div> </div> <div class="mt-3"> <label for="password">Password</label> <div> <input class="w-100" pInputText type="password" formControlName="password" placeholder="Password" /> </div> </div> <div class="mt-3"> <label for="password">Confirm Password</label> <div> <input class="w-100" pInputText type="password" formControlName="confirmPassword" placeholder="Confirm Password" /> </div> </div> </ng-template> <ng-template pTemplate="footer"> <div class="flex justify-content-center align-items-center"> <button pButton pRipple label="Sign up" (click)="signup()" ></button> </div> <div class="mt-2 flex justify-content-center align-items-center"> <p-button label="Already have an account? Click Here" styleClass="p-button-link" routerLink="/login" ></p-button> </div> </ng-template> </p-card> </div> </div> </div> </form>
How do we reset the password?
Reset Password
To reset the password, send an email with a link so that the user only can reset the email link. This functionality is also already added in the auth service.
export class ResetPasswordComponent implements OnInit { resetForm: FormGroup; constructor(private authService: AuthService) {} ngOnInit(): void { this.resetForm = new FormGroup({ email: new FormControl('', Validators.required), }); } sendResetLink() { if (this.resetForm.valid) { this.authService.ForgotPassword(this.resetForm.value.email); } } }
UI to enter email to send the link to reset password
<form [formGroup]="resetForm"> <div class="grid"> <div class="col-12"> <div class="card flex justify-content-center"> <p-card header="Login" [style]="{ width: '360px' }"> <!-- <ng-template pTemplate="header"> </ng-template> --> <ng-template pTemplate="content"> <div> <label for="username">Email</label> <div> <input class="w-100" pInputText formControlName="email" type="text" placeholder="Email" /> </div> </div> </ng-template> <ng-template pTemplate="footer"> <div class="flex justify-content-center align-items-center"> <button pButton pRipple label="Reset Password" (click)="sendResetLink()" ></button> </div> <div class="mt-2 flex justify-content-center align-items-center"> <p-button label="Don't have an account? Sign Up here!" styleClass="p-button-link" routerLink="/sign-up" ></p-button> </div> <div class="mt-2 flex justify-content-center align-items-center"> <p-button label="Already have an account? Login Here" styleClass="p-button-link" routerLink="/login" ></p-button> </div> </ng-template> </p-card> </div> </div> </div> </form>
Let’s explore the use of route guards.
Using Route Guards
In Angular, route guards protect the routes.
Now I’ll explain how to use the canActivate() route guard mechanism to quickly secure routes from unwanted access.
Let’s create our guard by using the command ng generate guard
ng generate guard shared/guard/auth Select CanActivate()
Let’s look at our isLoggedIn() method, which returns boolean if the users object is stored in localStorage
// Returns true when user is looged in and email is verified get isLoggedIn(): boolean { const user = JSON.parse(localStorage.getItem('user')!); return user !== null && user.emailVerified !== false ? true : false; }
In our auth file in the canActivate() check if user is logged in and if no, then return user to the login-page and:
constructor(private authService: AuthService, public router: Router) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { if (!this.authService.isLoggedIn) { this.router.navigate(['']); } return true; }
In our app-routing.module.ts file add canActivate to our route whose access we want to give to our authenticated users.
{ path: '', loadChildren: () => AuthenticateModule, canActivate: [AuthGuard] },
There you have it! That’s how one goes about Firebase authentication with Angular! I’m sure you will enjoy executing this tutorial blog.
Send us an email with your thoughts about this blog. Visit us at Nitor Infotech to know about the software we craft.