Search Lessons, Code Snippets, and Videos
search by algolia
X
#native_cta# #native_desc# Sponsored by #native_company#

Firebase OAuth Login With Custom Firestore User Data

Episode 55 written by Jeff Delaney
full courses for pro members

Health Check: This lesson was last reviewed on and tested with these packages:

  • Angular v6.0.3
  • RxJS v6.2
  • @angular/fire v5

Update Notes: AngularFire2 has been moved under the official Angular repo. The lesson has upgraded its packages from angularfire2 to @angular/fire.

Find an issue? Let's fix it

Source code for Firebase OAuth Login With Custom Firestore User Data on Github

In this lesson, I am going to show you how to build an OAuth authentication feature using the Google sign-in method. We will also save custom user data to the Firestore database, making it possible to manage custom user data and/or loop over all your users at once.

Step 1: Activate OAuth Providers in Firebase

I am using the Google auth provider because it works without any additional configuration. You can follow the same exact steps for Facebook, Github, and Twitter, but you will need to get credentials from a developer account for each app you plan on using.

Make sure to active the social providers you want to use in Firebase.

Step 2: Install AngularFire v5 and Create a Core Module

Make sure you have @angular/fire (formerly angularfire2) v5 installed in your Angular app. Following the initial install instructions here if starting from scratch. If you are upgrading an existing Angular project, make sure the AngularFire version in package.json is at least up to 5.0.0.

Generate a Core Module (Optional)

Although technically optional, it is a good design pattern to keep your authentication setup in a core module. The purpose of a core module is to provide services that your app will use globally, such as authentication, logging, toast messages, etc.

ng g module core

Add the AngularFire Firestore and Auth modules to your core module (unless they are already in your app module). After you finish this lesson, the core module should look something like this.

import { NgModule } from '@angular/core';

import { AuthService } from './auth.service';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';

@NgModule({
imports: [
AngularFireAuthModule,
AngularFirestoreModule
],
providers: [AuthService]
})
export class CoreModule { }

Then add the core module to your app.module imports section.

import { CoreModule } from './core/core.module';
// ...

@NgModule({
// ... omitted
imports: [
BrowserModule,
AppRoutingModule,
AngularFireModule.initializeApp(yourConfig),
CoreModule, // <-- add core module
]
})
export class AppModule { }

Step 3: Auth Service

demo of how to save custom user data to the firestore database

ng g service core/auth

The auth service is where most of the magic happens. It facilitates the sign-in process, watches the user session, and allows us to save custom user data to the Firestore database. Here’s a breakdown of everything that is happening in the code.

  • interface User: The interface declares the properties of the custom user object. Feel free to add any custom data you want here to extend the basic Firebase auth data.
  • constructor(): The constructor will set the Observable. First it receives the current Firebase auth state. If present, it will hit up Firestore for the user’s saved custom data. If null, it will return an Observable.of(null).
  • googleLogin(): This method triggers the popup window that authenticates the user with their Google account. It returns a Promise that resolves with the auth credential. The oAuthLogin() method is useful if you have multiple OAuth options because it can be reused with different providers.
  • updateUserData(): This private method runs after the user authenticates and sets their information to the Firestore database. We pass the { merge: true } option to make this a non-destructive set.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

import { auth } from 'firebase/app';
import { AngularFireAuth } from '@angular/fire/auth';
import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore';

import { Observable, of } from 'rxjs';
import { switchMap} from 'rxjs/operators';

interface User {
uid: string;
email: string;
photoURL?: string;
displayName?: string;
favoriteColor?: string;
}


@Injectable({ providedIn: 'root' })
export class AuthService {

user: Observable<User>;

constructor(
private afAuth: AngularFireAuth,
private afs: AngularFirestore,
private router: Router
) {

//// Get auth data, then get firestore user document || null
this.user = this.afAuth.authState.pipe(
switchMap(user => {
if (user) {
return this.afs.doc<User>(`users/${user.uid}`).valueChanges()
} else {
return of(null)
}
})
)
}

googleLogin() {
const provider = new auth.GoogleAuthProvider()
return this.oAuthLogin(provider);
}

private oAuthLogin(provider) {
return this.afAuth.auth.signInWithPopup(provider)
.then((credential) => {
this.updateUserData(credential.user)
})
}


private updateUserData(user) {
// Sets user data to firestore on login

const userRef: AngularFirestoreDocument<any> = this.afs.doc(`users/${user.uid}`);

const data: User = {
uid: user.uid,
email: user.email,
displayName: user.displayName,
photoURL: user.photoURL
}

return userRef.set(data, { merge: true })

}


signOut() {
this.afAuth.auth.signOut().then(() => {
this.router.navigate(['/']);
});
}
}

Step 4: Auth Guard

auth guard with firestore Angular router

ng g guard core/auth

The next thing we want to do is protect our routes from unauthenticated users. Now that we have an observable in the service, we can handle this task easily with a canActivate guard. We just pass it the observable of the user from the auth service. If it emits true, the route can be accessed. If false, the user is redirected to the login page.

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';

import { AuthService} from './auth.service'
import { Observable } from 'rxjs';
import { tap, map, take } from 'rxjs/operators';

@Injectable()
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router) {}


canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> {

return this.auth.user.pipe()
take(1),
map(user => !!user),
tap(loggedIn => {
if (!loggedIn) {
console.log('access denied')
this.router.navigate(['/login']);
}
})
)
}
}

You can use the guard in your router by simply adding it to the canActivate array for a given route, for example:

const routes: Routes = [
///...
{ path: 'notes', component: NotesListComponent, canActivate: [AuthGuard] },
];

Step 5: User Profile

As a final step, let’s create a user profile so you can see how to use the user Observable in the HTML.

ng g component user-profile

The component TypeScript just needs to have the auth service injected as a public property in the constructor.

import { Component } from '@angular/core';
import { AuthService } from '../../core/auth.service';

@Component({
selector: 'user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss']
})
export class UserProfileComponent {


constructor(public auth: AuthService) { }

}

In the component HTML, we have two separate templates that are shown conditionally based on the user Observable data. If it’s null, we show the guest template, but if it’s present we can show the authenticated template and corresponding user data.

I have also added a Google Login button to the profile, but you might consider making it a standalone component that you can use outside of the user profile.


<div *ngIf="auth.user | async; then authenticated else guest">
<!-- template will replace this div -->
</div>


<!-- User NOT logged in -->
<ng-template #guest>
<h3>Howdy, GUEST</h3>
<p>Login to get started...</p>

<button (click)="auth.googleLogin()">
<i class="fa fa-google"></i> Connect Google
</button>

</ng-template>


<!-- User logged in -->
<ng-template #authenticated>
<div *ngIf="auth.user | async as user">
<h3>Howdy, {{ user.displayName }}</h3>
<img [src]="user.photoURL">
<p>UID: {{ user.uid }}</p>
<p>Favorite Color: {{ user?.favoriteColor }} </p>
<button (click)="auth.signOut()">Logout</button>
</div>
</ng-template>

The End

That’s it for basic OAuth with Firebase. Now you can start saving custom user information to firestore to build a complex and robust user authentication system.