Getting started with SSO Kit

Step-by-step guide on how to use SSO Kit in your application.

SSO Kit builds upon Spring Boot and Spring Security. It comes with a starter module that configures the security settings needed to authenticate with an identity provider.

1. Create a Hilla application without authentication

You can create a Hilla application by entering the following from the command-line:

npx @hilla/cli init --react <your-project-name>

2. Backend

Once you’ve created the Hilla application, you can begin securing it by installing and configuring SSO Kit on the backend. The following section shows how to block unauthorized users from using a button in an application. You can install and update SSO Kit by adding it as a dependency to your application in the pom.xml file.

2.1. Add the SSO Kit dependency

Add the sso-kit-starter module and other required dependencies to the pom.xml of a Vaadin application like so:


Check the SSO Kit releases page for the latest version, or a different version of the sso-kit-starter dependency.

2.2. Configure endpoints

For Hilla to be able to parse and generate endpoints from an application and from SSO Kit for your frontend, you have to add a configuration to the Hilla Maven plugin with your application and SSO Kit package names like this:

    <!-- Add this configuration -->
    <!-- ... -->

By default, the package name in generated Hilla projects is com.example.application. SSO Kit uses the package name dev.hilla.sso.starter. To be able to find the annotated endpoints in SSO Kit that are required, you have to whitelist its package name in your Spring Boot application. To do this, open your and add these packages to the @SpringBootApplication annotation like so:

@SpringBootApplication(scanBasePackages = {
  "com.example.application", // Application package
  "dev.hilla.sso.starter" // SSO Kit package
public class Application // ...

2.3. Configure the SSO provider in Spring

Next, you need to set some configuration properties to connect SSO Kit to your OpenID Connect provider. These properties can be added to your file where you give the provider URL and the client registration details, such as credentials and scope.

Provider definition is configured within the namespace where you give a key to identify your provider, such as keycloak. You can use the same key to register the client for that provider within the namespace, where you specify client credentials and the requested scope.

The scope is a list of keywords to request the provider for a specific set of information, such as user profile, email or roles. The following is an example of the properties to set to enable a Keycloak instance to perform authentication:

# Customize the following property values for your Keycloak configuration:

2.4. Secure the application

A Hilla application includes front-end code and back-end endpoints. Both of them can and should benefit from the authentication protection.

2.4.1. Protect the example endpoint

Hilla allows fine-grained authorization on endpoints and endpoint methods. You can use annotations like @PermitAll or @RolesAllowed(…​) to declare who can access what.

To try this feature, replace the @AnonymousAllowed annotation in with @PermitAll, so that unauthenticated users will be unable to access all endpoint methods. You could also apply the same annotation at the method level for more fine-grained control.

Start the application using the ./mvnw command (.\mvnw on Windows). Then try the application in the browser. It should work correctly, except that when you click on the Say hello button, nothing happens. This is because the endpoint is no longer accessible without authentication.

3. Frontend

Once the backend is secure, you can begin extending authentication features to the frontend. The following section shows how to display user information (e.g., a name) on secured views and enable users to log in and out.

3.1. Implement authentication state

All of the essential authentication state is already available in a global variable and can be used as application state. Create a new file named useAuth.tsx and define a React Context:

import { createContext, Dispatch, SetStateAction } from "react";
import SingleSignOnData from "./generated/dev/hilla/sso/starter/SingleSignOnData";

// Used for access control
export type AccessProps = Readonly<{
    requiresLogin?: boolean;

// The context type
export type Authentication = Readonly<{
    state: SingleSignOnData;
    hasAccess: (route: AccessProps) => boolean;
    clearAuthInfo: () => void;

// All necessary data is already loaded in the Hilla global variable
export const initialState = (window as any).Hilla.SSO as SingleSignOnData;

// The context itself
export const AuthContext = createContext<Authentication>({
    state: initialState,
    hasAccess: () => false,
    clearAuthInfo: () => { },

// The hook to use the context
export const useAuth = (
    state: SingleSignOnData,
    setState: Dispatch<SetStateAction<SingleSignOnData>>
): Authentication => {
    return {
        hasAccess: (route: AccessProps) => {
            return !route.requiresLogin || state.authenticated;
        clearAuthInfo: () => {
                authenticated: false,
                backChannelLogoutEnabled: false,
                logoutLink: undefined,
                roles: [],

Next, add the state and the context to App.tsx and wrap the RouterProvider:

import router from 'Frontend/routes.js';
import { useState } from 'react';
import { RouterProvider } from 'react-router-dom';
import { AuthContext, initialState, useAuth } from './useAuth';

export default function App() {
  const [state, setState] = useState(initialState);

  return <AuthContext.Provider value={useAuth(state, setState)}>
    <RouterProvider router={router} />
  </AuthContext.Provider >;

3.2. Add log-in and log-out buttons

As an example, add two buttons to the drawer footer — one to sign in, and another to sign out. When signing out, it’s important to invoke the logout function provided by Hilla to perform logout on the server. Then, load the SSO provider logout page.

import { logout } from '@hilla/frontend';
import { Button } from '@hilla/react-components/Button.js';
import { AuthContext } from 'Frontend/useAuth';
import { Suspense, useContext } from 'react';

// Use the AuthContext
const { state } = useContext(AuthContext);

// Define button event handlers
async function signOut() {
  await logout(); // Logout on the server
  location.href = state.logoutLink!;

function signIn() {
  location.href = state.loginLink;

// Add the buttons to the footer
<footer slot="drawer">
    ? <Button onClick={signOut}>Sign out</Button>
    : <Button onClick={signIn}>Sign in</Button>

3.3. Add access control

You can protect your views by verifying that each authentication has happened before loading the view.

Open the frontend/routes.tsx and add the requiredLogin parameter to a view:

import { AccessProps } from './useAuth';

// Enrich the ViewRouteObject type with AccessProps
export type ViewRouteObject = (IndexViewRouteObject | NonIndexViewRouteObject) & AccessProps;

// Add requiresLogin to the About View
  path: '/about',
  element: <AboutView />,
  handle: { icon: 'la la-file', title: 'About' },
  requiresLogin: true,

Next, in the main layout, filter the menu:

// Gather the hasAccess function
const { state, hasAccess } = useContext(AuthContext);

// Filter the menu when rendering
{menuRoutes.filter(hasAccess).map(({ path, handle: { icon, title } }) => (
  // ...

Now the About item in the menu appears only when authenticated.

3.4. Show user information

The SSO Kit provides a default endpoint to get information about the authenticated user. You can implement yours if you want to customize the returned object and its fields.

As the About page is now protected, that’s a perfect place to show some information about the current user:

import User from "Frontend/generated/dev/hilla/sso/starter/endpoint/User";
import { UserEndpoint } from "Frontend/generated/endpoints";
import { useEffect, useState } from "react";

// Store the authenticated user
const [user, setUser] = useState<User | undefined>();

// Fetch the authenticated user from the server
useEffect(() => {
}, []);

// Add some output
<p>Username: {user?.preferredUsername}</p>
<p>Full name: {user?.fullName}</p>
<p>Email: {user?.email}</p>

4. Single sign-off

SSO Kit provides two methods for logging out the user. They’re defined by the OpenID Connect specification like so:

4.1. RP-initiated logout

RP-initiated logout (i.e., Relaying Party, the application) enables the user to logout from the application itself, ensuring the connected provider session is terminated.

4.2. Back-channel logout

Back-Channel Logout is a feature that enables the provider to close user sessions from outside the application. For example, it can be done from the provider’s user dashboard or from another application.

4.2.1. Enable the feature

To enable the feature in the application, you need to set the hilla.sso.back-channel-logout property to true. You would do this like you see here:


The client should then be configured on the provider’s dashboard to send logout requests to a specific application URL: /logout/back-channel/{registration-key}, where {registration-key} is the provider key.

4.2.2. Modify the frontend

As an example, show a dialog when the user is logged out from outside the application. You can do that in the main layout file:

import { ConfirmDialog } from '@hilla/react-components/ConfirmDialog.js';
import { BackChannelLogoutEndpoint } from 'Frontend/generated/endpoints';
import { Suspense, useContext, useEffect, useState } from 'react';

// Add a state that is modified when the log-out event happens
const [backChannelLogout, setBackChannelLogout] = useState(false);

// Gather the clearAuthInfo function from the AuthContext
const { state, hasAccess, clearAuthInfo } = useContext(AuthContext);

// Subscribe to the endpoint and update the state, accordingly
useEffect(() => {
  if (state.backChannelLogoutEnabled) {
    const subscription = BackChannelLogoutEndpoint.subscribe();
    subscription.onNext(() => {
}, []);

// Add the click event handlers
async function loginAgain() {
  await logout(); // Logout on the server
  location.href = state.loginLink;

async function stayOnPage() {
  await logout(); // Logout on the server
  clearAuthInfo(); // Clear the user info on the client

// Finally, add the dialog
<ConfirmDialog header='Logged out' cancelButtonVisible opened={backChannelLogout}
  onConfirm={loginAgain} onCancel={stayOnPage}>
  <p>You have been logged out. Do you want to log in again?</p>

You can trigger a logout externally using the provider tools. For Keycloak, you can sign out a session from the admin console or visit the page