Optimizing Server-Side Login and Logout Processes

Chapter 8 of Comprehensive Guide to SvelteKitAuth: Secure Authentication for SvelteKit Apps

In the previous article, we explored how to authenticate users on the Client side. In this section, we will delve into the server-side authentication process. There are two ways in which we can initiate the server-side authentication:

Using Form Actions

<SignIn /> and <SignOut /> are components that @auth/sveltekit provides out of the box - they handle the sign-in/sign-out flow, and can be used as-is as a starting point or customized for your own components.

The detailed example is provided in the official documentation, so I'll skip this approach and move to the next one.

Using programatically to auto sign-in and sign-out users

If your organization has outsourced the authentication mechanism to a third-party vendor, you will not have the flexibility to use form actions (as described in the previous section) to perform authentication. You will have to programatically login user and this is how you could do that:

Sign-in Flow

We can programmatically redirect users to the login route via hooks if they are unauthenticated and carry on with silent sign-in.

The procedure is exactly the same as in client-side authentication. The difference is we have to carry out each step manually, which otherwise was done by SvelteKitAuth for us in the client-side flow.

  1. Prepare payload for sign-in and pass callbackUrl and other params if required

  2. Make a POST call to /auth/signin/<provider-id> by passing Content-Type and X-Auth-Return-Redirect header values

  3. Get the URL for sign-in as response (this is the same URL that we pass as authorization URL in the provider) and redirect user for authentication.

// -> `/login/page.server.ts`

import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';

export const load = (async ({ fetch, locals, url: _url }) => {
  let url = '';
  try {
    const session = await locals.auth();
    if (!session?.user) {
      const params = new URLSearchParams();
      params.append('scope', 'api openid profile email');

      const formData = new URLSearchParams();
      formData.append('redirect', 'true');
      formData.append('callbackUrl', `${_url.origin}/ssr-login`);

      const signInRequest = await fetch('/auth/signin/auth1? ' + params.toString(), {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-Auth-Return-Redirect': '1'
        },
        body: formData.toString()
      });
      const signInResponse = await new Response(signInRequest.body).json();

      if (signInResponse?.url) {
        url = signInResponse.url;
      }
    }
  } catch (e: any) {
    console.log('Exception thrown while auto-sign-in: ', e);
  }

  if (url) {
    console.log('Auto login user: ', url);
    throw redirect(302, url);
  }
}) satisfies PageServerLoad;

NOTE: In the above code snippet, if you provide the option of callbackUrl within formData, that will be the output of signInResponse, else it will default to the URL of the page that initiated the sign-in request!

Sign-out Flow

The process is exactly similar to the one that we saw for the sign-in flow in the previous section.

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

export const load = (async ({ fetch, locals }) => {
  try {
    const session = await locals.auth();
    if (session && !!session.user?.access_token) {
      const formData = new URLSearchParams();
      formData.append('redirect', 'false');
      formData.append('callbackUrl', `${_url.origin}`);

      const signOutRequest = await fetch('/auth/signout', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'X-Auth-Return-Redirect': '1'
        },
        body: formData.toString()
      });
    }
  } catch (e: any) {
    console.log('Exception thrown while auto-sign-out: ', e);
  }
}) satisfies PageServerLoad;

NOTE: After signing out, we must reload the page. If we use the inbuilt signOut(), SvelteKitAuth auto reloads the page and redirects to the callbackUrl or to the URL that initiated the sign-in request. Since we programmatically logged users out, it is our responsibility to reload the page afterward.

<!-- src/routes/logout/+page.svelte file -->
<script lang="ts">
  import { onMount } from 'svelte';
  import { page } from '$app/stores';
  import { goto } from '$app/navigation';

  onMount(() => {
    // session-storage ensures that we reload only once!
    const isAppReloaded = sessionStorage.getItem('reloadApp') || 'false';
    if (isAppReloaded === 'false') {
      sessionStorage.setItem('reloadApp', 'true');
      window.location.reload();
    }
  });
</script>

Conclusion

In this article, we have explored the intricacies of optimizing server-side login and logout processes. We began by discussing the use of form actions provided by @auth/sveltekit for handling authentication flows seamlessly. However, recognizing that not all organizations have the flexibility to use these form actions, we delved into the programmatic approach for auto sign-in and sign-out, which is particularly useful when dealing with third-party authentication vendors.

We detailed the steps involved in the sign-in flow, including preparing the payload, making a POST call, and redirecting users for authentication. Similarly, we covered the sign-out flow, emphasizing the importance of reloading the page after logging out to ensure a smooth user experience.

By understanding both client-side and server-side authentication processes, you are now equipped to implement robust and flexible authentication mechanisms in your applications.

Here is the link to the GitHub repository with the codebase. In the next section, we will explore the differences between signing out a user from the application versus signing out from the OAuth provider, providing further insights into managing user sessions effectively.

Did you find this article valuable?

Support Aakash Goplani by becoming a sponsor. Any amount is appreciated!