Angular Firebase Authentication - Implement Auth in Minutes
In this post, you'll discover Angular Firebase authentication, its benefits, and how to set it up properly in an Angular project.
What Is Firebase?
Firebase is a service provided by Google that allows us to build projects without needing to worry about setting up a backend or database. For instance, in a previous post, I demonstrated how to utilize Firebase to create a database and integrate it into an Angular application.
Now, we're taking the next step by implementing authentication using Firebase. This means we can write our code within Angular without the need for backend or database implementation, as Firebase handles it all. This makes it incredibly simple to develop projects without requiring expertise in areas you may not be familiar with yet.
Moreover, Firebase authentication offers various features such as email verification, registration, login, password recovery, and more, all implemented correctly within the Firebase platform.
Creating Project in Firebase
First, create an account on Firebase. Then, proceed to create a new project.
Once the project is created, select "No" when prompted to enable Google Analytics. Next, navigate to the "Authentication" section under the "Build" tab on the left. Here, you'll find various authentication providers such as Email, Google, Facebook, and Twitter. For this post, we'll focus solely on email/password authentication. Enable email/password authentication but refrain from enabling passwordless sign-in.
After enabling email/password authentication, you can proceed to the "Users" tab and create your first user.
Great! Your first user is now saved in Firebase.
The project
With the configuration complete, you're now ready to integrate Firebase into your Angular project. Let's get started!
Our project now consists of two pages: a registration page and a login page, each containing simple forms. At this stage, both components lack any additional logic.
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
standalone: true,
imports: [ReactiveFormsModule],
})
export class RegisterComponent {
fb = inject(FormBuilder);
http = inject(HttpClient);
router = inject(Router);
form = this.fb.nonNullable.group({
username: ['', Validators.required],
email: ['', Validators.required],
password: ['', Validators.required],
});
onSubmit(): void {
console.log('register');
}
}
Here's what the Register
component looks like.
Configuring Firebase
The initial step is to install our dependencies.
npm i @angular/fire
The next step is to configure the Angular library for Firebase. To do this, we need to obtain the API keys from Firebase. Begin by clicking on "Create a new web project" and enter any desired name. Then, click on "Register app".
After completing these steps, you'll receive a firebaseConfig
object that you can directly copy into your application. This configuration contains the necessary API keys and other settings required for Firebase integration.
// src/app.config.ts
const firebaseConfig = {
apiKey: 'API_KEY',
authDomain: 'AUTH_DOMAIN',
projectId: 'PROJECT_ID',
storageBucket: 'STORAGE_BUCKET',
messagingSenderId: 'MESSAGING_SENDER_ID',
appId: 'APP_ID'
}
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
importProvidersFrom([
provideFirebaseApp(() => initializeApp(firebaseConfig)),
provideAuth(() => getAuth())
])
],
};
Here, we've initialized the Firebase application and Firebase authentication. We then provided this configuration, including the correct firebaseConfig
, to our appConfig
.
Registering a User
The initial step is to create an interface for the user object that we'll be working with.
// src/app/user.interface.ts
export interface UserInterface {
email: string;
username: string;
}
Now, we're prepared to create a service that will interact with Firebase.
// src/auth.service.ts
export class AuthService {
firebaseAuth = inject(Auth)
register(email: string, username: string, password: string): Observable<void> {
const promise = createUserWithUsernameAndPassword(
this.firebaseAuth,
email,
password
).then(response => updateProfile(response.user, {displayName: username}))
return from(promise)
}
}
Firstly, we injected firebaseAuth
into our AuthService
. This service stores all our API keys, allowing us to make requests through it. We've also added a register
method that must return an Observable. Since Firebase returns a Promise, we convert it to an Observable to align with Angular's preferred pattern.
Since it's not possible to set a username when creating a user, we use the updateProfile
method upon success to achieve this.
Next, we'll utilize this function in our RegisterComponent
.
export class RegisterComponent {
authService = inject(AuthService)
...
onSubmit(): void {
const rawForm = this.form.getValue()
this.authService
.register(rawForm.email, rawForm.username, rawForm.password)
.subscribe(() => {
this.router.navigateByUrl('/')
})
}
}
Now, you can navigate to our registration page, sign up, and be directed to the homepage upon successful registration. Checking your Firebase admin panel will reveal a new user created there. This confirms that our registration code is functioning correctly.
Adding Validation
It seems we have encountered an issue.
It appears that when clicking "register" on the empty form, an unhandled error occurs stating that "Email is invalid". To address this, we need to catch this error and display validation messages instead.
export class RegisterComponent {
...
errorMessage: string | null
onSubmit(): void {
const rawForm = this.form.getValue()
this.authService
.register(rawForm.email, rawForm.username, rawForm.password)
.subscribe({
next: () => {
this.router.navigateByUrl('/')
},
error: (err) => {
this.errorMessage = err.code
}
})
}
}
We've added error handling to our subscribe method, capturing any errors and setting them to the errorMessage
property. Now, we can render this error message in the markup.
@if (errorMessage) {
<div>{{errorMessage}}</div>
}
...
As you can observe, we're now receiving an error message instead of an uncaught error in the console.
Adding User Login
Before we proceed with implementing login functionality, we need to create a method to call Firebase correctly.
// src/auth.service.ts
export class AuthService {
firebaseAuth = inject(Auth)
...
login(email: string, password: string): Observable<void> {
const promise = signInWithUsernameAndPassword(
this.firebaseAuth,
email,
password
)
return from(promise)
}
}
Here, we've implemented a similar method to the registration process, but this time we're calling signInWithUsernameAndPassword
instead. As before, we're converting a Promise to an Observable.
We can now copy and paste the entire code from the registration component to the login component, as the logic is very similar.
export class LoginComponent {
...
authService = inject(AuthService)
errorMessage: string | null
onSubmit(): void {
const rawForm = this.form.getValue()
this.authService
.login(rawForm.email, rawForm.password)
.subscribe({
next: () => {
this.router.navigateByUrl('/')
},
error: (err) => {
this.errorMessage = err.code
}
})
}
}
The concept remains consistent: we call the login
method, passing the form data, and navigate the user upon successful login. In case of an error, we display an error message.
@if (errorMessage) {
<div>{{errorMessage}}</div>
}
...
And remember to update your markup accordingly.
As demonstrated in the browser, we can now log in to our application using existing credentials.
Persisting a User
The reason why our register
and login
methods return void
instead of user information is because the typical approach involves retrieving a user token and storing it in local storage or a cookie for persistence. However, we haven't implemented this step because Firebase automatically handles it for us.
As evident in IndexedDB, Firebase stores the user token, which it utilizes to fetch user information.
Firebase automatically handles the storage of this token, requiring no additional code from us.
This implies that even after a page reload, Firebase automatically authenticates our user, and all we need to do is read this information.
Firebase provides a user data stream that we can subscribe to, allowing us to receive this information with every update.
export class AuthService {
firebaseAuth = inject(Auth)
user$ = user(this.firebaseAuth)
currentUserSig = signal<UserInterface | undefined>(undefined)
}
In our AuthService
, we've created a user$
observable to capture all new values of the authenticated user. Additionally, we've introduced a currentUserSig
signal, which we intend to utilize throughout the application. Why not simply use user$
? While user$
contains various properties that we may not need, I prefer using signals for consistency across the entire project.
// src/app.component.ts
export class AppComponent implements OnInit {
authService = inject(AuthService)
ngOnInit(): void {
this.authService.user$.subscribe(user => {
if (user) {
this.authService.currentUserSig.set({
email: user.email!,
username: user.displayName
})
} else {
this.authService.currentUserSig.set(undefined)
}
})
}
}
In our AppComponent
, after each page reload, we subscribe to the user stream and update our signal accordingly based on the received data. This ensures that the entire application is notified whenever a user logs in or logs out.
Now, let's update our markup in app.component.html
.
@if (!authService.currentUserSig()) {
<div>
<a routerLink="/login">Login</a>
<a routerLink="/register">Register</a>
</div>
}
@if (!authService.currentUserSig()) {
<div>
<span (click)="logout()">Logout</span>
</div>
}
Now, we can render the correct links based on the user's authentication state.
Logout
The final step is to implement the logout functionality.
export class AuthService {
logout(): Observable<void> {
const promise = signOut(this.firebaseAuth);
return from(promise)
}
}
Here, we simply call the signOut
method and convert it to a promise.
logout(): void {
this.authService.logout()
}
In our app.component.ts
, we can call the service when the logout button is clicked.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done