Full code available at Github : Angular Authentication Example (Firebase)

: Angular Authentication Example (Firebase) Check out a demo With all of the various social platforms, on top of e-mail and anonymous sign-ins, authentication in this day and age is quite a complex task. You would be nuts to try and build a comprehensive login system from scratch! Fortunately, there are authentication systems we can use such as Firebase and Auth0 that do most of the heavy leg work for us. Still, even with these systems in place, integrating them into Angular can be a bit tricky. In this tutorial, I'm going to show you exactly how to build an Angular Authentication system that uses Firebase as the auth platform. We're even going to work in some very smooth animations, though, that will not be the primary focus. While you may scroll down and notice how lengthy this tutorial is, once you take the time to understand what's happening, you can quickly move past authentication worries and start focusing on what matters; building your damn app! So let's get started.

UPDATE: AngularFire2 Version 4 Update In early May (2017) AngularFire2 upgraded to version 4. This requires some changes in terms of AngularFire2 integration. Please follow my AngularFire2 Version 4 Tutorial to see how to integrate AngularFire2 within an Angular2+ (version 4 as well) project. With some adjustments based on that tutorial, you can still make this particular project work.

First, if you prefer watching a video tutorial The video below walks you through the entire process of this written tutorial. Please subscribe to the channel if you enjoy it!

Pre-requisites This is what you're going to need in order to follow along with this tutorial: Nodejs & npm (or better yet, yarn!)

Angular-cli (for creating the Angular project and making generating components and other stuff a cinch)

A firebase account (It's free to start one).

A basic understanding of at least Angular 2 (watch our free angular course if not). Once that stuff is out of the way, proceed.

Setting up the Firebase Project First, head over to Firebase and create a new project. Give it a name and then click Add Firebase to your web app. The following screen will appear with your Firebase project settings: Copy just the lines that are blurred out above and save it in a text file somewhere on your desktop; we'll need these details shortly. Then click on the Authentication link on the left sidebar and choose SIGN-IN METHOD on the top tab: Once there, enable at least Email/Password, Google, and Facebook. Enabling Email/Password and Google are easy, but Facebook requires providing it with an App ID and an App Secret. In order to access those, you'll need to head on over to Facebook Developers. If you don't already have a page, you'll need to create one. Then once created, you can access the App ID and App Secret, from which you need to paste into the Facebook settings on Firebase as shown below: Also, notice the red arrow? You need to click that icon to copy the OAuth Redirect URI. Then head back to faceboook developers, click "Facebook Login" (if it's not there, you have to click Add Product to add it (just go through the steps quickly, nothing too important there)) and paste in the OAuth redirect URI as shown below: Next, click on Database on the left sidebar at Firebase, and then the Rules tab at the top. Change them to: { "rules": { ".read": true, ".write": true } } And hit Publish. Your screen should look similar to this: Awesome. Now we're ready to rock.

Setting up the Angular Project In the console, run: ng new myproject cd myproject Once you're in the new project folder, we need to install AngularFire2: npm install angularfire2 firebase --save Now in your code editor, navigate to /src/app/app.module.ts and import AngularFire, along with exporting the firebaseConfig: ... other imports ... import { AngularFireModule } from 'angularfire2'; export const firebaseConfig = { apiKey: '', authDomain: '', databaseURL: '', storageBucket: '', messagingSenderId: '' }; Paste in the Firebase details I told you to save earlier. And then in @NgModule, we must add AngularFire as an import: imports: [ BrowserModule, FormsModule, HttpModule, AngularFireModule.initializeApp(firebaseConfig) ],

Generating the Components Our app will have 4 primary component: LoginComponent - This will feature our social logins and a button for logging in with email, and signing up with email. EmailComponent - This will be the email login component. SignupComponent - This will allow users to signup via email. MembersComponent - This will serve as the protected area that authenticated users will have access to. Let's use the angular-cli to generate them in the console: ng generate component login ng generate component email ng generate component signup ng generate component members Now with our new components generated for our angular authentication demo, let's proceed by focusing on their class code and templating. We'll start with the LoginComponent.

LoginComponent As mentioned above, the LoginComponent will serve as the entry point into our application where users can login or signup. In /src/app/login/login.component.ts: import { Component, OnInit, HostBinding } from '@angular/core'; import { AngularFire, AuthProviders, AuthMethods } from 'angularfire2'; import { Router } from '@angular/router'; import { moveIn } from '../router.animations'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'], animations: [moveIn()], host: {'[@moveIn]': ''} }) Notice we're including HostBinding. That will help us later on in this tutorial when we add router animations. AngularFire, AuthProviders and AuthMethods is required for logging in and checking if a user is currently logged in. The Router will allow us to navigate to different pages based on what's being clicked. Later on, we will create a router.animations file that will export several animation functions that we'll create. This includes moveIn. In the component decorator, we add animations and host which is needed for router-based animations. In the LoginComponent class, add: error: any; constructor(public af: AngularFire,private router: Router) { this.af.auth.subscribe(auth => { if(auth) { this.router.navigateByUrl('/members'); } }); } loginFb() { this.af.auth.login({ provider: AuthProviders.Facebook, method: AuthMethods.Popup, }).then( (success) => { this.router.navigate(['/members']); }).catch( (err) => { this.error = err; }) } loginGoogle() { this.af.auth.login({ provider: AuthProviders.Google, method: AuthMethods.Popup, }).then( (success) => { this.router.navigate(['/members']); }).catch( (err) => { this.error = err; }) } So, we have three things happening here. In the constructor we're using dependency injection on AngularFire and the Router. Inside of it, we're checking when this app loads if a user is currently logged in. If so, we're sending them to the /members route (which, does not currently work). In both our loginFb() and loginGoogle() methods, we're authenticating users and if successful, we use the Router to send them to the /members page, and if not successful, we set an error property to the returned error response. Now let's define the /login.component.html template: <div class="form-container"> <img src="assets/images/lock.svg" id="lock"> <span class="error" *ngIf="error">{{ error }}</span> <button (click)="loginFb()" id="fb">Login With Facebook</button><br> <button (click)="loginGoogle()" id="google">Login With Google</button> <button routerLink="/login-email" id="email">Email</button> <a routerLink="/signup" routerLinkActive="active" class="alc">No account? <strong>Create one here</strong></a> </div> So we're checking for an error with ngIf, and if the error property exists, we use interpolation to show them the error. We also have buttons. We use event binding to call our methods on the Facebook and Google providers, and a routerLink to login with Email on the third.

SignupComponent Our SignupComponent is fairly straight forward. We allow users to join with an email and password. In the component imports and decorator, designate: import { Component, OnInit } from '@angular/core'; import { AngularFire, AuthProviders, AuthMethods } from 'angularfire2'; import { Router } from '@angular/router'; import { moveIn, fallIn } from '../router.animations'; @Component({ selector: 'app-signup', templateUrl: './signup.component.html', styleUrls: ['./signup.component.css'], animations: [moveIn(), fallIn()], host: {'[@moveIn]': ''} }) And in the component class: state: string = ''; error: any; constructor(public af: AngularFire,private router: Router) { } onSubmit(formData) { if(formData.valid) { console.log(formData.value); this.af.auth.createUser({ email: formData.value.email, password: formData.value.password }).then( (success) => { console.log(success); this.router.navigate(['/login']) }).catch( (err) => { console.log(err); this.error = err; }) } } The state property is something we'll use later on for our animations. onSubmit is the method called when a user attempts to join. It uses AngularFire's createUser and if it succeeds (.then) we send them to /login and if not, we bind this.error to the response. In the signup.component.html template: <div class="form-container"> <a routerLink="/login" id="goback">Go back</a> <h2>Join now</h2> <span class="error" *ngIf="error" [@fallIn]='state'>{{ error }}</span> <form #formData='ngForm' (ngSubmit)="onSubmit(formData)"> <input type="text" placeholder="Email address.." (ngModel)="email" name="email" class="txt" required> <input type="password" placeholder="Password" (ngModel)="password" name="password" class="txt" required> <button type="submit" [disabled]="!formData.valid" class="basic-btn">Create my account</button> </form> </div> Notice we're using [@fallIn]='state', this is for animation. You can remove it for now if you want, because this page will not work until we've created our animations file. And then we have a form for accepting email and password. You would probably want to add a second password for email verification in a real world setting; which is simple enough.

EmailComponent This component allows users to login via email and password. In the component decorator and imports: import { Component, OnInit } from '@angular/core'; import { AngularFire, AuthProviders, AuthMethods } from 'angularfire2'; import { Router } from '@angular/router'; import { moveIn, fallIn } from '../router.animations'; @Component({ selector: 'app-email', templateUrl: './email.component.html', styleUrls: ['./email.component.css'], animations: [moveIn(), fallIn()], host: {'[@moveIn]': ''} }) And in the class: state: string = ''; error: any; constructor(public af: AngularFire,private router: Router) { this.af.auth.subscribe(auth => { if(auth) { this.router.navigateByUrl('/members'); } }); } onSubmit(formData) { if(formData.valid) { console.log(formData.value); this.af.auth.login({ email: formData.value.email, password: formData.value.password }, { provider: AuthProviders.Password, method: AuthMethods.Password, }).then( (success) => { console.log(success); this.router.navigate(['/members']); }).catch( (err) => { console.log(err); this.error = err; }) } } This is quite similar to the SignupComponent as you can see. In the email.component.html template: <div class="form-container"> <a routerLink="/login" id="goback">Go back</a> <h2>Custom Login</h2> <span class="error" *ngIf="error" [@fallIn]='state'>{{ error }}</span> <form #formData='ngForm' (ngSubmit)="onSubmit(formData)"> <input type="text" placeholder="Email address.." (ngModel)="email" name="email" class="txt" required> <input type="password" placeholder="Password" (ngModel)="password" name="password" class="txt" required> <button type="submit" [disabled]="!formData.valid" class="basic-btn">Log in</button> <a routerLink="/signup" class="alc">Don't have an account?</a> </form> </div>

MembersComponent Now, let's designate the component code for our members area in /members/members.component.ts: import { Component, OnInit } from '@angular/core'; import { AngularFire, AuthProviders, AuthMethods } from 'angularfire2'; import { Router } from '@angular/router'; import { moveIn, fallIn, moveInLeft } from '../router.animations'; @Component({ selector: 'app-other', templateUrl: './other.component.html', styleUrls: ['./other.component.css'], animations: [moveIn(), fallIn(), moveInLeft()], host: {'[@moveIn]': ''} }) export class OtherComponent implements OnInit { name: any; state: string = ''; constructor(public af: AngularFire,private router: Router) { this.af.auth.subscribe(auth => { if(auth) { this.name = auth; } }); } logout() { this.af.auth.logout(); console.log('logged out'); this.router.navigateByUrl('/login'); } ngOnInit() { } } There are a few things different happening here, although nothing too drastic. First, we're importing a third animation function which will really allow us to add some interesting animations to the UI once it's activated. In the constructor we're binding a this.name property to a returned auth object which will allow us to greet the logged in user with their name that Firebase returns. And we also have a logout() method that logs the user out and sends them back to the /login page. In the members.component.html: <div class="form-container" id="toolbar"> <header [@fallIn]="state"> <button (click)="logout()" class="basic-btn">Logout</button> </header> <div id="page" [@moveInLeft]="state"> <h2>Hey {{ name.auth.displayName }}!</h2> <img src="assets/images/filler.png" /> </div> </div> We just have a simple header, along with a button for logging out calling the logout() method, and we're using name.auth.displayName to show their display name. If a user is logged in via email however, this will be null. So you could decide to note show this property if that is the case.

AppComponent In app.component.html change it to: <router-outlet></router-outlet>

Creating an AuthGuard Service This AuthGuard service will be used to protect our /members page in the routes that we'll define after this step. At the time of writing this tutorial, the angular-cli does not yet support generating services. So create a auth.service.ts file in /src/app with the following contents: import { CanActivate, Router } from '@angular/router'; import { AngularFireAuth } from "angularfire2/angularfire2"; import { Injectable } from "@angular/core"; import { Observable } from "rxjs/Rx"; import 'rxjs/add/operator/do'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/take'; @Injectable() export class AuthGuard implements CanActivate { constructor(private auth: AngularFireAuth, private router: Router) {} canActivate(): Observable<boolean> { return Observable.from(this.auth) .take(1) .map(state => !!state) .do(authenticated => { if (!authenticated) this.router.navigate([ '/login' ]); }) } } canActivate decides if a designated route can be accessed, such as our /members component. It's checking authentication from an AngularFireAuth observable. If it's not authenticated (!authenticated), then they're denied and directed back to /login .

Creating the Routes Create app.routes.ts in /src/app/ with the following: import { ModuleWithProviders } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AppComponent } from './app.component'; import { LoginComponent } from './login/login.component'; import { OtherComponent } from './other/other.component'; import { AuthGuard } from './auth.service'; import { SignupComponent } from './signup/signup.component'; import { EmailComponent } from './email/email.component'; export const router: Routes = [ { path: '', redirectTo: 'login', pathMatch: 'full' }, { path: 'login', component: LoginComponent }, { path: 'signup', component: SignupComponent }, { path: 'login-email', component: EmailComponent }, { path: 'members', component: OtherComponent, canActivate: [AuthGuard] } ] export const routes: ModuleWithProviders = RouterModule.forRoot(router); Notice the path members has canActiviate: [AuthGuard] - It's as simple as that to protect a given route!

App.module.ts Going back to our /app.module.ts you need to ensure that the AuthGuard and routes are included: import { AuthGuard } from './auth.service'; import { routes } from './app.routes'; And inside the @NgModule decorator: @NgModule({ ... imports: [ ... AngularFireModule.initializeApp(firebaseConfig), routes ], providers: [AuthGuard], ... })

Creating Animations In order for our animations to work, we need to create a router.animations.ts in /src/app/ with the following: import {trigger, state, animate, style, transition} from '@angular/core'; export function moveIn() { return trigger('moveIn', [ state('void', style({position: 'fixed', width: '100%'}) ), state('*', style({position: 'fixed', width: '100%'}) ), transition(':enter', [ style({opacity:'0', transform: 'translateX(100px)'}), animate('.6s ease-in-out', style({opacity:'1', transform: 'translateX(0)'})) ]), transition(':leave', [ style({opacity:'1', transform: 'translateX(0)'}), animate('.3s ease-in-out', style({opacity:'0', transform: 'translateX(-200px)'})) ]) ]); } export function fallIn() { return trigger('fallIn', [ transition(':enter', [ style({opacity:'0', transform: 'translateY(40px)'}), animate('.4s .2s ease-in-out', style({opacity:'1', transform: 'translateY(0)'})) ]), transition(':leave', [ style({opacity:'1', transform: 'translateX(0)'}), animate('.3s ease-in-out', style({opacity:'0', transform: 'translateX(-200px)'})) ]) ]); } export function moveInLeft() { return trigger('moveInLeft', [ transition(':enter', [ style({opacity:'0', transform: 'translateX(-100px)'}), animate('.6s .2s ease-in-out', style({opacity:'1', transform: 'translateX(0)'})) ]) ]); } There's a lot happening here so I won't describe everything that's happening. However, it took a lot of experimenting on my part to get these animations to work correctly with the right properties! The moveIn() function is what allows for animating the components that are routed in the router-outlet. Hat tip to Gerard Sans for this post, which helped me figure this out.

CSS & Remaining Bits Now, we just need to define some CSS and also make a few adjustments to our index.html In /login.component.css #lock { width:40%; margin: 1.5em auto 4em auto; display:block; } #fb { background:#3B5998 url('assets/images/facebook.svg') no-repeat 14px 6px; background-size: 47px; color:#fff; } #google { border: 1px solid #95989A; background: #fff url('assets/images/google.svg') no-repeat 25px; background-size: 25px; } #email { background: #ECECEC url('assets/images/email.svg') no-repeat 25px; background-size: 25px; } @media (max-width: 600px) { #page { padding:1em; } #toolbar { width:90%; margin-left: -45%; } #fb { background:#3B5998; } #google { background: #fff; } #email { background: #ECECEC; } } members #toolbar { padding:0; width:70%; margin-left:-35%; } header { background:#3B8598; width:100%; } .basic-btn { width:100px; margin:0; } #page { padding:3em; margin:0; } #page img { margin-top:30px; } h2 { margin:0; } @media (max-width: 600px) { #page { padding:1em; } #toolbar { width:90%; margin-left: -45%; } } /src/styles.css body { background:#E2E4E6; padding-top:4em; } .form-container { background:white; padding:3.5em; width:500px; position:fixed; left:50%; margin-left:-250px; } button { padding:1.2em; width:100%; cursor:pointer; margin-bottom:15px; font-size:1.3em; } .basic-btn { background: #3B8598; color:white; } input.txt { background:#fff !important; padding:1.3em 1em; font-size:1.3em; border: 1px solid #BBBBBB; } h2 { margin: 1.7em 0 .9em 0; } .alc { text-align:center; display:block; margin: 15px 0; } .error { background:#f1f0ef; padding:1em; width:100%; display:block; margin-bottom:20px; } #goback { font-weight:bold; text-transform:uppercase; font-size:.8em; color:#3B8598; } #goback span { font-size:1em; } .loading { width: 30px; height: 30px; border: 5px solid #ccc; position: fixed; left: 50%; margin-left: -20px; top: 50%; margin-top: -20px; border-radius: 50%; } .loading:after { content: ''; position: absolute; width: 40px; height: 10px; background: #E2E4E6; top: 10px; left: -5px; animation: spin 1.2s infinite; } @keyframes spin { 100% { transform: rotate(360deg); } } @media (max-width: 600px) { body { padding-top:1.2em; } .form-container { padding:1.2em; width:90%; margin-left:-45%; } button { font-size:1em; } } And in index.html within the <head> tags: <link media="all" type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.3.0/css/foundation-flex.min.css"> <script src="https://cdnjs.cloudflare.com/ajax/libs/web-animations/2.2.2/web-animations.min.js"></script> We're including the Foundation 6 Flex grid, though you can choose to replace this with your choice of CSS framework, as well as a polyfill that will allow our animations to work on most other browsers. Also, change: <app-root><div class="loading"></div></app-root> I have included a custom loader.

Run it! Now it's time to give it a go! In the console, simply type: ng serve And visit your browser at http://localhost:4200. With any luck, your logins should work.