How to implement a custom SSO authentication login flow

This tutorial shows how to implement an SSO login flow in SvelteKit using route pages to request authentication, handle the access token response and redirect to a custom page.

📘

Required modules

You will need the API Integration module. Check your modules at Admin > Licenses. To obtain this module, contact your IXON account manager or IXON distributor.

Use cases and needs

Normally, in IXON's environment, logging in with SSO Authentication implies that you will be immediately redirected to the IXON Portal, for example to the /devices page. This is done by making the SSO provider's API endpoints and IXON endpoints work together.
Regardless of which provider you will use (Google, Microsoft etc), the last step to be successfully redirected to the landing page consists of generating a one-time use access token that grants access to the user.

Thanks to SvelteKit Routes, you can create a simple redirection flow that:

  • Requests a login from your SSO provider;
  • Handles the provider’s callback in an intermission handler page;
  • Redirects the user immediately to a custom URL path.

Before you start

To create this project, you will need:

  • A code editor. This tutorial uses and recommends Visual Studio Code;
  • Node.js (v18 or later recommended);
  • npm (comes with Node.js);
  • Basic knowledge of Svelte and SvelteKit;
  • An IXON API Application ID;
  • An Identity Provider public ID, which can be obtained by querying the SsoIdentityProviderList endpoint:

    curl --request GET \
         --url https://portal.ixon.cloud/api/sso/identity-providers \
         --header 'Api-Application: $applicationId' \
         --header 'Api-Version: 2' \
         --header 'accept: application/json'
    
    {
      "type": "SsoIdentityProvider",
      "data": [
        {
          "publicId": "$microsoftIdentityPublicId",
          "name": "Microsoft"
        },
        {
          "publicId": "$googleIdentityPublicId",
          "name": "Google"
        }
      ],
      "moreAfter": null,
      "status": "success"
    }
    

Once you have everything you need to continue, you can start setting up the project.

Project creation

To create a SvelteKit project, please follow the instructions found in this official tutorial. Once you will run the npx sv create my-app command you will be prompted to choose a template, whether you want TypeScript type checking or not and more dependencies. These are the options that we have used for this example:

  1. SvelteKit minimal;
  2. Typescript syntax;
  3. prettier;

Additionally, here is a preview of how your project's src/routes folder structure will look:

👍

Recommended extension

It is highly recommended to install the Svelte for VS Code extension, as it will make using Svelte and SvelteKit easier.

Step 1 - Create the login page

This file will be created inside the login folder, and will trigger the login process by making a request to your SSO API and then redirecting the user to the provider’s login page. This is done through IXON's SsoAuthenticationRequestList endpoint with a POST request that contains the publicId of the identity provider and the redirect URL pointing to the callback folder.

In this example, we used Google login's page:

<script lang="ts">
    let error = $state<string | null>(null);

    async function requestLogin() {
        try {
            const res = await fetch('https://portal.ixon.cloud/api/sso/authentication/request', {
                method: 'POST',
                headers: {
                    'Api-Application': '$applicationId', // <-- your real API applicationId
                    'Api-Version': '2',
                    'accept': 'application/json',
                    'content-type': 'application/json'
                },
                body: JSON.stringify({
                    identityProvider: {
                        publicId: '$identityProviderPublicId'
                    },
                    redirectUrl: 'http://localhost:5173/login/callback'
                })
            });

            if (!res.ok) throw new Error(`HTTP ${res.status}`);

            const json = await res.json();
            console.log('Login request response:', json);

            const url = json?.data?.url;
            if (url && typeof url === 'string') {
                // Redirect in the same tab for SSO flow
                window.location.href = url;
            } else {
                throw new Error('No valid URL in SSO response');
            }

        } catch (err: unknown) {
            if (err instanceof Error) {
                error = err.message;
            } else {
                error = String(err);
            }
            console.error('Login request failed:', err);
        }
    }

    // Run automatically when page loads
    $effect(() => {
        requestLogin();
    });
</script>

{#if error}
    <p style="color:red">{error}</p>
{:else}
    <p>Redirecting to SSO provider...</p>
{/if}

Step 2 - Create the callback page

This page handles the redirect from the SSO provider, requests the access token SsoAccessTokenList endpoint, and immediately redirects the user to the desired page, which in our case is in /devices. It will also safely store the token in the local storage for later use in other possible routes:

<script lang="ts">
    import { page } from '$app/stores';
    
    let error = $state<string | null>(null);

    async function fetchAccessToken(code: string, state: string) {
        try {
            const res = await fetch('https://portal.ixon.cloud/api/sso/access-token', {
                method: 'POST',
                headers: {
                    'Api-Application': '$applicationId', // <-- your real API applicationId
                    'Api-Version': '2',
                    'accept': 'application/json',
                    'content-type': 'application/json'
                },
                body: JSON.stringify({
                    ssoAuthentication: { code, state },
                    expiresIn: 3600
                })
            });

            if (!res.ok) throw new Error(`HTTP ${res.status}`);

            const json = await res.json();
            console.log('Access token response:', json);

            const token = json?.data?.secretId;
            if (token && typeof token === 'string') {
                // Store token securely in localStorage
                localStorage.setItem('bearerToken', token);

                // Redirect immediately
                window.location.href = "http://localhost:5173/devices";
            } else {
                throw new Error('No secretId found in response');
            }
        } catch (err: unknown) {
            error = err instanceof Error ? err.message : String(err);
            console.error('Failed to get access token:', err);
        }
    }

    $effect(() => {
        const query = $page.url.searchParams;
        const code = query.get('code');
        const state = query.get('state');

        if (code && state) {
            fetchAccessToken(code, state);
        } else {
            error = 'Missing code or state in URL';
        }
    });
</script>

{#if error}
    <p style="color:red">{error}</p>
{/if}

👍

Required fields for SsoAccessTokenList

For this endpoint we will only need the code and state fields contained in the ssoAuthentication object and the expiresIn field. The rest of the fields shown in the API endpoint documentation page are optional.

Step 3 - Create the landing page

This is your target landing page after authentication! This route checks localStorage for the bearerToken. If not found, it redirects to /login.

<script lang="ts">
    import { onMount } from 'svelte';

    let token: string | null = null;
        
    onMount(() => {
        token = localStorage.getItem('bearerToken');
        if (!token) {
            // No token? Redirect to login page
            window.location.href = '/login';
        }
    });
</script>

{#if token}
    <h1>Devices Page</h1>
    <p>Welcome! You are now logged in.</p>
{/if}

How the flow works

To sum everything up:

  1. User visits /login.
  2. SvelteKit calls IXON's SSO API endpoint and redirects the user to the SSO provider;
  3. The SSO provider redirects back to /complete?code=...&state=...;
  4. /callback exchanges the code for an access token and stores it safely in the local storage;
  5. If successful, it redirects to /devices without showing the token;
  6. /devices displays the user’s device page.