šŸš€ Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more →
Socket
Sign inDemoInstall
Socket

sveltekit-keyloak-multitenant

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sveltekit-keyloak-multitenant

Multi-tenant authentication and authorization for SvelteKit apps using KeyCloak.

0.2.3
npm
Version published
Weekly downloads
0
-100%
Maintainers
1
Weekly downloads
Ā 
Created
Source

sveltekit-keycloak-multitenant (BETA)

Multi-tenant (fully federated) authentication and authorization library for KeyCloak in SvelteKit apps. Enables role-based-access-controls configuration in Keycloak, and SvelteKit app role metadata access in SSR. Uses a Hybrid Authentication flow and JWT tokens.

Motivation

Needed a solution for multitenant authentication in a SvelteKit app intended to run in a containerized environment. Intended for apps that need fully federated authentication (i.e. enterprise B2B apps serving customers with varying authentication requirements.) Most auth solutions are single-tenant, including most keycloak libraries for sveltekit.

This library enables apps to use multiple keycloak realms mapping customer user email domains to realms.

  • Each customer has fully configurable authentication per their enterprise reqiurements. (i.e. bring-your-own-identity-provider, 2-factor Auth for others, different authentication rules for every customer)
  • Roles and Role-based-access-controls configuration done entirely in Keycloak, simplify complexity of managing multitenancy within your app.
  • Easy to add new tenants at run-time using a simple externally loaded dependancy. (Yaml file that maps email domains to realms.)

Some added benefits of this library:

  • Single implementation in hooks.server.ts handles all routes and general API calls from client to server transparently
  • Low-client trust: No access tokens sent to client. Refresh token is only thing shared with client.
  • only HTTP-only strict domain cookies used. (for CSRF)
  • ability to retain communication between sveltekit app and keycloak within private network. (assumes keycloak and your sveltekit app containers use secure network.)

Install

npm install -S sveltkit-keycloak-multitenant

Setup Summary

  • Setup .ENV variables
  • add userinfo definition in app.d.ts locals
  • Add handler to hooks.server.ts file
  • Make a basic login form
  • Make a Logout route with SSR
  • Integrate the navigation and logout into layout.server.ts
  • Setup Keycloak
  • Update tenants.yaml with the tenants and clients setup in the last step

Setup Explained

1. Setup .ENV variables[1. Setup .ENV variables]

VariablePurposeExample (Default)
KEYCLOAK_URLURL of your Keycloak server taking OIDC authentication calls.https://auth.mayapp
KEYCLOAK_INTERNAL_URLIntenal URL of your Keycloak server within containerized network.http://keycloak:8085
LOGIN_PATHRelative path to user email form pre-login./auth/login
LOGOUT_PATHpath where you want to redirect to post logout. (Route must have a server side +page.server.ts/js file)/auth/logout
POST_LOGIN_PATH(Optional) post authentication redirect if initial landing was not a deep link. (default / if not set)/homepage

2. add userinfo definition in app.d.ts locals

User info and user roles is stored in locals by hooks.server.ts.

// See https://kit.svelte.dev/docs/types#app
/// <reference types="@auth/sveltekit" />
import type { UserInfo } from '$lib/server/keycloakservice';

declare global {
	namespace App {
		interface Locals {
			user: UserInfo | null,
		}
	}
}

export {};

3. Add handler to hooks.server.ts file

import type { Handle } from "@sveltejs/kit";
import { KeyCloakHandle } from "$lib/server/keycloakservice";

export const handle: Handle = KeyCloakHandle;

Or, if you have multiple handle functions running in all server-side calls, you can use sequence.

import { sequence } from '@sveltejs/kit/hooks';
import type { Handle } from "@sveltejs/kit";
import { KeyCloakHandle } from "$lib/server/keycloakservice";

export const handle: Handle = sequence(
  KeyCloakHandle,
  // Your other handle functions here...
);

4. Make a basic login form

Define a route that matches the ENV variable LOGIN_PATH that includes the basic email submission form. KeyCloakHandle will map the user to a tenant using email (domain), and redirect the user to the appropriate realm for authenication. Will also pass the email to the authentication for convenience so user does not have to specify that twice.

Form requires:

  • input with type=email and name=email
  • post action="?/login"
<script lang="ts"></script>

<div>
    <form method="post" action="?/login">
        <label for="email">email</label>
        <input type="email" name="email" />
        <button>Sign In</button>
    </form>
</div>

<style>
    ... your style here ...
<style>

5. Make a Logout route with SSR

Implement a route for whatever LOGOUT_PATH is going to be set to. When a logout button/link will be clicked, KeyCloakHandle will do a response redirect to that. This route must have +page.svelte and +page.server.ts file in order to force a server-side after logout. It does not matter what you want to put on the logout page beyond having those files present. This is required so that a SSR call will clear the locals, which layout.svelte then hides unauthenticated routes. It will also ensure the refresh cookie is expired on page render in the browser before they do anything else.

6. Integrate the navigation and logout into layout.server.ts

Pass through locals variables out as page data in +layout.server.ts. (Note, for pages that hide/show features within the page by role, you can pass the UserInfo object to the page renderer in that pages load method as well.)

import type { LayoutServerLoad } from "./$types"

export const load: LayoutServerLoad = async (event) => {

  return {
    user: event.locals.user,
  }
}

Example +layout.svelte file. (You can do anything with the UserInfo metadata returned in the laste step.) This shows:

  • protected route rendering for authenticated users
  • rendering of special routes based on custom roles you define in KeyCloak
  • (No rendering for unauthenticated/logged-out user. Will just render the content defined by LOGIN_PATH route in the slot.)
<script lang="ts">
    import type { PageData } from './$types';
    export let data: PageData;
</script>

<nav>
    {#if data.user}
        <span>Welcome: {data.user.username}</span>
        <span>tenant: {data.user.tenant}</span>
        <a href="/">Home</a>
        <a href="/protectedRoute1">Some Protected Route</a>
        <a href="/protectedRoute2">Another Protected Route</a>
        {#if data.user.roles && data.user.roles.includes('admin')}
            <a href="/admin">Admin</a>
        {/if}
        <a href="/auth/logout" data-sveltekit-preload-data="off">Logout</a>
    {/if}
</nav>

<slot />

<style>
    ... your style here ...
</style>

7. Setup Keycloak

8. Update tenants.yaml with the tenants and clients setup in the last step

Structure your metadata as follows... the key in the first level is the assumed "tenant name". (Might do this different in the future an leverage a service account in the master realm to get all this, for now this is simple enough. Easy enough to update the dependency in a container in Kubernetes or Docker. login route will try to refresh pulling this so the lookupt won't get out-of-sync if the file is updated.)

mustangs:
  client_id: 'webapp'
  client_secret: 'yourclientsecrethere'
  realm: 'mustangs'
  email_domain: 'mustangs.com'
camaros:
  client_id: 'webapp'
  client_secret: 'yourclientsecrethere'
  realm: 'camaros'
  email_domain: 'camaros.io'

Optional: Extend this with any additional attributes to pass into locals.

mustangs:
  client_id: 'webapp'
  client_secret: 'yourclientsecrethere'
  realm: 'mustangs'
  email_domain: 'mustangs.com'
  customer_logo: '',
  default_locale: 'en-US'

Note: If you want type support for those attributes in +layout.svelte/+page.svelte, you can extend the definition in app.d.ts:

import type { UserInfo } from '$lib/server/keycloakservice';

interface MyAppUserInfo extends UserInfo {
    customer_logo:string;
    default_locale:string;
}

declare global {
	namespace App {
		interface Locals {
			user: MyAppUserInfo | null,
		}
	}
}

export {};

FAQs

Package last updated on 19 Apr 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts