loginradiusloginradius Blog

How to Authenticate Svelte Apps

Are you building Svelte apps? In this tutorial, you'll learn to add secure user authentication in your Svelte apps using LoginRadius APIs.

In this tutorial, I’ll talk about adding authentication in a Svelte application using LoginRadius Authentication APIs. You'll understand step by step how to:

  • create forms in Svelte;
  • add reactivity to it;
  • configure routing for our Svelte app; and,
  • setup protected routes for authenticated users.

You'll also learn how to consume REST APIs in a Svelte app using Fetch API and update user information in your Svelte store.

What is Svelte?

Svelte is a lightweight and minimal JavaScript compiler that ships a single JavaScript bundle to your application without any internal code of its own. This results in smaller bundle size and often a faster running application. It was also the most loved framework of 2021.

But what's the first feature you'd add in a Svelte application? Authentication! It's the gateway for your users to interact and use your website.

So in this post, I’ll talk about how to add authentication in a Svelte application.

Svelte Project Demo

You'll learn how to create forms in Svelte and add reactivity to it. You'll also understand how to conditionally render different UI components and set up protected routes for authenticated users in a Svelte application.

To speed things up, you won't build your own authentication services from scratch. Instead, you'll use LoginRadius to quickly configure an authentication backend for your Svelte App.

Here's a little demo of what your Svelte App would look like by the end of the tutorial:

Looks exciting? Let's jump right into it!

Setup a Svelte App

Let's start by creating a new Svelte project and adding some boilerplate code.

Create a New Svelte Project

Navigate into the directory of your choice and create a new Svelte project by running:

npx degit sveltejs/template svelte-auth-app

That should create a new Svelte project with a simple starter template.

Additionally, you'll need to install svelte-navigator package to set up routing in your Svelte app:

npm i svelte-navigator

Configure Environment Variable Support

You need to add environment variable support in your Svelte app to store and use your LoginRadius API Keys and Secrets.

First, install a package called dotenv that let's create and use a special .env file to define your environment variables.

npm i dotenv

Next, head over to the rollup.config.js file. Here, make some configurational changes on the plugins property, so your Svelte app can read those environment variables on the client-side:

	...
	plugins: [
		replace({
			// stringify the object
			__myapp: JSON.stringify({
			  env: {
				isProd: production,
				...config().parsed // attached the .env config
			  }
			}),
		  }),
    ...
	],

You can refer to the complete rollup.config.js file for this project here.

Create Files and Folders

Besides the initial set of files, your project will have the following directory structure:

Directory Structure Svelte App

So go ahead and create those files and folders accordingly. I'll talk about each of these as you start filling them with some code.

Add Project Styles

Your starter template should come with the following styles inside your App.svelte file:

main {
  text-align: center;
  padding: 1em;
  max-width: 240px;
  margin: 0 auto;
}

h1 {
  color: #ff3e00;
  text-transform: uppercase;
  font-size: 4em;
  font-weight: 100;
}

@media (min-width: 640px) {
  main {
    max-width: none;
  }
}

However, I have also added some additional styles inside the global stylesheet inside the /public/build/global.css file. You can copy those styles from here.

Global Data Store for Authenticated User

Your Svelte app will store the authenticated user's data in a global data store to easily access and modify that data from any component within your application. Svelte provides you with the writable function inside the svelte/store dependency.

Create a global object called user inside your /src/stores.js file:

import { writable } from "svelte/store"

export const user = writable(
  localStorage.user ? JSON.parse(localStorage.getItem("user")) : null
)

You have also synced the above user object with the browser's local storage so that this data persists on page reloads. If you're unfamiliar with what local storage is, check out my guide on Local Storage vs. Session Storage vs. Cookies that dives deeper into browser storage and other related concepts.

The App Component Boilerplate

The root component in your Svelte app is the App Component. This is located inside /src/App.svelte.

It has the following imports declared inside:

<script>
  /** IMPORTS */ import Login from './Login.svelte'; import Signup from
  './Signup.svelte'; import Home from './Home.svelte'; import PrivateRoute from
  "./routes/PrivateRoutes.svelte"; /** VARIABLES */ const sdkoptions = {}
</script>

It also has an sdkoptions variable that will store the environment variables that you can easily pass to different components as and when they need it. You can get rid of any additional HTML inside the App.svelte file generated by the starter template.

The Login Component

Let's head over to the /src/Login.svelte file to create the Login Page.

Create Login Form

Here's what the HTML of your Login Form consists of:

<h3>Login</h3>
<form on:submit|preventDefault="{handleSubmit}">
  <input
    class="form-field"
    bind:value="{email}"
    type="email"
    placeholder="Email"
  />
  <input
    class="form-field"
    bind:value="{password}"
    type="password"
    placeholder="Password"
  />
  <button class="form-field">
    Login
  </button>
</form>
<p>
  Don't have an account?
  <strong class="link" on:click="{navigateToSignup}">Sign up</strong>
</p>

You have a login form with two input fields for email and password and a submit button. The form also has an on:submit handler with an event modifier to prevent the default reloading action of the form when it gets submitted.

After the form, you have a simple link with an on:click handler that allows the user to navigate to the Signup page.

Login Form Field Bindings and Handlers

Inside the script of your Login component, create two variables — email and password — to handle the bindings to the input fields. Also, make the sdkoptions variable exportable since you'll receive it as props from the App component.

<script>let email; let password; export let sdkoptions;</script>

You also need to create a function called handleSubmit that is the on:submit handler for your Login form:

<script>
	...
    const handleSubmit=(e)=>{
       let loginFields={email,password};
	   console.log(loginFields)
    }
    const navigateToSignup=()=>{}
</script>

At the moment, you're simply encapsulating the email and password bindings into a JavaScript object. Later, you'll send a request to your Login API here. You also have an empty navigateToSignup function that I'll come back to once you set up your routes.

Let's render the Login component inside your App.svelte file:

<main>
  <Login />
</main>

Login Page

Awesome! Let's follow the same process to create your Signup form.

The Signup Component

Head over to the /src/Signup.svelte file to create your Signup Page.

Create Signup Form

Similar to the Login Form, the Signup Form will have the following HTML:

<h3>Create a New Account</h3>
<form on:submit|preventDefault="{handleSubmit}">
  <input
    class="form-field"
    bind:value="{firstName}"
    type="text"
    placeholder="First Name"
  />
  <input
    class="form-field"
    bind:value="{lastName}"
    type="text"
    placeholder="Last Name"
  />
  <input
    class="form-field"
    bind:value="{email}"
    type="email"
    placeholder="Email"
  />
  <input
    class="form-field"
    bind:value="{password}"
    type="password"
    placeholder="Password"
  />
  <button class="form-field">
    Signup
  </button>
</form>
<p>
  Already have an account?
  <strong class="link" on:click="{navigateToLogin}">Login</strong>
</p>

The only difference is now you have two additional fields — first name and last name. You also have a link just below the signup form that takes the user to the login page.

Signup Form Field Bindings and Handlers

Here's what the script section of your Signup.svelte file should look like:

<script>

	let email;
    let password;
    let firstName;
    let lastName;
    let loading;


    export let sdkoptions;

    const handleSubmit=()=>{
        const signupFields={
            "FirstName": firstName,
            "LastName": lastName,
            "Email": [
                {
                "Type": "Primary",
                "Value": email
                }
            ],
            "Password": password
     }
     console.log(signupFields)
   }

   const navigateToLogin=()=>{}
</script>

Your signupFields object has a specific format because it aligns with the structure of your Signup API's request. It would make complete sense when you hook your Signup API here.

Let's render the Signup component inside your App.svelte file:

Signup Page

The Home Component

Let's create your Home component or the page where you'll redirect an user on a successfull login. Inside /src/Home.svelte file, add the following HTML:

{#if user && _user}
<h3>Welcome {_user?.profile?.FullName}</h3>
<button on:click="{logout}">Logout</button>
{/if}

In the above HTML, you simply use some Svelte conditionals to render the user's name and a logout button. The script part of your Home component looks like this:

<script>
    import {user} from './stores';

    let _user;

    user.subscribe(data=>_user=data)
    const logout=()=>{
        user.set(null);
        localStorage.clear();
    }

</script>

You import the global user object from your Svelte store and store it inside your Home component's state _user. For the logout function, you simply set this user object in your store to null and clear the local storage.

Setup LoginRadius

The next step is to set up LoginRadius, so you can start using its Authentication APIs from your Svelte App.

Create LoginRadius Account

Head over to LoginRadius and create a new account by filling in the following details:

Signup 2

You'll then see a form with the name of your App, a URL, and a Data Center:

Signup 3

Hit Next and LoginRadius will set up an authentication app for you. You can try the pre-built authentication page LoginRadius provides for your app by clicking on Try Signing Up Now.

Signup 4

Once you do that, you'll be shown a pre-built authentication page of your LoginRadius app:

LoginRadius Auth Page

However, for this tutorial, all you need are the LoginRadius APIs since you already have a frontend (your Svelte App) in place.

Get LoginRadius API Configurations

Once you're inside your LoginRadius account, head over to the Configurations tab from your dashboard. Then expand the API Credentials tab of your application.

LoginRadius Configuration Tab

Inside that, you should see your APP Name, API Key, and API Secret.

LoginRadius API Credentials

Add API Credentials as Environment Variables

Head back to the Svelte App's .env file and add the above credentials inside it:

APP_NAME=<YOUR_APP_NAME>
API_CLIENT_KEY= <YOUR_APP_API_KEY>
API_SECRET= <YOUR_APP_API_SECRET>

You'll need to ensure that the above .env file is present inside the .gitignore of your Svelte project. You don't want to accidentally push this file to a public repository on Github.

/node_modules/
/public/build/

.DS_Store

.env

Consume Environment Variables in Svelte Components

Finally, you need to extract these environment variables in your sdkoptions variables inside your App component file.

...
	const sdkoptions = {
	"apiKey": __myapp["env"].API_CLIENT_KEY,
	"appName":__myapp["env"].APP_NAME,
    "apiSecret":__myapp["env"].API_SECRET
	}
...

And pass this variable as props to both the Login and Signup component:

...
<Login {sdkoptions} />
<Signup {sdkoptions} />
...

Great! Now you're ready to integrate LoginRadius APIs in your Svelte app.

Integrate LoginRadius API

You'll use two APIs — one for registering the user on the Signup page and the other for authenticating the user on the Login Page. However, both APIs will take the API key and API secret as query parameters.

const endpoint = `${BASE_URL}?apikey=${sdkoptions.apiKey}&apisecret=${sdkoptions.apiSecret}`

So in the above endpoint, you'll only change the BASE_URL according to which API you're hitting.

Integrate Signup API

Inside your Signup.svelte file, you'll create a variable called signupResponse to store the result of the Signup API. It also has properties like success and error to prompt the user on the status of your API calls.

<script>
    let signupResponse={
            success:null,
            error:null,
            uid:null,
            id:null,
            fullname:null
     }
 	let loading;

</script>

Since an API request is an asynchronous process, you also have a' loading' variable to disable the submit button until the API responds.

Signup API Request on Signup Form Submission

You need to send a request to the Signup API inside your handleSubmit() function. First, set the loading to true and reset the signupResponse object. Then, use the fetch API to make a POST request with the signupFields object as post parameter:

const handleSubmit = () => {
  const signupFields = {
    FirstName: firstName,
    LastName: lastName,
    Email: [
      {
        Type: "Primary",
        Value: email,
      },
    ],
    Password: password,
  }
  loading = true
  signupResponse = {
    success: null,
    error: null,
    uid: null,
    id: null,
    fullname: null,
  }
  const endpoint = `https://api.loginradius.com/identity/v2/manage/account?apikey=${sdkoptions.apiKey}&apisecret=${sdkoptions.apiSecret}`
  fetch(endpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(signupFields),
  })
    .then(response => response.json())
    .then(data => {})
    .catch(error => console.log(error))
    .finally(() => (loading = false))
}

Handle API Response

Inside the callback of your fetch request, you can handle any errors returned by the API based on different error codes. In case of success, you'll populate your signupResponse object with data from the API. In the finally block, set the loading to false.

...
.then(data=>{
          if(data.ErrorCode){
              if(data.ErrorCode==936){
              signupResponse={
                    ...signupResponse,
                    error:data.Message
                }
             }
            else if(data.ErrorCode==1134){
                signupResponse={
                    ...signupResponse,
                    error:data.Errors[0].ErrorMessage
                }
            }else{
                signupResponse={
                    ...signupResponse,
                    error:data.Description
                }
            }
        }else{
            signupResponse={
                ...signupResponse,
                success:"Account created successfully! Please login via the same details.",
                fullname:data.FullName,
                id:data.id,
                uid:data.Uid
            }
        }
      })
...

Disable Submit Button

You can set the disabled attribute on your Submit button based on the loading state of the API.

...
<button disabled="{loading}" class="form-field">
  Signup
</button>
...

Show Signup Success and Error

Lastly, render a conditional template based on the status of your Signup API.

... {#if signupResponse.error}
<p class="error">Error ❌ {signupResponse.error}</p>
{/if} {#if signupResponse.success}
<p class="success">
  Success ✔ {signupResponse.success}
</p>
{/if} ...

And that's it! Let's give your Signup functionality a whirl. Create a new account first:

Signup Success with API

And voila! You can see in the network tab that the API is successful. And you got back a bunch of data about the newly created user. You also have a success that appears underneath the Signup form.

Let's also test an erroneous flow. What if you signup via the same credentials again?

Signup Failure Existing Email

The LoginRadius Signup API returns the error message, details, error code, etc., that you've handled accordingly.

Also, if you use a simple password, the LoginRadius API returns an error based on a validation mechanism.

Signup Error Password Validation

That's great because it makes your authentication workflow more air-tight.

Integrate Login API

Similarly, you can now integrate the Login API as well in your Login.svelte file:

<script>

	...
    import {user} from './stores';

    let email;
    let password;
    let loading;

    let loginResponse={
        error:null,
        success:null,
        profile:null,
        auth_token:null
    }

    export let sdkoptions;

    const handleSubmit=(e)=>{
       let loginFields={email,password};
       loading=true;
        loginResponse={
        error:null,
        success:null,
        profile:null,
        auth_token:null
    }
       const endpoint=`https://api.loginradius.com/identity/v2/auth/login?apikey=${sdkoptions.apiKey}&apisecret=${sdkoptions.apiSecret}`;
       loading=true;
    fetch(endpoint,
        {
        method:'POST',
        headers: {
        'Content-Type': 'application/json'
        },
        body:JSON.stringify(loginFields)
        }).then(response=>response.json())
        .then(data=>{
            console.log(data)
            if(data.ErrorCode){
                if(data.ErrorCode==936){
                loginResponse={
                        ...loginResponse,
                        error:data.Message
                    }
                }
                else if(data.ErrorCode==1134){
                    loginResponse={
                        ...loginResponse,
                        error:data.Errors[0].ErrorMessage
                    }
                }else{
                    loginResponse={
                        ...loginResponse,
                        error:data.Description
                    }
                }
            }else{
                loginResponse={
                    ...loginResponse,
                    success:true,
                    profile:data.Profile,
                    auth_token:{
                        access_token:data.access_token,
                        refresh_token:data.refresh_token,
                        ttl:data.expires_in
                    }
                }
                user.set(loginResponse)
                localStorage.setItem('user',JSON.stringify(loginResponse))

            }
        })
        .catch(error=>console.log(error))
        .finally(()=>loading=false);
    }
    ...

</script>

...
{#if loginResponse.error}
	<p class="error">Error ❌ {loginResponse.error}</p>
{/if}
{#if loginResponse.success}
	<p class="success">
        Success ✔
    </p>
{/if}
<p>
    Don't have an account?
    <strong class="link" on:click={navigateToSignup}>Sign up</strong>
</p>

If you relate it to the Signup component, it's almost identical. The only thing that has changed is the API endpoint. Additionally, if the Login API is a success, you're also storing the data in your user store and consequently updating your localStorage.

Let's also test this out:

Login API Success

Let's also test an erroneous Login flow by entering the credentials for a user that doesn't exist:

Login Error

Great! Let's now tie all these individual pages together by setting up routing in your Svelte app.

Setup Routes

You'll use the svelte-navigator package that you previously installed to configure routing in your app.

Add Routing

First, you'll define the routes for the Signup, Login, and Home pages inside your App.svelte file:

<script>

  /** IMPORTS */
  import { Router, Route } from "svelte-navigator";
  ...
</script>

<main>
  <Router>
    <div>
      <Route path="signup">
        <Signup {sdkoptions} />
      </Route>
      <Route path="login">
        <Login {sdkoptions} />
      </Route>
      <Route path="/">
        <Home />
      </Route>
    </div>
  </Router>
</main>

If you visit /login, you should see the Login Page. Similarly, if you go to /signup, you should see the Signup Page.

Programmatic Redirection

Remember, you had a link to the Signup page at the bottom of the Login page and vice versa?

Also, your login page doesn't redirect anywhere after you login successfully. In order to programmatically navigate to a specific page on completion of an action, you can use the useNavigate hook from the svelte-navigator library.

To use this hook, import useNavigate and save its invoked reference inside a variable, as follows:

<script>
  import {useNavigate} from "svelte-navigator"; const navigate = useNavigate();
  ...
</script>

Now call the navigate() function and pass the path of the page you wish to redirect to. So let's complete the navigateToLogin function inside your Signup component:

<script>... const navigateToLogin=()=>navigate('/login'); ...</script>

Similarly, you can also complete the navigateToSignup function inside your Login component:

<script>... const navigateToSignup=()=>navigate('/signup') ...</script>

Finally, you'll also navigate to the Home page on a successful response from your Login API inside your Login component's handleSubmit function:

 const handleSubmit=(e)=>{
       ...
    fetch(endpoint,
       ...
        .then(data=>{
 				...
            }else{
				...
                navigate('/')
            }
        })
       ...
    }

</script>

Great! You can now navigate from your Login page to the Signup page and vice versa. You should also be automatically redirected to the Home page on a successful login.

Add Protected/Private Routes

If you try to visit the Home component by entering the path / in the URL, you'll be able to see the Home page regardless of your authenticated state. However, you must protect this route so that it's only accessible to authenticated users in your application.

For this purpose, you have created a RouteGuard.svelte file inside your routes folder. It's a higher-order component that uses the useNavigation hook to programmatically redirect to the login page if an unauthenticated user visits the home page. You use the user object from your Svelte store to validate the authenticated state of a user.

<script>
    import { useNavigate, useLocation } from "svelte-navigator";
    import { user } from "../stores";

    const navigate = useNavigate();
    const location = useLocation();

    $: if (!$user) {
      navigate("/login", {
        state: { from: $location.pathname },
        replace: true,
      });
    }
  </script>

  {#if $user}
    <slot />
  {/if}

You can then wrap another higher order Route component on top of the RouteGuard component inside the PrivateRoutes.svelte file, as follows:

<script>
    import { Route } from "svelte-navigator";
    import RouteGuard from "./RouteGuard.svelte";

    export let path;
  </script>

  <Route {path} let:params let:location let:navigate>
    <RouteGuard>
      <slot {params} {location} {navigate} />
    </RouteGuard>
  </Route>

Now, all you need to do is use this PrivateRoute component to wrap your Home component instead of a regular Route component:

...
	<PrivateRoute path="/" let:location>
		<Home />
	</PrivateRoute>
...

And now you should be able to access the / route or the Home page only when you're authenticated:

Home Component

Conclusion

I hope I've shed some light on how to authenticate Svelte apps. You can do a lot from here, like adding Svelte Kit to this project to simplify the routing system for your Svelte application.

You can refer to the entire source code for this tutorial here.

You can also explore LoginRadius to add forgot password functionality or social signups with Facebook and Google.

Siddhant Varma

Written by Siddhant Varma

He is a full-stack JavaScript engineer who loves building pixel perfect UIs and engineering tech products. He loves creating technical content for developers and has worked as a frontend engineer for many startups.

LoginRadius CIAM Platform

Our Product Experts will show you the power of the LoginRadius CIAM platform, discuss use-cases, and prove out ROI for your business.

Book A Demo Today