Firebase Authentication tutorial | Angularfire 7.4, Auth & Firestore | Google, Facebook, email and more! [2023]
In this article, you will learn how to create a completely free authorization panel using Firebase and AngularFire 7.4 in the easiest way! We will mainly focus on technical issues based on Angularfire and TypeScript.
#1 — Overview
We are gonna implement all auth panel logic using an Angularfire 7.4 library, in TypeScript. The presented snippets are for Angular but don't worry at all. The code is as simple as possible so you can use it whenever framework you want (or no framework at all) 👌👌.
Before we are gonna dive straight into code, you need to have already set up the Firebase project. We won’t focus on it here (there are a lot of tutorials about it), but please make sure your project has configured:
- Firebase Authentication providers: Email/Password and Google.
- Firestore Database.
All Firebase logic will be closed in a service called Auth Service. This service can be used wherever you want, directly in component, in some effect logic, in the facade, etc. For an article purpose, we are gonna present it in the most simple way— in components.
❗❗❗One more thing! All angularfire auth methods presented in this article, in case of failure, return an appropriate error on which we can react as we want — but error handling is not covered in this article.
So let’s get started!
#2— Register/sign in
First, let’s add methods for signing by Google and by email & password.
import { inject, Injectable } from '@angular/core';
import {
Auth,
GoogleAuthProvider,
signInWithPopup,
UserCredential
} from '@angular/fire/auth';
@Injectable({ providedIn: 'root' })
export class AuthService {
private _auth = inject(Auth);
byGoogle(): Promise<UserCredential> {
return signInWithPopup(this._auth, new GoogleAuthProvider());
}
signup(email: string, password: string): Promise<UserCredential> {
return createUserWithEmailAndPassword(
this._auth,
email.trim(),
password.trim()
);
}
}
The first method — byGoogle — opens a popup where you can pick your Google account. If you are not already logged into Google you can do it directly there or even create a new account.
Second one — signup — takes email and password as parameters and passes it into angularfire. Email is understood as a unique ID, so creation can fail if there is already a user with the same email.
NOTE. You don’t have to use a popup for OAuth providers, which can lead to bad UX. Instead, you can use signInWithRedirect function, which is even more recommended for mobile devices.
Wait, that’s it? Yup, exactly that. Now you can use these methods in your component, and see the magic happen 🥳.
@Component({
selector: 'app-register',
template: 'your fancy template here',
styles: [],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RegisterComponent {
private _service = inject(AuthService);
private _fb = inject(FormBuilder);
registerForm = this._fb.group( /* ...form controls here */ );
byGoogle(): void {
this._service
.byGoogle()
.then(() => /* some logic here */ )
.catch(() => /* some logic here */ );
}
byForm(): void {
const { email, password } = this.registerForm.value;
this._service
.signup(email!, password!)
.then(() => /* some logic here */ )
.catch(() => /* some logic here */ );
}
}
#3 — Login in
To make it even simpler I’ve got some good news. For OAuth providers (like Google) the methods work for both sign-in and log-in! Firebase does the magic for us, and for example, GoogleAuthProvider, can be used for signing new users, or logging into an existing one, cool!🤩
So we need only one more method — the old school way— login by email and password.
import {
Auth,
GoogleAuthProvider,
signInWithEmailAndPassword, // 👈👈👈 new
signInWithPopup,
UserCredential
} from '@angular/fire/auth';
@Injectable({ providedIn: 'root' })
export class AuthService {
...
login(email: string, password: string): Promise<UserCredential> {
return signInWithEmailAndPassword(
this._auth,
email.trim(),
password.trim()
);
}
}
Simple again — the method takes email and password and passes them to the angularfire method that asynchronously signs in by given parameters. It will fail if the email address and password do not match.
That’s it, now you can use it in component or whenever you want :)
#4 — Save the user
We already have basic auth logic, but in most cases, we would like to store user data in a database with — probably — a few more metadata, depending on our needs. So is it possible with Firebase? Sure, let’s add the data to the Firestore! 👍
First, let’s create a model for our User.
interface User {
id: string;
name: string;
email: string;
emailVerified: boolean;
// all field above (AND MANY MORE) are alrady provided by firebase
// let's add some new
platformId: string;
lang: string;
// ... and more and more
}
So now, we have to map the Firebase User to our model. We can do it in each method we want. Let’s see an example:
@Injectable({ providedIn: 'root' })
export class AuthService {
...
login(email: string, password: string): Promise<User> {
return signInWithEmailAndPassword(
this._auth,
email.trim(),
password.trim()
).then((auth) => this._setUserData(auth)); // 👈👈👈 new
}
private _setUserData(auth: UserCredential): Promise<User> {
const user: User = {
id: auth.user.uid,
name: (auth.user.displayName || auth.user.email)!,
email: auth.user.email!,
emailVerified: auth.user.emailVerified,
// custom ones
platformId: 'xyz',
lang: 'yzx';
};
// todo: saving logic
}
}
NOTE. If you want more user metadata, please check the UserCredential interface, provided by angularfire lib.
All we have left is to save the data to Firestore!
import { doc, Firestore, setDoc } from '@angular/fire/firestore'; // 👈👈👈 new
@Injectable({ providedIn: 'root' })
export class AuthService {
private _firestore = inject(Firestore); // 👈👈👈 new
private _setUserData(auth: UserCredential): Promise<User> {
...
// 👇👇👇 saving logic
const userDocRef = doc(this._firestore, `users/${user.id}`);
return setDoc(userDocRef, user).then(() => user);
// you can save some user data here to the other doc,
// by using firestore 'batch' functionality
}
}
What we did here, is set the whole new document (with user data) to a given path users/:userId
.
NOTE. We did a small trick here. Since angularfire setDoc method returns void, and we want actual user data, we mapped the response to previous data.
All AuthService code together.
import { inject, Injectable } from '@angular/core';
import { doc, Firestore, setDoc } from '@angular/fire/firestore';
import {
Auth,
GoogleAuthProvider,
signInWithPopup,
UserCredential
} from '@angular/fire/auth';
@Injectable({ providedIn: 'root' })
export class AuthService {
private _firestore = inject(Firestore);
private _auth = inject(Auth);
byGoogle(): Promise<UserCredential> {
// you can simply change the Google for another provider here
return signInWithPopup(this._auth, new GoogleAuthProvider()).then(
(auth) => this._setUserData(auth)
);
}
signup(email: string, password: string): Promise<UserCredential> {
return createUserWithEmailAndPassword(
this._auth,
email.trim(),
password.trim()
).then((auth) => this._setUserData(auth));
}
login(email: string, password: string): Promise<User> {
return signInWithEmailAndPassword(
this._auth,
email.trim(),
password.trim()
).then((auth) => this._setUserData(auth));
}
private _setUserData(auth: UserCredential): Promise<User> {
const user: User = {
id: auth.user.uid,
name: (auth.user.displayName || auth.user.email)!,
email: auth.user.email!,
emailVerified: auth.user.emailVerified,
// custom ones
lastRoute: string;
configId: string;
};
const userDocRef = doc(this._firestore, `users/${user.id}`);
return setDoc(userDocRef, user).then(() => user);
}
}
#5 —Last but not least! Auth state and other methods
Of course, signing in and login in is one thing, and knowing if the user is already logged in is the other thing. Angularfire to the rescue again!🙌
The lib has a logic that handles the auth state for us. Or more humanely, it tells us if the user is logged in or not. Of course, it also works with logging out.
import { inject, Injectable } from '@angular/core';
import {
Auth,
authState,
idToken,
user
} from '@angular/fire/auth';
@Injectable({ providedIn: 'root' })
export class AuthService {
private _auth = inject(Auth);
authState$ = authState(this._auth); // 👈👈👈
user$ = user(this._auth); // 👈👈👈
idToken$ = idToken(this._auth); // 👈👈👈
}
Each of these methods creates an observable we can use across our app/logic. The difference between them is:
- authState is only triggered on sign-in or sign-out,
- user/idToken — are triggered for sign-in, sign-out, and token refresh events. First returns the whole User object, second only ID.
Here is a list of useful angularfire Auth methods. All methods are available in docs (or look for them directly in code). Most of them you can use the same way as presented earlier in this article.
— createUserWithEmailAndPassword
Creates a new user account associated with the specified email address and password
— signInAnonymously / signInWithCredential / signInWithCustomToken / signInWithEmailAndPassword / signInWithEmailLink / signInWithPhoneNumber
All methods above asynchronous signs in using adequate logic. For example, signing by phone number sends an SMS to the given number, or signing anonymously creates a new user. Remember you need to set up these options in the Firebase console!
— signInWithPopup / signInWithRedirect
Authenticates a Firebase client using a popup-based OAuth authentication flow or a full-page redirect flow. Available OAuth providers:
- Facebook — FacebookAuthProvider,
- GitHub — GithubAuthProvider,
- Google — GoogleAuthProvider,
- Twitter — TwitterAuthProvider
Remember you need to set up these providers in the Firebase console!
—sendPasswordResetEmail
Sends a password reset email to the given email address.
—signOut
Signs out the current user.
#6 — Conclusions
As you already know, Angulrfire allows us to create really simple full auth logic for our app. It’s pretty intuitive and developer—friendly. Combining Firebase Auth and Firestore gives us a powerful setup and that’s just the beginning! There are a lot more features, that were not covered in this article. If you have any questions, please let me know!
Thanks for reading, cheers guys! 💪💪