Angular is a widely used framework for writing web and mobile applications, that’s a fact. If we also combine it with Firebase, we have a powerful rapid application development environment that can save you a lot of time for several reasons:
- You can focus on the user experience and forget about the backend, since it is integrated into the services offered by Firebase.
- You have a real-time database, so any change in data will be automatically reflected in your application without having to reload or re-check.
- You have the entire user authentication flow ready to implement.
For all this and more, I think Angular + Firebase combination is a good choice to start programming professional and scalable applications.
And while this is all very well, there is one task that always seems to me to be repetitive and monotonous when starting a new application, and that is, the implementation of the user authentication flow. This flow is always the same in all applications:
- User registering
- User login
- Check user authentication state
In this article we will take the first steps in creating an application from scratch with integrated authentication. We will learn how to configure the development environment of Angular with Angular CLI, how to create components and pages, how to manage routing, how to integrate our Angular frontend with the Firebase backend and finally how to manage and monitor user authentication… So you never have to do it again! Let’s get started.
1. Install Angular CLI
In order to create an Angular project we need to install in our system the CLI (Command Line Interface) of Angular. This tool is used to create projects, components and run our frontend locally, among other things. To install it and have it available globally in our system, we must execute the following command in our terminal:
$ npm install -g @angular/cli
Once we have the CLI installed we already execute the following command to create an empty Angular project. When asked if we want to use the Angular routing, answer yes. Then it asks us which CSS preprocessor to use. I like SCSS, but it’s not relevant right now.
$ ng new myproject ? Would you like to add Angular routing? Yes ? Which stylesheet format would you like to use? CSS ❯ SCSS [ https://sass-lang.com/documentation/syntax#scss ] Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ] Less [ http://lesscss.org ] Stylus [ http://stylus-lang.com ]
We already have an empty Angular project ready to go. To launch a web server with our project we will only have to cd to the project directory and run npm start:
$ cd myproject $ npm start
When it finishes “compiling” we will have a web server at http://localhost:4200 with our new project that we can access from our browser. I recommend that you leave a terminal open with npm start so that Angular will automatically reload the changes we make. What we’ll see entering http://localhost:4200 will be something like this:
The code for this view can be found in src/app/app.component.html. You can browse around a bit, but basically you have to throw it all away. It’s just a little welcome template so the project doesn’t look empty.
2. Components
In Angular you will work with entities called components. A component is basically anything you see on the screen that has its own organization, from a button with its events “click” to a whole page. It’s important to be clear that components can contain other components.
In the base project of Angular there is a single large component called AppComponent and its view is described in src/app/app.component.html. Unless our application is extremely simple, we’re going to need to create more than one page and split our application into several large components (or pages). For this reason we have activated routing. For this reason too we’re going to remove all of the code from src/app/app.component.html and leave only the following lines:
<h1>Our first Angular app</h1> <router-outlet></router-outlet>
What we have just done is to tell the main view of our application that it must display a phrase and, just below it, the content of the active component in our routing service. We’ll see how the Angular Router works in the next point, but before that we’re going to create two pages or large components, one for the login and another for the user dashboard.
$ ng g component LoginPage CREATE src/app/login-page/login-page.component.scss (0 bytes) CREATE src/app/login-page/login-page.component.html (25 bytes) CREATE src/app/login-page/login-page.component.spec.ts (650 bytes) CREATE src/app/login-page/login-page.component.ts (291 bytes) UPDATE src/app/app.module.ts (489 bytes) $ ng g component DashboardPage CREATE src/app/dashboard-page/dashboard-page.component.scss (0 bytes) CREATE src/app/dashboard-page/dashboard-page.component.html (29 bytes) CREATE src/app/dashboard-page/dashboard-page.component.spec.ts (678 bytes) CREATE src/app/dashboard-page/dashboard-page.component.ts (307 bytes) UPDATE src/app/app.module.ts (601 bytes)
2. Routing
We already have two pages, one for the login and one for the dashboard. Let’s see how we can access them by routing Angular. The routing is the mechanism Angular has to build the different URLs of our application, and its operation is programmed in src/app/app-routing.module.ts. What we are going to do now is to add two new routes for our two new pages. Just add the corresponding objects in the array routes by associating them with our new components:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LoginPageComponent } from './login-page/login-page.component'; import { DashboardPageComponent } from './dashboard-page/dashboard-page.component'; /* Add here your routes */ const routes: Routes = [ { path: 'login', component: LoginPageComponent }, { path: 'dashboard', component: DashboardPageComponent }, ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
If we open our browser right now at http://localhost:4200/login we will see this:
First we see the phrase “Our first Angular app”, which as you can see is part of the general AppComponent view, described in src/app/app.component.html. Below we see “login-page works!” which is the view of our new LoginPageComponent described in src/app/login-page/login-page.component.html. This is how the Angular router works. Usually the main component AppComponent is in charge of drawing the elements common to all pages. The <router-outlet> tag is in charge of drawing the content of the components activated by our router.
3. Firebase
Before we continue, and since we already need to get into user authentication issues, let’s create our backend in the Firebase console. To do this we need to provision a project and a web application within it. Once we’ve done this, we’ll copy its credentials to insert them into our Angular project (Spanish video, but you’ll get it ;):
After this we must install the Firebase SDK in our Angular project. We could simply install the web SDK that is listed in the official Firebase documentation, but I think it is much more useful to directly install the official Firebase library for Angular, AngularFire, and select the project we just created in the Firebase console:
$ ng add @angular/fire@next Installing packages for tooling via npm. Installed packages for tooling via npm. UPDATE package.json (1534 bytes) ✔ Packages installed successfully. ✔ Preparing the list of your Firebase projects ? Please select a project: (Use arrow keys or type to search) ❯ myproject (myproject-a2e0b)
Now that we have our Firebase backend provisioned and the official library for Angular installed, we need to configure it in our project so that they are definitely linked. To do this we need to make some modifications in our application module. Remember that you must copy YOUR OWN firebaseConfig configuration from your Firebase console:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { LoginPageComponent } from './login-page/login-page.component'; import { DashboardPageComponent } from './dashboard-page/dashboard-page.component'; import { AngularFireModule } from '@angular/fire'; import { AngularFireAuthModule, AngularFireAuth } from '@angular/fire/auth'; /* REPLACE WITH YOUR CONFIG */ const firebaseConfig = { apiKey: 'AIzaSyCOZ_rGQGQjrw-iB9yXqIZasD4PRqY****', authDomain: 'myproject-a2e0b.firebaseapp.com', databaseURL: 'https://myproject-a2e0b.firebaseio.com', projectId: 'myproject-a2e0b', storageBucket: 'myproject-a2e0b.appspot.com', messagingSenderId: '80332117****', appId: '1:803321171754:web:e779ee1f1bc939e50c****', measurementId: 'G-VJF2E1****' }; @NgModule({ declarations: [ AppComponent, LoginPageComponent, DashboardPageComponent ], imports: [ BrowserModule, AppRoutingModule, AngularFireModule.initializeApp(firebaseConfig), AngularFireAuthModule ], providers: [ AngularFireAuth ], bootstrap: [AppComponent] }) export class AppModule { }
At this point it is best to stop the execution of your project on your terminal with Ctrl+C and “compile” again with npm start. Now our Angular project is already integrated with our Firebase backend!
3. Authentication
Don’t count your chickens yet. Even if we have our backend integrated, we can enter indiscriminately to /login or /dashboard since there is no user checks anywhere. We’re not checking authentication yet.
First of all we must tell our backend what kind of authentication we are going to support in our application. For this example we’re going to enable Google authentication, but you can enable whatever you need.
Now let’s modify our /login page to add a button that opens the authentication dialog with Google.
<p>login-page works!</p> <button (click)="loginGoogle()">Login with Google</button>
import { Component, OnInit } from '@angular/core'; import { AngularFireAuth } from '@angular/fire/auth'; import * as firebase from 'firebase'; import { Router } from '@angular/router'; @Component({ selector: 'app-login-page', templateUrl: './login-page.component.html', styleUrls: ['./login-page.component.scss'] }) export class LoginPageComponent implements OnInit { constructor( private afAuth: AngularFireAuth, private router: Router ) { } ngOnInit(): void { } loginGoogle() { this.afAuth.signInWithPopup(new firebase.auth.GoogleAuthProvider()) .then(userCredentials => { /* LOGIN OK */ this.router.navigateByUrl('/dashboard'); }) .catch(err => { /* LOGIN ERR */ alert(err.message); }) .finally(() => { /* DO SOMETHING IN ANY CASE */ }); } logout() { this.afAuth.signOut(); } }
At this point we will see the Login with Google button in our http://localhost:4200/login view. If we click on it a window will appear asking for our Google account to identify us. In case we identify ourselves correctly we tell the router to send us to /dashboard. If the identification fails we show the error message returned by Google. This example can be extrapolated to any identification method supported by Firebase.
We already have half our authentication flow done, but… What happens if we go directly to the route /dashboard without going through the login first? Open a private browser window to start a new clean user session and you’ll see it:
Indeed, we enter our dashboard quietly without being authenticated. And that’s bad, because no unauthenticated user should be able to visit our dashboard. Therefore we are going to put the necessary security measures in place so that unauthenticated users cannot snoop where they should not. To do this we will use the mechanisms of AngularFire designed for this task. This is the AngularFireAuthGuard class and its associated functions. They are implemented in our routing module:
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { LoginPageComponent } from './login-page/login-page.component'; import { DashboardPageComponent } from './dashboard-page/dashboard-page.component'; import { AngularFireAuthGuard, redirectUnauthorizedTo, redirectLoggedInTo } from '@angular/fire/auth-guard'; const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']); const redirectLoggedInToDashboard = () => redirectLoggedInTo(['dashboard']); const routes: Routes = [ { path: 'login', component: LoginPageComponent, canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectLoggedInToDashboard } }, { path: 'dashboard', component: DashboardPageComponent, canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin } } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { }
As you can see I have extended the properties of the routes with the parameters canActivate and data. This way it’s AngularFireAuthGuard who takes care of checking if the user is already properly authenticated. With the parameter data we configure the action to be done. In this case the non-authenticated users that try to go to /dashboard will be redirected to /login, while the authenticated users that visits /login will be sent directly to /dashboard. Try it out.
It is a great advantage for the programmer to forget about the management and supervision of the authentication, and from this moment on you can do it. You simply need to add new guards to the new pages.
But one more thing, since we have not considered the most important route of our application: the root route. Or what is the same, the home page: http://localhost:4200/. Personally I like to support all this logic in the login component. This way, the home page is the one that decides where the path should go or simply shows the login screen in case the user is not identified. To do this we simply have to modify the path of the LoginPageComponent by the root path leaving the path property empty:
[...] const routes: Routes = [ { path: '', component: LoginPageComponent, canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectLoggedInToDashboard } }, [...]
As you can see at http://localhost:4200, our authentication system is already working properly! If we are not authenticated it shows us the login view, and if we are authenticated it sends us to the dashboard. If we try to visit the dashboard without being authenticated, it sends us back to login. Easy.
At this point we have everything we need to start implementing our user interface and business logic without worrying about user authentication anymore. This is a good time to save your project and use it as a template for your future developments.
Let’s go over what we’ve learned:
- We have installed the Angular CLI, an essential tool to create Angular projects.
- We have created page components and linked them to their corresponding paths using the Angular router.
- We have provisioned a Firebase backend and linked it to our Angular frontend via the official Firebase library for Angular.
- We’ve added a user authentication method, and implemented it using AngularFireAuth.
- We have monitored and protected the paths of our application using AngularFireAuthGuard.
- We have set up our home page.
In future deliveries:
- We will implement the PrimeNG component library to make the user interface more beautiful and useful.
- We will use Firebase Database to consult and modify data in real time.
- We will deploy our Angular + Firebase application in production using Firebase hosting.
Useful links:
- Official documentation of Firebase for web.
- Official documentation of AngularFire.
- Angular Firebase Template, my template to start Angular projects with Firebase and PrimeNG. It implements almost everything we’ve discussed in this article, and a few other things.
If you have found this article useful, perhaps it will be useful for someone else in your environment . Share it in your social networks and follow me on mines!