Install @teamhanko/hanko-elements

Once you’ve initialized your app, installing hanko-elements provides you with access to the prebuilt components: hanko-auth and hanko-profile.

Add the Hanko API URL

Retrieve the API URL from the Hanko console and place it in your .env file.

.env
PUBLIC_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io

If you are self-hosting you need to provide the URL of your running Hanko backend.

Add <hanko-auth> component

The <hanko-auth> web component adds a login interface to your app. Begin by importing the register function from @teamhanko/hanko-elements into your Svelte component. Call it with the Hanko API URL as an argument to register <hanko-auth> with the browser’s CustomElementRegistry. Once done, use the <hanko-auth> element in your component.

HankoAuth.svelte
<script>
  import { onMount } from "svelte";
  import { goto } from "$app/navigation";
  import { register } from "@teamhanko/hanko-elements";
  import { env } from "$env/dynamic/public";
  const hankoApi = env.PUBLIC_HANKO_API_URL;

  const redirectAfterLogin = () => {
    goto("/dashboard");
  };

  onMount(async () => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  });
</script>

<hanko-auth on:onSessionCreated={redirectAfterLogin} />

When incorporating any Hanko component in your +page.svelte file(routes), it is necessary to disable server-side rendering (SSR) by setting ssr to false in your +layout.ts file.

filename="routes/login/+layout.ts"
export const ssr = false;

But why?

The register function tries to set up the <hanko-auth> element for the browser. But, SvelteKit pre-renders pages before displaying them. Since it doesn’t have access to the browser’s window object during this pre-render phase, the registration of the custom element would fail.

By now, your sign-up and sign-in features should be working. You should see an interface similar to this 👇

Add <hanko-profile> component

The <hanko-profile> component provides an interface, where users can manage their email addresses and passkeys.

HankoProfile.svelte
<script>
  import { register } from "@teamhanko/hanko-elements";
  import { onMount } from "svelte";
  import { env } from "$env/dynamic/public";

  const hankoApi = env.PUBLIC_HANKO_API_URL;

  onMount(async () => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  });
</script>

<hanko-profile />

It should look like this 👇

Implement logout functionality

You can use @teamhanko/hanko-elements to easily manage user logouts. Below, we make a logout button component that you can use anywhere.

LogoutButton.svelte
<script>
  import { Hanko } from "@teamhanko/hanko-elements";
  import { goto } from "$app/navigation";
  import { env } from "$env/dynamic/public";
  const hankoApi = env.PUBLIC_HANKO_API_URL;

  const hanko = new Hanko(hankoApi);
  const logout = () => {
    hanko.user.logout().catch((error) => {
      // handle error
    });
    goto("/login")
  };
</script>

<button on:click={logout}>Logout</button>

Customize component styles

You can customize the appearance of hanko-auth and hanko-profile components using CSS variables and parts. Refer to our customization guide.

Securing routes

To add JWT verification with Hanko in your application, we’re using jose library. However, you’re free to choose any other suitable library. This hook will ensure secure access to specific routes, like /dashboard here, by checking for valid JWT. Here we define the Hanko API URL, extract and verify the JWT from cookies, and redirect unauthorized users to the login page.

hooks.server.ts
import { type RequestEvent, redirect, type Handle } from "@sveltejs/kit";

import { jwtVerify, createRemoteJWKSet } from "jose";
import { env } from "$env/dynamic/public";

const hankoApiUrl = env.PUBLIC_HANKO_API_URL;

const authenticatedUser = async (event: RequestEvent) => {
  const { cookies } = event;
  const hanko = cookies.get("hanko");
  const JWKS = createRemoteJWKSet(
    new URL(`${hankoApiUrl}/.well-known/jwks.json`)
  );

  try {
    await jwtVerify(hanko ?? "", JWKS);
    return true;
  } catch {
    return false;
  }
};

export const handle: Handle = async ({ event, resolve }) => {
  const verified = await authenticatedUser(event);

  if (event.url.pathname.startsWith("/dashboard") && !verified) {
    throw redirect(303, "/login");
  }

  const response = await resolve(event);
  return response;
};

Try out yourself

SvelteKit example

Full source code available on our GitHub