Article cover image

Authentication with SvelteKit and Auth.js

  • Claudio
    Claudio
    Director of Engineering

Auth.js is a new framework agnostic authentication library extracted from the core of NextAuth.js. It's built around a single core package, with experimental framework adapters for Next.js, SvelteKit, SolidStart available now with more coming soon.

In this article, we will set up Auth.js with a new SvelteKit project and integrate with GitHub for OAuth login.

Check out the full source code here.

Prerequisites

First, we need to scaffold out a new SvelteKit project and install the required dependencies.

Run npx create-svelte@latest and follow the dialogs to create a SvelteKit app. Choose the "Skeleton project" and, for this tutorial, I will be using the TypeScript option.

Next, install the Auth.js dependencies, both the core package and the SvelteKit adapter.

npm i @auth/core @auth/sveltekit

Finally, we need to configure an OAuth application in GitHub. This will provide us with a client ID and client secret that we will need to pass to Auth.js

While logged in, navigate to GitHub developer settings and select "New OAuth app".

Give your app a name and then use http://localhost:5127 for the "Homepage URL" and http://localhost:5127/auth/callback/github as the "Authorization callback URL".

Using localhost allows for local testing of the authentication flow, but it won't work for a deployed app. When deploying your app, either update the OAuth app's URLs to reflect your deployed URLs or create a new OAuth app entirely.

Image for Authentication with SvelteKit and Auth.js

Select "Register Application" and then generate a new client secret. Copy both the new client secret and the client ID and place them in a .env file at the root of your new SvelteKit project.

Lastly, Auth.js requires an AUTH_SECRET environment variable that is used to sign sessions. Any random string that is a minimum of 32 characters could work. Generate one by running openssl rand -hex 32 in your terminal or by visiting https://generate-secret.vercel.app/32 and add the result to your .env file.

## .env
GITHUB_CLIENT_ID="client id here"
GITHUB_CLIENT_SECRET="client secret here"
AUTH_SECRET="random 32 character string here"

Integrating Auth.js and SvelteKit

Auth.js works by watching specific routes in your application and responding to them automatically for you. In SvelteKit, it does so by using the handle hook.

Create a hooks.server.ts file under your src directory and paste the following code snippet:

// src/hooks.server.ts
import { SvelteKitAuth } from '@auth/sveltekit'
import GitHub from '@auth/core/providers/github'
import { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } from '$env/static/private'

export const handle = SvelteKitAuth({
  providers: [
    GitHub({ clientId: GITHUB_CLIENT_ID, clientSecret: GITHUB_CLIENT_SECRET })
  ]
})

After calling SvelteKitAuth with our GitHub provider configuration, we export the result as our handle hook. This is all the setup we have to do; we are ready to start using Auth.js in our application code.

Implementing Login

With our handle hook in place, we can now look at allowing users to sign up and log in. First, let's make the current session data available to all routes using a root-level layout load function.

Create a +layout.server.ts under src/routes and copy the following snippet:

// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types'

export const load: LayoutServerLoad = async (event) => {
  return { session: await event.locals.getSession() }
}

Now, we can use the session data anywhere in our pages using the $page store.

Next, we'll make a simple login page.

Create src/routes/login/+page.svelte and copy the following code:

// src/routes/login/+page.svelte
<script lang="ts">
  import { page } from '$app/stores'
  import { signIn, signOut } from '@auth/sveltekit/client'
</script>

{#if !$page.data.session}
  <button on:click={() => signIn('github', { callbackUrl: '/' })}>
    Sign in with GitHub
  </button>
{:else}
  <button on:click={signOut}> Log out</button>
{/if}

Using the data we got from our layout load function, we check to see if the user has an active session and present the appropriate options. We specify that we want to sign in using GitHub and redirect back the home route once successful.

Accessing Session Data and Protecting Routes

Let's jump over to the home page and display some user information.

Copy this snippet into src/route/+page.svelte.

// src/routes/+page.svelte
<script lang="ts">
  import { page } from '$app/stores'
</script>

<h1>Welcome {$page.data.session?.user?.name}!</h1>
<img
  width="200"
  src={$page.data.session?.user?.image}
  alt="github profile avatar"
/>

After logging in, our session data will be populated with the user's profile information. We can now use any user-specific data that we need.

However, if an unauthenticated user visits the home page, it will cause an error because their session data will be null. Similarly, in most apps, there is a need to protect some routes and redirect unauthenticated visitors elsewhere. There are a couple ways to accomplish this with Auth.js and SvelteKit, but let's take a look at the most straightforward method: checking the session in a +page.server.ts load function.

// src/routes/+page.server.ts
import { redirect } from '@sveltejs/kit'
import type { PageServerLoad } from './$types'

export const load: PageServerLoad = async (event) => {
  const session = await event.locals.getSession()
  if (!session) {
    throw redirect(303, '/login')
  }
}

Since this load function is directly under src/routes, it will run whenever the index route is visited. We can first check if the user has a valid session and, if not, redirect them back to the login page. You could abstract this session validation logic into a function and use it where you need to verify a session before handling a request.

Recap

You've now got a working SvelteKit app that uses Auth.js to log in users and protect authenticated routes. For further reading, check out the Auth.js documentation.