How To Create An Email and Password Login Using Pocketbase and SvelteKit

No Comments
Published: 14.01.2024

Learn how to create an authentication system to login and register with email and password using Pocketbase and SvelteKit. For this, we will first create the register flow and then the login flow. We will connect these screens with the backend using form actions.

Before we start with this part, make sure that you have set up an SMTP in Pocketbase, as we will need to send emails both for verification and in case of a forgotten password. If you have not done it yet, check this post here!

So let’s get started!

This is the third part of my free Pocketbase course, check the introduction and a list of all posts here.

Don’t want to read? Watch the video instead!

Set up an email and password registration flow using Pocketbase and SvelteKit

Before we can log in as a user, we have to create one. Therefore we will start with the registration flow using email and password registration. To begin, we need to create a new route called src/routes/login with both +page.svelte and +page.server.ts files.
We will start with a basic UI for the login and registration inside the +page.svelte:

<script lang="ts">
    import type { PageData } from './$types';

    export let data: PageData;
</script>

<div class="h-full flex flex-col justify-center sm:mx-auto sm:w-full sm:max-w-sm">
    <form action="?/login" method="post">
        <label class="label">
            <span>E-Mail</span>
            <input class="input" name="email" title="E-Mail" type="email" placeholder="mail@example.com" />
        </label>
        <label class="label mt-4">
            <span>Password</span>
            <input class="input" name="password" title="Password" type="password" placeholder="mail@example.com" />
        </label>
        <button class="block ml-auto hover:underline my-2" formnovalidate formaction="?/reset">Reset Password</button>
        <button class="btn variant-filled w-full mt-4" type="submit">Login</button>
        <button class="btn variant-filled w-full mt-4 mb-2" type="button" formaction="?/register">Register</button>
    </form>
</div>

In the HTML, you can first see the action ?/login as part of the form element and then the actions ?/register and ?/reset as part of the buttons. These actions will be connected to a form action in the next step, and the name after ?/ refers to the name of the action.

Need help or want to share feedback? Join my discord community!

So let’s create the register action inside of +page.server.ts. The action consists of reading the form data and then doing the registration of the user:

import { fail, redirect } from '@sveltejs/kit';
import type { ClientResponseError } from 'pocketbase';

export const actions = {
    register: async ({ locals, request }) => {
        const data = await request.formData();
        const email = data.get('email');
        const password = data.get('password');

        if (!email || !password) {
            return fail(400, { emailRequired: email === null, passwordRequired: password === null });
        }

        data.set('passwordConfirm', password?.toString())        
        try {
            await locals.pb.collection('users').create(data);
            await locals.pb.collection('users').authWithPassword(email, password.toString());
            await locals.pb.collection('users').requestVerification(email);
        } catch (error) {
            const errorObj = error as ClientResponseError;
            return fail(500, {fail: true, message: errorObj.data.message});
        } 

        throw redirect(303, '/dashboard');
    }
}

If everything is successful, we will redirect the user to the route /dashboard. In a later post we will create the expense tracker dashboard in this route. Besides this, we added some error handling, which is visible through the lines with return fail(...). To visualize these errors for the user, we can add the following code to our +page.svelte component:

KOFI Logo

If this guide is helpful to you and you like what I do, please support me with a coffee!

<script lang="ts">
    import type { ActionData, PageData } from './$types';

    export let data: PageData;
    export let form: ActionData;
</script>

<div class="h-full flex flex-col justify-center sm:mx-auto sm:w-full sm:max-w-sm">
    {#if form?.fail}
        <div class="variant-soft-error px-4 py-2 mb-2 rounded-token">
            {form.message}
        </div>
    {/if}
    // ...
</div>

Set up an email and password login flow using Pocketbase and SvelteKit

Next, we will start with the login flow using email and password. We have already created the UI in the last step, so we just need to create a new form action for the login flow. For this, we add the following to the action object inside the +page.server.ts file:

login: async ({ locals, request }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    if (!email || !password) {
        return fail(400, { emailRequired: email === null, passwordRequired: password === null });
    }

    try {
        await locals.pb.collection('users').authWithPassword(email.toString(), password.toString());
    } catch (error) {
        const errorObj = error as ClientResponseError;
        return fail(500, {fail: true, message: errorObj.data.message});
    }

    throw redirect(303, '/dashboard');
}

It is really similar to the registration flow, just without the user creation and verification request.

Redirect from login

For the next part, we do not want the logged-in user to access the login page before he signs out again. Therefore, we will “protect” a single route by checking if the user is authenticated and, if this is the case, redirecting him to the /dashboard route.
To do this, we will add a load function to the src/routes/login/+page.server.ts file with the following content:

import type { PageServerLoad } from './$types';

export const load = (async ({locals}) => {    
    if (locals.pb.authStore.model) {
        return redirect(303, '/dashboard')
    }

    return {};
}) satisfies PageServerLoad;

With that, we created a lock for the /login route for logged-in users!

Set up a password-forgotten page

In case a user forgot their password we need to give them the ability to do so. As we already specified the reset password button, we only need to add the following action to our existing actions in src/routes/login/+page.server.ts:

reset: async ({ locals, request }) => {
    const data = await request.formData();
    const email = data.get('email');

    if (!email) {
        return fail(400, { emailRequired: email === null });
    }

    try {
        await locals.pb.collection('users').requestPasswordReset(email.toString());
    } catch (error) {
        const errorObj = error as ClientResponseError;
        return fail(500, {fail: true, message: errorObj.data.message});
    }

    throw redirect(303, '/login');
}

And with that, our users are also able to reset their password!

You can also access the current state of the project in this repository and the branch called “course-3”.

Conclusion

In this post, we created an email and password based login and registration with Pocketbase and SvelteKit. For that, we first created the base screen, and then we set up the registration and log-in flows. In the next post, we will extend this with an OAuth-based flow! I hope you liked the course so far. See you in the next part!

And as always, if you have any questions… ask! 😀

If you liked this post and if you want to stay up to date with all my posts, consider subscribing to my monthly newsletter!

[convertkit form=2303042]

Discussion (0)