> Markdown version of [Authentication with Spring Security](https://vaadin.com/docs/next/hilla/lit/guides/security/spring-login). Section index: [llms.txt](https://vaadin.com/docs/next/hilla/llms.txt)

# Authentication with Spring Security

Authentication may be configured to use Spring Security. Since the downloaded application is a [Spring Boot](https://spring.io/projects/spring-boot) project, the easiest way to enable authentication is by adding [Spring Security](https://spring.io/projects/spring-security).

## <a id="dependencies"></a>Dependencies

Using Spring Security requires some dependencies. Add the following to your project Maven file:

`pom.xml`

```xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
```

After doing this, the application is protected with a default Spring login view. By default, it has a single user (i.e., 'user') and a random password. When you add `logging.level.org.springframework.security = DEBUG` to the `application.properties` file, the username and password are shown in the console when the application starts.

## <a id="server-configuration"></a>Server Configuration

To implement your own security configuration, create a new configuration class that uses the `VaadinSecurityConfigurer` class. Then annotate it to enable security.

`VaadinSecurityConfigurer` is a helper which provides default bean implementations for [SecurityFilterChain](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/SecurityFilterChain.html) and [WebSecurityCustomizer](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityCustomizer.html). It takes care of the basic configuration for requests, so that you can concentrate on your application-specific configuration.

`SecurityConfig.java`

```java
@EnableWebSecurity
@Configuration
public class SecurityConfig {

  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http, RouteUtil routeUtil) throws Exception {
    http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
      // use a custom login view and redirect to root on logout
      configurer.loginView("/login", "/");
    });
    return http.build();
  }

  @Bean
  public UserDetailsManager userDetailsService() {
    // Configure users and roles in memory
    return new InMemoryUserDetailsManager(
      // the {noop} prefix tells Spring that the password is not encoded
      User.withUsername("user").password("{noop}user").roles("USER").build(),
      User.withUsername("admin").password("{noop}admin").roles("ADMIN", "USER").build()
    );
  }
}
```

> **Warning: Never Hard-Coded Credentials**
>
> You should never hard-code credentials in an application. The [Security](https://vaadin.com/docs/next/hilla/guides/security/spring-login.md#appendix-production-data-sources) documentation has examples of setting up LDAP or SQL-based user management.

### <a id="public-views-resources"></a>Public Views & Resources

Public views need to be added to the configuration in `securityFilterChain`. Here’s an example of this:

`SecurityConfig.java`

```java
  @Bean
  SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(registry -> {
        registry.requestMatchers("/public-view").permitAll(); // custom matcher
    });
    http.with(VaadinSecurityConfigurer.vaadin(), configurer -> { ... });
    return http.build();
  }
```

Public resources can be added to the configuration in `securityFilterChain` like so:

`SecurityConfig.java`

`SecurityConfigDemo.java`

```java
http.authorizeHttpRequests(
                auth -> auth.requestMatchers("/images/**").permitAll());
```

## <a id="login-view"></a>Login View

Use the `<vaadin-login-overlay>` component to create the following login view, so that the autocomplete and password features of the browser are used.

`frontend/login-view.ts`

`login-view.ts`

```typescript
import '@vaadin/login';
import { html, LitElement } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import type { LoginResult } from '@vaadin/hilla-frontend';
import type { RouterLocation, WebComponentInterface } from '@vaadin/router';
import { login } from './auth';

@customElement('login-view')
export class LoginView extends LitElement implements WebComponentInterface {
  @state()
  private error = false;

  // the url to redirect to after a successful login
  private returnUrl?: string;

  render() {
    return html`
      <vaadin-login-overlay opened .error="${this.error}" @login="${this.login}">
      </vaadin-login-overlay>
    `;
  }

  async login(event: CustomEvent): Promise<LoginResult> {
    this.error = false;
    // use the login helper method from auth.ts, which in turn uses
    // Vaadin provided login helper method to obtain the LoginResult
    const result = await login(event.detail.username, event.detail.password, {
      navigate: (toPath: string) => {
        // Consider absolute path to be within the application context.
        const serverUrl = toPath.startsWith('/') ? new URL(`.${toPath}`, document.baseURI) : toPath;

        // If a login redirect was initiated by the client router, this.returnUrl contains the original destination.
        // Otherwise, use the URL provided by the server.
        // As we do not know if the target is a resource or a Hilla view or a Flow view, we cannot just use Router.go
        window.location.replace(this.returnUrl ?? serverUrl);
      },
    });
    this.error = result.error;

    return result;
  }

  onAfterEnter(location: RouterLocation) {
    this.returnUrl = location.redirectFrom;
  }
}
```

The authentication helper methods in the code examples are grouped in a separate TypeScript file, as shown in the following. It utilizes a Hilla `login()` helper method for authentication based on Spring Security.

`frontend/auth.ts`

`auth.ts`

```typescript
// Uses the Vaadin provided login an logout helper methods
import {
  login as loginImpl,
  type LoginOptions,
  type LoginResult,
  logout as logoutImpl,
  type LogoutOptions,
} from '@vaadin/hilla-frontend';
import type UserInfo from 'Frontend/generated/com/vaadin/demo/fusion/security/authentication/UserInfo';
import { UserInfoService } from 'Frontend/generated/endpoints';

interface Authentication {
  user: UserInfo;
  timestamp: number;
}

let authentication: Authentication | undefined;

const AUTHENTICATION_KEY = 'authentication';
const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000;
/**
 * Forces the session to expire and removes user information stored in
 * `localStorage`.
 */
export function setSessionExpired() {
  authentication = undefined;

  // Delete the authentication from the local storage
  localStorage.removeItem(AUTHENTICATION_KEY);
}


// Get authentication from local storage
const storedAuthenticationJson = localStorage.getItem(AUTHENTICATION_KEY);
if (storedAuthenticationJson !== null) {
  const storedAuthentication = JSON.parse(storedAuthenticationJson) as Authentication;
  // Check that the stored timestamp is not older than 30 days
  const hasRecentAuthenticationTimestamp =
    new Date().getTime() - storedAuthentication.timestamp < THIRTY_DAYS_MS;
  if (hasRecentAuthenticationTimestamp) {
    // Use loaded authentication
    authentication = storedAuthentication;
  } else {
    // Delete expired stored authentication
    setSessionExpired();
  }
}

/**
 * Login wrapper method that retrieves user information.
 *
 * Uses `localStorage` for offline support.
 */
export async function login(
  username: string,
  password: string,
  options: LoginOptions = {}
): Promise<LoginResult> {
  return await loginImpl(username, password, {
    ...options,
    async onSuccess() {
      // Get user info from endpoint
      const user = await UserInfoService.getUserInfo();
      authentication = {
        user,
        timestamp: new Date().getTime(),
      };

      // Save the authentication to local storage
      localStorage.setItem(AUTHENTICATION_KEY, JSON.stringify(authentication));
    },
  });
}

/**
 * Login wrapper method that retrieves user information.
 *
 * Uses `localStorage` for offline support.
 */
export async function logout(options: LogoutOptions = {}) {
  return await logoutImpl({
    ...options,
    onSuccess() {
      setSessionExpired();
    },
  });
}

/**
 * Checks if the user is logged in.
 */
export function isLoggedIn() {
  return !!authentication;
}

/**
 * Checks if the user has the role.
 */
export function isUserInRole(role: string) {
  if (!authentication) {
    return false;
  }

  return authentication.user.authorities.includes(`ROLE_${role}`);
}
```

`UserInfo.java`

```java
package com.vaadin.demo.fusion.security.authentication;

import jakarta.annotation.Nonnull;

import java.util.Collection;
import java.util.Collections;

/**
 * User information used in client-side authentication and authorization. To be
 * saved in browsers’ LocalStorage for offline support.
 */
public class UserInfo {

    @Nonnull
    private String name;
    @Nonnull
    private Collection<String> authorities;

    public UserInfo(String name, Collection<String> authorities) {
        this.name = name;
        this.authorities = Collections.unmodifiableCollection(authorities);
    }

    public String getName() {
        return name;
    }

    public Collection<String> getAuthorities() {
        return authorities;
    }

}
```

After the login view is defined, you should define a route for it in the `routes.ts` file. Don’t forget to import the `login-view` component, otherwise the login view won’t be visible.

`frontend/routes.ts`

```typescript
import './login-view';
// ...
const routes = [
  {
    path: '/login',
    component: 'login-view'
  },
  // more routes
]
```

Update the `SecurityConfig` to use the `loginView()` helper, which sets up everything needed for a Hilla-based login view:

`SecurityConfigDemo.java`

```java
http.with(VaadinSecurityConfigurer.vaadin(),
                configurer -> configurer.loginView("/login"));
```

Note, the `path` for the login view in `routes.ts` must match the one defined in `SecurityConfig`.

## <a id="protect-hilla-views"></a>Protect Hilla Views

Access control for Hilla views cannot be based on URL filtering. The Hilla view templates are always in the bundle and can be accessed by anyone. Therefore, it’s important not to store any sensitive data in the view template.

The data should go to browser-callable services, and those services should be protected instead. Read [Controlling Service Access](https://vaadin.com/docs/next/hilla/guides/security/configuring.md) on protecting services to learn more about this.

You can still achieve a better user experience by redirecting unauthenticated requests to the login view with the route action.

Below is an example using the route action:

`frontend/routes.ts`

```typescript
import { Commands, Context, Route } from '@vaadin/router';
import './my-view';
// ...
const routes = [
  // ...
  {
    path: '/my-view',
    action: (_: Context, commands: Commands) => {
      if (!isLoggedIn()) {
        return commands.redirect('/login');
      }
      return undefined;
    },
    component: 'my-view'
  }
  // ...
]
```

You can also add the route action to the parent layout, so that all child views are protected. In this case, the login component should be outside of the main layout — that is, not a child of the main layout in the route configuration.

`frontend/routes.ts`

```typescript
import { Commands, Context, Route } from '@vaadin/router';
import './login-view';
// ...
const routes = [
  // ...
  {
    path: '/login',
    component: 'login-view'
  },
  {
    path: '/',
    action: (_: Context, commands: Commands) => {
      if (!isLoggedIn()) {
        return commands.redirect('/login');
      }
      return undefined;
    },
    component: 'main-layout',
    children: [
      // ...
    ]
  }
  // ...
]
```

The `isLoggedIn()` method in these code examples uses a `lastLoginTimestamp` variable stored in the [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to check if the user is logged in. The `lastLoginTimestamp` variable needs to be reset when logging out.

Using `localStorage` permits navigation to sub-views without having to check authentication from the backend on every navigation. In this way, the authentication check can work offline.

## <a id="logout"></a>Logout

To handle logging out, you can use the `logout()` helper defined earlier in `auth.ts`. You typically would use a button to handle logout, instead of navigation and a route. This is to avoid timing problems between rendering views and logging out. For example, you can do the following:

```html
<vaadin-button @click="${() => logout()}">Logout</vaadin-button>
```

### <a id="configuration-helper-alternatives"></a>Configuration Helper Alternatives

`VaadinSecurityConfigurer` configures HTTP security to bypass framework internal resources. If you prefer to make your own configuration, instead of using the helper, the matcher for these resources can be retrieved with `VaadinSecurityConfigurer.getDefaultHttpSecurityPermitMatcher()`.

For example, if you want to allow public access to certain views and require authentication for all other requests, you can configure it as follows:

`SecurityConfig.java`

```java
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(registry ->
        registry.requestMatchers(
           VaadinSecurityConfigurer.getDefaultHttpSecurityPermitMatcher()
        ).permitAll()
        .requestMatchers("/public-view").permitAll() // custom matcher
        .anyRequest().authenticated()
    );
    ...
    return http.build();
}
```

Similarly, the matcher for static resources to be ignored is available as `VaadinSecurityConfigurer.getDefaultWebSecurityIgnoreMatcher()`:

`SecurityConfig.java`

```java
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(registry ->
        registry.requestMatchers(
           VaadinSecurityConfigurer.getDefaultWebSecurityIgnoreMatcher()
        ).permitAll()
        .requestMatchers("static/**").permitAll() // custom matcher
        .anyRequest().authenticated()
    );
    ...
    return http.build();
}
```

## <a id="implement-stateful-authentication"></a>Implement Stateful Authentication

Vaadin applications that have both Hilla and Flow views, can be configured to use stateful authentication. This requires some basic [steps for Hilla](https://vaadin.com/docs/next/hilla/lit/guides/security/spring-login.md) and [steps for Flow](https://vaadin.com/docs/next/flow/security/enabling-security.md). An example project that demonstrates the stateful authentication for the hybrid case can be found in [GitHub](https://github.com/vaadin/flow-hilla-hybrid-example/tree/v24.4).

For this example, you’d add [Spring Security dependency](#dependencies) and then set up [Security Configuration](#server-configuration).

The browser page needs to be reloaded after login and, if you want to exclude the `LoginView` from the automatically generated menu, you need to set:

`LoginView.tsx`

```typescript
export const config: ViewConfig = {
    menu: { exclude: true}
}
```

The next step is to protect the views with login and roles. Add the annotations to the server-side views, as described in [Annotating View Classes](https://vaadin.com/docs/next/flow/security/enabling-security.md#annotating-the-view-classes). Add the `ViewConfig` object to the client-side views, as shown below:

`HillaView.tsx`

```typescript
export const config: ViewConfig = {
    loginRequired: true,
    rolesAllowed: ['ROLE_USER'],
};
```

Use `createMenuItems` function to create a main layout, that filters out protected views and shows the only allowed views for an authenticated user.

`frontend/views/@layout.tsx`

```typescript
import { createMenuItems } from '@vaadin/hilla-file-router/runtime.js';
import { AppLayout, SideNav } from '@vaadin/react-components';
import { Outlet, useLocation, useNavigate } from 'react-router';

// inside layout component:
const navigate = useNavigate();
const location = useLocation();
// ...
<AppLayout>
    // ...
    // SideNav Vaadin component inside <AppLayout>
    <SideNav
        onNavigate={({ path }) => navigate(path!)}
        location={location}>
        {
            createMenuItems().map(({ to, title }) => (
                <SideNavItem path={to} key={to}>{title}</SideNavItem>
            ))
        }
    </SideNav>
</AppLayout>
```

As an alternative, add the menu items manually and specify the access options:

`frontend/MainLayout.tsx`

`MainLayout.tsx`

```typescript
import { Suspense } from 'react';
import { NavLink, Outlet } from 'react-router';
import { AppLayout } from '@vaadin/react-components/AppLayout.js';
import { Button } from '@vaadin/react-components/Button.js';
import { DrawerToggle } from '@vaadin/react-components/DrawerToggle.js';
import { useAuth } from './auth';
import { useRouteMetadata } from './routing';

const navLinkClasses = ({ isActive }: any) =>
  `block rounded-m p-s ${isActive ? 'bg-primary-10 text-primary' : 'text-body'}`;

export default function MainLayout() {
  const currentTitle = useRouteMetadata()?.title ?? 'My App';
  const { state, logout } = useAuth();

  return (
    <AppLayout primarySection="drawer">
      <div slot="drawer" className="flex flex-col justify-between h-full p-m">
        <header className="flex flex-col gap-m">
          <h1 className="text-l m-0">My App</h1>
          <nav>
            {state.user ? (
              <NavLink className={navLinkClasses} to="/">
                Hello World
              </NavLink>
            ) : null}
            {state.user ? (
              <NavLink className={navLinkClasses} to="/about">
                About
              </NavLink>
            ) : null}
          </nav>
        </header>
        <footer className="flex flex-col gap-s">
          {state.user ? (
            <>
              <div className="flex items-center gap-s">{state.user.name}</div>
              <Button onClick={async () => logout()}>Sign out</Button>
            </>
          ) : (
            <a href="/login">Sign in</a>
          )}
        </footer>
      </div>

      <DrawerToggle slot="navbar" aria-label="Menu toggle"></DrawerToggle>
      <h2 slot="navbar" className="text-l m-0">
        {currentTitle}
      </h2>

      <Suspense>
        <Outlet />
      </Suspense>
    </AppLayout>
  );
}
```

Then you can add a custom configuration for routes — this is optional. Routes configuration is usually present in `routes.tsx` file, which is generated by Vaadin. This should be enough for common cases:

`Frontend/generated/routes.tsx`

```typescript
import { RouterConfigurationBuilder } from '@vaadin/hilla-file-router/runtime.js';
import Flow from 'Frontend/generated/flow/Flow';
import fileRoutes from 'Frontend/generated/file-routes.js';

export const { router, routes } = new RouterConfigurationBuilder()
    .withFileRoutes(fileRoutes)
    .withFallback(Flow)
    .protect()
    .build();
```

Note that the client-side views are protected by default with a `protect()` function. If a custom routing is desired, the generated file `Frontend/generated/routes.tsx` should be copied to `Frontend/routes.tsx` and modified.

For example, you may want to change the login URL:

`Frontend/generated/routes.tsx`

```typescript
new RouterConfigurationBuilder().protect('/custom-login-url')
```

Add specific React route objects with `withReactRoutes` function:

`Frontend/generated/routes.tsx`

```typescript
new RouterConfigurationBuilder().withReactRoutes(
    [
      {
        element: <MainLayout />,
        handle: { title: 'Main' },
        children: [
            { path: '/hilla', element: <HillaView />, handle: { title: 'Hilla' } }
        ],
      },
      { path: '/login', element: <Login />, handle: { title: 'Login' } }
    ]
)
```

Disable server-side views or add a fallback component with a `withFallback` function. For example, 404 page that will be shown if no client-side view is found for a given URL.

`Frontend/generated/routes.tsx`

```typescript
    new RouterConfigurationBuilder().withFallback(PageNotFoundReactComponent)
```

## <a id="appendix-production-data-sources"></a>Appendix: Production Data Sources

The example given here of managing users in memory is valid for test applications. However, Spring Security offers other implementations for production scenarios.

### <a id="sql-authentication"></a>SQL Authentication

The following example demonstrates how to access an SQL database with tables for users and authorities.

`SecurityConfig.java`

**VaadinSecurityConfigurer**

```java
@EnableWebSecurity
@Configuration
public class SecurityConfig {
  //...

  @Bean
  UserDetailsService userDetailsService(DataSource dataSource) {
      // Configure users and roles in a JDBC database
      JdbcUserDetailsManager jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource);
      jdbcUserDetailsManager.setUsersByUsernameQuery(
              "SELECT username, password, enabled FROM users WHERE username=?");
      jdbcUserDetailsManager.setAuthoritiesByUsernameQuery(
              "SELECT username, authority FROM authorities WHERE username=?");
      return jdbcUserDetailsManager;
  }

  @Bean
  PasswordEncoder passwordEncoder() {
      return new BCryptPasswordEncoder();
  }
}
```

### <a id="ldap-authentication"></a>LDAP Authentication

This next example shows how to configure authentication by using an LDAP repository:

`SecurityConfig.java`

**VaadinSecurityConfigurer**

```java
@EnableWebSecurity
@Configuration
public class SecurityConfig {
  //...

    @Bean
    public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() { // (1)
        return EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
    }

    @Bean
    AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource, LdapAuthoritiesPopulator authorities) {
        LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
                contextSource, NoOpPasswordEncoder.getInstance());
        factory.setUserDnPatterns("uid={0},ou=people");
        factory.setUserSearchBase("ou=people");
        factory.setPasswordAttribute("userPassword");
        factory.setLdapAuthoritiesPopulator(authorities);
        return factory.createAuthenticationManager();
    }

    @Bean
    LdapAuthoritiesPopulator authorities(BaseLdapPathContextSource contextSource) {
        String groupSearchBase = "ou=groups";
        DefaultLdapAuthoritiesPopulator authorities =
                new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
        authorities.setGroupSearchFilter("member={0}");
        return authorities;
    }
}
```

1. `VaadinSecurityConfigurer` example here configure [embedded UnboundID LDAP server](https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#servlet-authentication-ldap-embedded). You can also configure [LDAP ContextSource](https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/ldap.html#servlet-authentication-ldap-contextsource) to connect to other LDAP server.

Remember to add the corresponding LDAP client dependency to the project:

`pom.xml`

```xml
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-ldap</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
```
