Simplifying Client-Side Authentication with Firebase and SvelteKit
Authentication tasks can often be a complex and challenging aspect of web development. If you’ve never worked with Firebase before, you’re in luck. In this article, I’ve created a sample repository that’s not just for learning purposes but can also be used in a production environment. We’ll explore how to use Firebase Authentication seamlessly with SvelteKit and TypeScript, making the authentication process smoother and more efficient.
Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app. It supports authentication using passwords, phone numbers, popular federated identity providers like Google, Facebook and Twitter, and more.
Understanding Firebase Authentication
Before diving into the implementation details, let’s briefly discuss what Firebase Authentication offers. Firebase Authentication is a robust backend service that provides developers with user authentication capabilities. It offers an easy-to-use SDK and pre-built UI libraries that allow you to authenticate users to your web application effortlessly. Firebase supports a wide range of authentication methods, including email/password, phone numbers, and federated identity providers like Google, Facebook, and Twitter.
Creating a SvelteKit project
To begin, we’ll set up a new SvelteKit project. If you’re new to SvelteKit, you can quickly create a project using the following commands:
npm create svelte@latest my-app
cd myapp
Install Firebase Emulator
Next, we’ll integrate Firebase into our SvelteKit project. Firebase provides a suite of services, and for this article, we’re focusing on Firebase Authentication. But before we proceed, we’ll set up Firebase Emulators for local development. Firebase Emulators allow us to test authentication locally, ensuring smooth development.
To set up Firebase Emulators, you need to install the Firebase CLI and initialize your Firebase project:Local development with Local Emulator Suite can be a good fit for your evaluation, prototyping, development and continuous integration workflows.
Installing Firebase CLI
npm install -g firebase-tools
Or you can download CLI manually.
After installing Firebase CLI, you need to login to your Google account.
firebase login
Then, you need to initialize Firebase project in your project directory.
firebase init
During the initialization process, choose Firebase Emulators, and select the Firebase project you want to use. Also, make sure to select the Authentication emulator.
After installation we will be ready to use Firebase Auth Emulator.
Install Firebase
In your project folder run the following command.
npm install firebase
After the installation, create a firebase.client.ts file in src/lib folder.
import { initializeApp } from 'firebase/app';
import { connectAuthEmulator, getAuth } from 'firebase/auth';
import type { FirebaseApp } from 'firebase/app';
import type { Firestore } from 'firebase/firestore';
import type { Auth } from 'firebase/auth';
import { browser } from '$app/environment';
export let db: Firestore;
export let app: FirebaseApp;
export let auth: Auth;
const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
useEmulator: import.meta.env.VITE_FIREBASE_USE_EMULATOR === 'true',
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN
};
export const initializeFirebase = () => {
if (!browser) {
throw new Error("Can't use the Firebase client on the server.");
}
if (!app) {
app = initializeApp(firebaseConfig);
auth = getAuth(app);
if (firebaseConfig.useEmulator) {
connectAuthEmulator(auth, 'http://127.0.0.1:9099');
}
}
};
This is our base code to initialize Firebase for our project. All the config variables we are going to need will be in .env file.
VITE_FIREBASE_API_KEY=API_KEY_HERE
VITE_FIREBASE_APP_ID=APP_ID_HERE
VITE_FIREBASE_USE_EMULATOR=true # true in development, false in production
VITE_FIREBASE_AUTH_DOMAIN=AUTH_DOMAIN_HERE
Now to invoke this initializeFirebase function in our project, let’s create a +layout.ts file in src/routes and add our load function that we are going to use on +layout.svelte page.
/** @type {import('./$types').LayoutLoad} */
import { initializeFirebase, auth } from '$lib/firebase.client';
import { browser } from '$app/environment';
import { onAuthStateChanged } from 'firebase/auth';
export async function load({ url }) {
if (browser) {
try {
initializeFirebase();
} catch (ex) {
console.error(ex);
}
}
function getAuthUser() {
return new Promise((resolve) => {
onAuthStateChanged(auth, (user) => resolve(user ? user : false));
});
}
return {
getAuthUser: getAuthUser,
url: url.pathname
};
}
Now our load function will get auth user on auth state changed. If user is not logged in, it will return false on mount.
Creating the State
To manage the authentication state in your SvelteKit app, we’ll create a session.ts
file in the src/lib
folder. This file will define the session state and export a session store.
import { writable, type Writable } from 'svelte/store';
type User = {
email?: string | null;
displayName?: string | null;
photoURL?: string | null;
uid?: string | null;
};
export type SessionState = {
user: User | null;
loading?: boolean;
loggedIn?: boolean;
};
export const session = <Writable<SessionState>>writable();
Using Layout page
Now to use our load function, let’s create +layout.svelte page
<script lang="ts">
import { onMount } from 'svelte';
import { session } from '$lib/session';
import { goto } from '$app/navigation';
import { signOut } from 'firebase/auth';
import { auth } from '$lib/firebase.client';
import type { LayoutData } from './$types';
export let data: LayoutData;
let loading: boolean = true;
let loggedIn: boolean = false;
session.subscribe((cur: any) => {
loading = cur?.loading;
loggedIn = cur?.loggedIn;
});
onMount(async () => {
const user: any = await data.getAuthUser();
const loggedIn = !!user && user?.emailVerified;
session.update((cur: any) => {
loading = false;
return {
...cur,
user,
loggedIn,
loading: false
};
});
if (loggedIn) {
goto('/');
}
});
</script>
<!-- +layout.svelte -->
{#if loading}
<div>Loading...</div>
{:else}
<div>
Logged in: {loggedIn}
<slot />
</div>
{/if}
Building a Login Page
We’ll start by creating a basic login form. In your Svelte page, you can create the login form and handle authentication actions.
<!-- login/+page.svelte -->
<div class="login-form">
<h1>Login</h1>
<form on:submit={loginWithMail}>
<input bind:value={email} type="text" placeholder="Email" />
<input bind:value={password} type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
<div>or</div>
<button on:click={loginWithGoogle}>Login with Google</button>
<div>Don't you have an account? <a href="/register"> Register</a></div>
</div>
In the script part of our Svelte page, we are going to add our functionality.
<script lang="ts">
// login/+page.svelte
import { session } from '$lib/session';
import { auth } from '$lib/firebase.client';
import {
GoogleAuthProvider,
signInWithPopup,
signInWithEmailAndPassword,
type UserCredential
} from 'firebase/auth';
import { goto } from '$app/navigation';
let email: string = '';
let password: string = '';
async function loginWithMail() {
await signInWithEmailAndPassword(auth, email, password)
.then((result) => {
const { user }: UserCredential = result;
session.set({
loggedIn: true,
user: {
displayName: user?.displayName,
email: user?.email,
photoURL: user?.photoURL,
uid: user?.uid
}
});
goto('/');
})
.catch((error) => {
return error;
});
}
async function loginWithGoogle() {
const provider = new GoogleAuthProvider();
await signInWithPopup(auth, provider)
.then((result) => {
const { displayName, email, photoURL, uid } = result?.user;
session.set({
loggedIn: true,
user: {
displayName,
email,
photoURL,
uid
}
});
goto('/');
})
.catch((error) => {
return error;
});
}
</script>
Building a Register Page
<script lang="ts">
// register/+page.svelte
import { auth } from '$lib/firebase.client';
import { createUserWithEmailAndPassword } from 'firebase/auth';
import { goto } from '$app/navigation';
import { session } from '$lib/session';
let email: string = '';
let password: string = '';
async function handleRegister() {
await createUserWithEmailAndPassword(auth, email, password)
.then((result) => {
const { user } = result;
session.update((cur: any) => {
return {
...cur,
user,
loggedIn: true,
loading: false
};
});
goto('/');
})
.catch((error) => {
throw new Error(error);
});
}
</script>
<!-- register/+page.svelte -->
<div class="register-form">
<form on:submit={handleRegister}>
<h2>Register</h2>
<input bind:value={email} type="text" placeholder="Email" />
<input bind:value={password} type="password" placeholder="Password" />
<button type="submit">Register</button>
</form>
</div>
In this article, we’ve walked through the process of integrating Firebase Authentication with a SvelteKit application. We’ve covered setting up Firebase Emulators for local development, initializing Firebase in your project, managing authentication state, and implementing login and registration features. This combination of Firebase and SvelteKit offers a powerful and streamlined solution for client-side authentication.
As you continue to work on your project, you can build upon these foundations and explore additional Firebase features to enhance the user experience and security of your web application.
By following these steps, you can simplify client-side authentication and create a secure and user-friendly web application using Firebase and SvelteKit.
GitHub Repository: SvelteKit Firebase Authentication Example
In this example project, you’ll see how all the pieces come together. You can examine the project structure, review the implementation of authentication features, and use it as a reference for your own projects.
Here are some key points about the example project:
- It showcases the integration of Firebase Authentication with SvelteKit, just as we discussed in this article.
- You can explore how Firebase Emulators are set up for local development.
- The project includes login and registration pages with email/password and Google authentication methods.
- The management of authentication state using Svelte’s stores is also demonstrated.
- Feel free to clone, fork, or download the project for your own use. It’s a practical resource to accelerate your development when working on projects that require user authentication.
By examining the example project alongside this article, you’ll gain a more comprehensive understanding of how to implement Firebase Authentication with SvelteKit effectively.
Happy coding!