How To Use Pocketbase Auth For A Login Screen (step-by-step)

No Comments

Need to add a login system to your application but not sure where to start? Pocketbase auth makes it easy! In this tutorial, we’ll guide you through the process of integrating Pocketbase’s email/password authentication provider into your project. Follow along and see how straightforward it is to add Pocketbase auth to your project.

Here is the link to the GitHub project. If you have any questions along the way, feel free to ask me via chat, ask a comment or send me a mail via mail@programonaut.com.

Project structure

The goal of this project is to create a login and sign-up screen that can be used in your web application. In the next guide, we will then extend the login system and screen with the functionality for a chat app (here).

For this project, we will use Pocketbase for the authentication and Svelte for the front end. The process and interaction should be the same for whatever framework you use.

The final project of this post will consist of a login/register screen that allows you to create a new user and then log in with that user and another screen that allows you to sign out again.

Use Pocketbase auth for a login screen

The project consists of three main stages. The first one is the setup of Pocketbase. The second one is the setup of svelte and tailwind for the front end, and the last step is the creation of the screens and the connection with Pocketbase.

Pocketbase setup

  1. Download the newest Pocketbase version here and unzip it.
  2. Run the executable using: ./pocketbase serve
  3. Optional: Set SMTP via Sendinblue, for example, for verification emails on account creation. I wrote a quick guide about it here.

Project setup

  1. npm create vite@latest pocketchat
    • Select Svelte and then Typescript
  2. cd supachat && npm i
  3. npm install pocketbase
  4. npm install -D tailwindcss postcss autoprefixer @tailwindcss/forms
  5. npx tailwindcss init -p
  6. Add the following to tailwind.config.cjs:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./src/**/*.{html,js,svelte,ts}'],
  theme: {
    extend: {}
  },
  plugins: [require('@tailwindcss/forms')]
};
  1. Add the following to src/app.css
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. Clear the boilerplate code and files, such as the content of src/App.svelte
  2. Create .env file with the URL to your Pocketbase instance:
VITE_POCKETBASE_URL=YOUR_POCKETBASE_URL
  1. Now we have to create src/lib/pocketbase.ts. It contains the pocketbase client and a svelte store containing the currently logged-in user. To get the user, we use a pocketbase function:
import PocketBase from 'pocketbase';
import { writable } from 'svelte/store';

export const pocketbase = new PocketBase(import.meta.env.VITE_POCKETBASE_URL);

export const currentUser = writable(pocketbase.authStore.model);

pocketbase.authStore.onChange(() => {
    currentUser.set(pocketbase.authStore.model);
});

export function errorMessage(error: unknown) {
    const errorObj = error as ClientResponseError;
    console.error(errorObj.message);
    return errorObj.message;
}

Create a login screen and connect it with Pocketbase auth

Now that we have the basic setup done, we will create the login screen based on this template by tailwind UI.

  1. Edit index.html and add the following classes to the html and body tags:
    <html class="h-full bg-gray-50">
    <body class="h-full">
  2. Create the file src/LoginScreen.svelte. After adding the following snippet to the file, we have the whole UI and all the necessary variables.
<script lang="ts">
  import { pocketbase, errorMessage } from "./lib/pocketbase";

  let email: string;
  let password: string;
  let loading: boolean = false;
  let register: boolean = false;
  let message: string = "";

  const handleLogin = async () => {};
</script>

<div class="flex min-h-full items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
  <div class="w-full max-w-md space-y-8">
    <div>
      <img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" alt="Your Company" />
      <h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">Sign in to your account</h2>
    </div>
    <!-- Added form action -->
    <form class="mt-8 space-y-6" on:submit|preventDefault={handleLogin}>
      <input type="hidden" name="remember" value="true" />
      <div class="-space-y-px rounded-md shadow-sm">
        <div>
          <label for="email-address" class="sr-only">Email address</label>
          <input
            id="email-address"
            name="email"
            type="email"
            autocomplete="email"
            required
            class="relative block w-full appearance-none rounded-none rounded-t-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
            placeholder="Email address"
            bind:value={email}
          />
        </div>
        <div>
          <label for="password" class="sr-only">Password</label>
          <input
            id="password"
            name="password"
            type="password"
            autocomplete="current-password"
            required
            class="relative block w-full appearance-none rounded-none rounded-b-md border border-gray-300 px-3 py-2 text-gray-900 placeholder-gray-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm"
            placeholder="Password"
            bind:value={password}
          />
        </div>
      </div>

      {#if message}
        <div class="flex items-center justify-between">
          <p class="text-indigo-500">{message}</p>
        </div>
      {/if}

      <!-- <div class="flex items-center justify-between">
          <div class="flex items-center">
            <input id="remember-me" name="remember-me" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500">
            <label for="remember-me" class="ml-2 block text-sm text-gray-900">Remember me</label>
          </div>
        </div> -->

      <div>
        <!-- Add loading indication -->
        <button
          type="submit"
          class="group relative flex w-full justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
          disabled={loading}
        >
          <span class="absolute inset-y-0 left-0 flex items-center pl-3">
            {#if !loading}
              <!-- Heroicon name: mini/lock-closed -->
              <svg
                class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
                xmlns="http://www.w3.org/2000/svg"
                viewBox="0 0 20 20"
                fill="currentColor"
                aria-hidden="true"
              >
                <path
                  fill-rule="evenodd"
                  d="M10 1a4.5 4.5 0 00-4.5 4.5V9H5a2 2 0 00-2 2v6a2 2 0 002 2h10a2 2 0 002-2v-6a2 2 0 00-2-2h-.5V5.5A4.5 4.5 0 0010 1zm3 8V5.5a3 3 0 10-6 0V9h6z"
                  clip-rule="evenodd"
                />
              </svg>
            {:else}
              <!-- Heroicon name: arrow-path -->
              <svg
                class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400 animate-spin"
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                stroke-width="1.5"
                stroke="currentColor"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
                />
              </svg>
            {/if}
          </span>
          {loading ? "Signing in...." : "Sign in"}
        </button>

        <button
          type="submit"
          class="group relative flex w-full justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 mt-2"
          disabled={loading}
          on:click={() => (register = true)}
        >
          <span class="absolute inset-y-0 left-0 flex items-center pl-3">
            {#if loading && register}
              <!-- Heroicon name: arrow-path -->
              <svg
                class="h-5 w-5 text-indigo-500 group-hover:text-indigo-400 animate-spin"
                xmlns="http://www.w3.org/2000/svg"
                fill="none"
                viewBox="0 0 24 24"
                stroke-width="1.5"
                stroke="currentColor"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0l3.181 3.183a8.25 8.25 0 0013.803-3.7M4.031 9.865a8.25 8.25 0 0113.803-3.7l3.181 3.182m0-4.991v4.99"
                />
              </svg>
            {/if}
          </span>
          {loading && register ? "Registering...." : "Register"}
        </button>
      </div>
    </form>
  </div>
</div>
  1. Next up, we will create the functionality to register a new user in pocketbase. One important bit for this is in line 112 in the above snippet. You see that when we click on the register button, we execute the function () => register = true. Through setting the variable, we know whether to register or log in as a user.
    To register a user, we have to call a function provided by the pocketbase lib, and we have to give it the data from the two input fields like this (add this to the function handleLogin):
loading = true;

if (register) {
    try {
      await pocketbase.collection("users").create({
        email,
        password,
        passwordConfirm: password,
      });

      try {
        await pocketbase.collection("users").requestVerification(email);
        message = "Check your email for the login link";
      } catch (error) {
        message = errorMessage(error);
      }
    } catch (error) {
      message = errorMessage(error);
    }
    register = false;
} 

loading = false;
  1. Now that we can register a new user, we have to add the functionality to log in as the user. For that, we use another function provided by pocketbase. This time we must choose the sign-in with email and password (append the code after line 22 of the previous snippet).
else {
      try {
          await pocketbase.collection("users").authWithPassword(email, password);
      } catch (error) {
          message = errorMessage(error);
      }
}
  1. With that, you should already be able to register a new user and log in as a new user. The next step is to change the screen based on the login status. For that, we create a new screen src/UserScreen.svelte with the following content:
<script lang="ts">
  import { currentUser, errorMessage } from "./lib/pocketbase";

  async function handleSignOut() {}
</script>

<div class="flex flex-col items-center justify-center w-full py-20">
  <p>{$currentUser.email}</p>
  <button
    class="group relative flex w-1/2 justify-center rounded-md border border-transparent bg-indigo-600 py-2 px-4 text-sm font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 mt-2"
    on:click={handleSignOut}
  >
    <span class="absolute inset-y-0 left-0 flex items-center pl-3" />
    Sign Out
  </button>
</div>
  1. To add the logout functionality, we call another function provided by pocketbase. Add the following code to handleSignOut:
function handleSignOut() {
  try {
    pocketbase.authStore.clear();
  } catch (error) {
    errorMessage(error);
  }
}
  1. Lastly, to swap between the two screens, we have to check whether a user is logged in or not conditionally. For that, we can use the svelte store that we created in the project setup section. Edit or create the file src/App.svelte with the following content:
<script lang="ts">
  import LoginScreen from "./LoginScreen.svelte";
  import { currentUser } from "./lib/pocketbase";
  import UserScreen from "./UserScreen.svelte";
</script>

{#if !$currentUser}
  <LoginScreen />
{:else}
  <UserScreen />
{/if}

With that, you have a login system base on pocketbase auth that consists of a login screen and a sign-out screen when you are logged in!

Conclusion

In summary, Pocketbase auth makes it pretty easy to add a login system to your application. We learned how to set up Pocketbases email/password authentication provider and implemented a login screen. If you have any questions or need further help, don’t hesitate to contact me via chat or email at mail@programonaut.com.

Don’t forget to subscribe to my monthly newsletter to stay up-to-date on all my blog posts and updates. Sign up now and never miss a post!

Possible problems

One problem that I encountered was that I could not run the svelte front end with npm run dev because of:

✘ [ERROR] Unexpected "\x7f"

    pocketbase:1:0:
      1 │ ELF☻☺☺☻>☺`�F@�☺@...
        ╵ ^

C:\projects\_posts\pocketchat\node_modules\esbuild\lib\main.js:1604
  let error = new Error(`${text}${summary}`);
              ^

Error: Build failed with 1 error:
pocketbase:1:0: ERROR: Unexpected "\x7f"
    at failureErrorWithLog (C:\projects\_posts\pocketchat\node_modules\esbuild\lib\main.js:1604:15)
    at C:\projects\_posts\pocketchat\node_modules\esbuild\lib\main.js:1056:28
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  errors: [
...

The reason was that I had the pocketbase executable in the root directory of the svelte application, and instead of using the pocketbase package, npm tried to use the executable.

To solve the problem you can create a new directory for example bin and move the executable in there, or simply rename it to something like pocketbase-exec.

Discussion (0)

Add Comment

Your email address will not be published. Required fields are marked *