DEV Community

Klee Thomas
Klee Thomas

Posted on

Svelte + Auth0 step by step

In this post, I run through, step by step, how I went about getting authentication into a Svelte application using Auth0. If you just want to see the full sample code for this can be found on GitHub.

Svelte is an amazing tool to use when creating interactive web apps. Pretty much any interactive web app needs to have Authentication baked in. Authentication is one of those things that you shouldn't be building for yourself. Auth0 provides a low-effort way to authenticate your users.

Get set up

Create an Auth0 account

For this post, I'm going to assume that you've already got an account on Auth0. If you don't it's as easy as heading to Auth0 and signing up for a free account.

Start a Svelte app

You could build out a Svelte app from the ground up, picking your bundler and application structure, or you can use a pre-built scaffold. For this post, I'll use a scaffold to keep things short.

Run npm create vite@latest svelte-auth --templte svelte-ts

Navigate into the cloned directory and install the dependencies by running npm install.

Start the application locally by running npm run dev this will start a server on port 5173. You can open the app by going to http://localhost:5173/.

Now that you've got a sample Svelte app running let's look at how we can add Authentication using Auth0.

Adding Authentication

Configure Auth0

Log into the Auth0 console. If you've just signed up or created a new tenant there will be an app called Default App that you can use, or you can create a new application.

Into this application, you're going to need to set the Allowed Callback URLs to http://localhost:5173/ and the Allowed Logout URLs to http://localhost:5173/. Note that the URL ends in /. Then scroll to the bottom and save the changes.

To configure the library you're going to need some values from Auth0. From the console take note of the Domain and Client ID values.

Auth0 console showing the Domain and Client ID fields

Add log in

Auth0 provides a library to make it easier to add Authentication to JavaScript applications. Add the library using npm install @auth0/auth0-spa-js.

Create a new file to be responsible for Authentication called auth.ts. To start with export a function called withAuth that returns an object with a login method.

Start by implementing the login function.
Create an Auth0 client using the values that you copied out of the Auth0 console.
You'll also need to tell the client where to redirect the user back to after login. Given that this is a single-page app we'll direct them back to window.location.origin.

Then use the client to redirect the user to Auth0 to log in.



async function login() {
  const client = await createAuth0Client({
    domain: "<your domain>", // e.g. "klee-test.au.auth0.com"
    clientId: "<your client id>", // e.g. "GGOFsf1eiSGvYOBkeDHAAJopE5qRpzN7"
    authorizationParams: {
      redirect_uri: window.location.href,
    },
  });
  client.loginWithRedirect();
}

export function withAuth(): {
  login: () => Promise<void>;
} {
 return { login }
}


Enter fullscreen mode Exit fullscreen mode

To test this out wire it into the view in App.svelte



<script lang="ts">
  import { withAuth } from './auth';

  const auth = withAuth();
</script>
<main>
  <button on:click={auth.login}>Login</button>
</main>


Enter fullscreen mode Exit fullscreen mode

Now when someone clicks on the button they'll be redirected to Auth0 and asked to log in. When they have they'll be redirected back to your app.

Add log out

Let's follow that up by adding logout functionality.
Go back to the auth.ts file and add a logout method. You'll need an Auth0 client with the same configuration you used for login and need to call the logout method.



async function login() { ... }

async function logout() {
  const client = await createAuth0Client({
    domain: "<your domain>", // e.g. "klee-test.au.auth0.com"
    clientId: "<your client id>", // e.g. "GGOFsf1eiSGvYOBkeDHAAJopE5qRpzN7"
    authorizationParams: {
      redirect_uri: window.location.href,
    },
  });
  client.logout();
}

export function withAuth(): {
  login: () => Promise<void>;
  logout: () => Promise<void>;
} {
 return { login, logout }
}


Enter fullscreen mode Exit fullscreen mode

Wire this into the App.svelte file.



<script lang="ts">
  import { withAuth } from './auth';

  const auth = withAuth();
</script>
<main>
  <button on:click={auth.login}>Login</button>
  <button on:click={auth.logout}>Logout</button>
</main>


Enter fullscreen mode Exit fullscreen mode

Now when the user clicks on Logout they'll be taken back to Auth0, their session will be cleared and they'll be redirected back to your app.

This is great, the user can log in and log out but... really we need to be able to show a different experience for logged-in users compared to the experience for logged-out users.

Get user details

Let's go ahead and add a welcome message for logged-in users and display their profile picture. We can also hide the login button for logged-in users and hide the logout button for logged-out users.

To share the user information with the rest of the app let's have auth.ts expose a Svelte store that provides the user details. This will allow App.svelte to respond to app.ts getting authentication asynchronously and render the logged-in UI when it's ready.

In auth.ts create a store called user by importing writable from svelte and calling it at the top level of the file.



import { writable, type Writable } from "svelte/store";

let user: Writable<User> = writable();


Enter fullscreen mode Exit fullscreen mode

Then add a new function getUser(). In this function start by setting up an Auth0 client using the same config as login and logout.

In this function call getTokenSilently. This is a bit unintuitive. Without this call, the Auth0 client will not have a token it can use to call the Auth0 authentication server to get the user information.

After the client has a token call the client again to get the user's information from the authentication server. Once you've got the user's details call the set method on the user store to publish the new information.

Wrap all the calls here in a try-catch block. This is because the getTokenSilently call will throw if the user is not authenticated. Adding this catch here is important because you need to call your getUser function as part of the withAuth function. This ensures that the user will automatically see the authentication information.

You should now have added some code that looks like this to your auth.ts file.



  async function getUser(): Promise<void> {
    const client = createAuth0Client({
      domain: "klee-test.au.auth0.com",
      clientId: "GGOFsf1eiSGvYOBkeDHAAJopE5qRpzN7",
      authorizationParams: {
        redirect_uri: window.location.href,
      },
    });

    try {
      // ensure the client has a token to cal the Auth0 Authentication server.
      await client.getTokenSilently();
      // get the client to fetch the user information.
      const userDetails = await client.getUser();
      // publish the user information
      user.set(userDetails);
    } catch (e) {
      // if the user is not logged in the getTokenSilently call will fail. 
      console.warn(e);
    }
  }

  getUser();


Enter fullscreen mode Exit fullscreen mode

You'll also need to remember to return the user store from withAuth so that App.svelte can subscribe to changes.



  return {
    login,
    logout,
    user,
  };


Enter fullscreen mode Exit fullscreen mode

Now to update the user interface and display some information from the user returned from Auth0.

In the <script> tag in the App.svelte file you need to subscribe to changes made to the user. To do this, create a variable to hold the user object and call the subscribe method on the user object returned from withAuth. subscribe takes a callback with the updated value for the user's auth.



<script lang="ts">
  import type { User } from '@auth0/auth0-spa-js';
  import { withAuth } from './auth';

  const auth = withAuth();

  // a variable to hold the authentication details
  let user: User;
  // update the local store of auth details when they change
  auth.user.subscribe((auth) =>{
    user = auth;
  } )
</script>


Enter fullscreen mode Exit fullscreen mode

Finally to get the display to update add an {#if} block to the tsx code to switch between showing the log in button or showing log out button and user profile information.



    {#if !user}
    <button on:click={auth.login}>Login</button>
    {:else}
    <button on:click={auth.logout}>Logout</button>
    <h1>Hello {user.nickname}</h1>
    <div class="profile">
      <img class="profile" src={user.picture} alt="User profile">
    </div>
    <p>User:</p>
    <pre>
      {JSON.stringify(user, null, 2)}
    </pre>
    {/if}


Enter fullscreen mode Exit fullscreen mode

With this, you've got an app that you can run to log a user in and see some profile information.

Getting an access token

The other thing you may want to do is get an access token to call an API.

For this, you'll need to go back over to the Auth0 console and add an API. You can find the APIs section under the Applications menu. Add a new API give it a name and pick an identifier. Take note of the identifier, you'll need to add it to your code.

A screen shot of adding an API to Auth0

Back in your code add the API's identifier to the code as part of the authorizationParams object when creating the client.

Now when you call client.getTokenSilently() it will return a valid JWT access token. Add a new store for the token, set it's value once the getTokenSiliently call has completed and return the store as part of the withAuth response so the rest of your application can make use of it when calling APIs.



const accessToken = await client.getTokenSilently();
token.set(accessToken);


Enter fullscreen mode Exit fullscreen mode

The full sample code for this can be found on GitHub

Top comments (1)

Collapse
 
lennartdeknikker profile image
Lennart de Knikker • Edited

Nice guide! One thing I noticed: You will not be able to get the token silently without updating your auth0 configuration to also include the localhost:5173/ domain in the allowed web origins.