> Markdown version of [Hilla](https://vaadin.com/docs/v24/building-apps/security/add-login/hilla). Section index: [llms.txt](https://vaadin.com/docs/v24/building-apps/llms.txt)

# Hilla Login

In this guide, you’ll learn how to create a login view using Hilla and React. A hands-on mini-tutorial at the end will walk you through **enabling Spring Security**, setting up **in-memory authentication**, and integrating a login form into a real Vaadin application.

> **Warning: Never Use Hard-Coded Credentials In Production**
>
> In-memory authentication is convenient for development, but production applications must use a more secure approach, such as **JDBC authentication**, **LDAP authentication**, or **OAuth 2.0**. Refer to the [Spring Security Reference Manual](https://docs.spring.io/spring-security/reference/servlet/authentication/index.html) for more details.

## <a id="client-configuration"></a>Client Configuration

Vaadin includes a client-side security extension that integrates with Spring Security on the server. To configure it, follow these steps:

1. Create a **user information endpoint** on the server.

2. Set up a **React context** in the frontend.

3. Enable the context in the **`App` component**.

### <a id="create-a-user-information-endpoint"></a>Create a User Information Endpoint

Since authentication happens on the server, you need a way to pass user information to the client. To achieve this, create a `@BrowserCallable` service that returns user details:

```java
@BrowserCallable
public class UserInfoService {

    @PermitAll // (1)
    public @NonNull UserInfo getUserInfo() {
        var auth = SecurityContextHolder.getContext().getAuthentication(); // (2)
        var authorities = auth.getAuthorities()
            .stream()
            .map(GrantedAuthority::getAuthority)
            .toList();
        return new UserInfo(auth.getName(), authorities); // (3)
    }
}
```

1. Allows **all authenticated users** to access `getUserInfo()`.

2. Retrieves user details from Spring Security.

3. Returns user information to the client.

Vaadin does not provide a built-in user information type, so you need to define your own:

```java
public record UserInfo(
    @NonNull String name,
    @NonNull Collection<String> authorities
) {
}
```

You can include **additional user details** (e.g., email, avatar) in this type and access them in React.

### <a id="setup-the-authentication-context"></a>Setup the Authentication Context

Now, configure a **React context** to store authentication details, using Vaadin’s helper `configureAuth()`:

frontend/security/auth.ts

```typescript
import { configureAuth } from '@vaadin/hilla-react-auth';
import { UserInfoService } from "Frontend/generated/endpoints";

const auth = configureAuth(UserInfoService.getUserInfo) // (1)
export const useAuth = auth.useAuth // (2)
export const AuthProvider = auth.AuthProvider // (3)
```

1. Uses `UserInfoService` to fetch user details from the server.

2. `useAuth` is a **React hook** that provides authentication state within views.

3. `AuthProvider` is a **React context provider**.

### <a id="enable-the-authentication-context"></a>Enable the Authentication Context

Next, wrap the root component with `<AuthProvider>`. First, **move `index.tsx` from `src/main/frontend/generated` to `src/main/frontend`**. Then, modify it as follows:

frontend/index.tsx

```tsx
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider } from 'react-router';
import { router } from 'Frontend/generated/routes.js';
// tag::snippet[]
import { AuthProvider } from "Frontend/security/auth";
// end::snippet[]

function App() {
    return (
// tag::snippet[]
        <AuthProvider>
{/* end::snippet[] */}
            <RouterProvider router={router} />
{/* tag::snippet[] */}
        </AuthProvider>
// end::snippet[]
    );
}
...
```

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

The login view is a standard [Hilla view](https://vaadin.com/docs/v24/building-apps/views/add-view/hilla.md). The easiest way to implement one is by using the [LoginForm](https://vaadin.com/docs/v24/components/login.md) component:

frontend/views/login.tsx

```tsx
import { LoginForm } from "@vaadin/react-components";
import { ViewConfig } from "@vaadin/hilla-file-router/types.js";
import { useSearchParams } from "react-router";

export const config: ViewConfig = {
    skipLayouts: true, // (1)
    menu: {
        exclude: true // (2)
    }
}

export default function LoginView() {
    const [searchParams] = useSearchParams()
    const hasError = searchParams.has("error"); // (3)

    return (
        <main className="flex justify-center items-center w-full h-full">
            <LoginForm error={hasError} action="login"/> {/* <4> */}
        </main>
    )
}
```

1. Disables auto layout to prevent the login view from being embedded in a [router layout](https://vaadin.com/docs/v24/building-apps/views/add-router-layout.md).

2. Excludes the login view from the navigation menu.

3. Detects if the `?error` query parameter is present.

4. Instructs the login form to send a `POST` request to `/login` for authentication.

Spring Security’s **form login** mechanism automatically processes authentication requests sent to `/login`. When authentication fails, the user is redirected back to the login page with `?error`, which the login view handles.

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

To instruct Spring Security to use your login view, modify your security configuration:

`SecurityConfig.java`

```java
@EnableWebSecurity
@Configuration
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Configure Vaadin's security using VaadinSecurityConfigurer
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
// tag::snippet[]
            configurer.loginView("/login");
// end::snippet[]
        });

        return http.build();
    }
    ...
}
```

Now, when a user tires to access a protected view, they’ll be redirected to the login page.

> **Important:** By default, Vaadin **permits access to Hilla views and router layouts**, and **denies access to Flow views and router layouts**. This is covered in more detail in the [Protect Views](https://vaadin.com/docs/v24/building-apps/security/protect-views.md) guide.

## <a id="try-it"></a>Try It

In this mini-tutorial, you’ll enable security and add a login form to a real Vaadin application. This serves as a foundation for future **security-related mini-tutorials**.

Set Up the Project

First, generate a [walking skeleton with a Hilla UI](https://vaadin.com/docs/v24/getting-started/start.md), [open](https://vaadin.com/docs/v24/getting-started/import.md) it in your IDE, and [run](https://vaadin.com/docs/v24/getting-started/run.md) it with hotswap enabled.

> **Note:** Security configuration changes may require a **manual restart** for them to take effect. Hotswap may not be enough.

Add the Spring Security Dependency

Add the following Spring Security dependency to `pom.xml`:

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

Create the Security Configuration Class

Create a new package: `[application package].security`

Inside this package, create a `SecurityConfig` class:

`SecurityConfig.java`

```java
import com.vaadin.flow.spring.security.VaadinAwareSecurityContextHolderStrategyConfiguration;
import com.vaadin.flow.spring.security.VaadinSecurityConfigurer;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Configure Vaadin's security using VaadinSecurityConfigurer
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
        });

        return http.build();
    }

    @Bean
    public UserDetailsManager userDetailsManager() {
        LoggerFactory.getLogger(SecurityConfig.class)
            .warn("Using in-memory user details manager!");
        var user = User.withUsername("user")
                .password("{noop}user")
                .roles("USER")
                .build();
        var admin = User.withUsername("admin")
                .password("{noop}admin")
                .roles("ADMIN")
                .build();
        return new InMemoryUserDetailsManager(user, admin);
    }
}
```

Create the User Information Endpoint

Inside the `[application package].security` package, create a `UserInfo` record:

UserInfo.java

```java
import org.jspecify.annotations.NonNull;
import java.util.Collection;

public record UserInfo(@NonNull String name,
                       @NonNull Collection<String> authorities) {
}
```

Then create a `UserInfoService` class:

UserInfoService.java

```java
import com.vaadin.hilla.BrowserCallable;
import jakarta.annotation.security.PermitAll;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;

@BrowserCallable
public class UserInfoService {

    @PermitAll
    public UserInfo getUserInfo() {
        var auth = SecurityContextHolder.getContext().getAuthentication();
        var authorities = auth.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority).toList();
        return new UserInfo(auth.getName(), authorities);
    }
}
```

Setup the Authentication Context

Create a new directory: `src/main/frontend/security`

Inside this directory, create an `auth.ts` file:

frontend/security/auth.ts

```typescript
import { configureAuth } from '@vaadin/hilla-react-auth';
import { UserInfoService } from "Frontend/generated/endpoints";

const auth = configureAuth(UserInfoService.getUserInfo)
export const useAuth = auth.useAuth
export const AuthProvider = auth.AuthProvider
```

Enable the Authentication Context

Locate the `src/main/frontend/generated/index.tsx` file and move it to `src/main/frontend`. Then add the `AuthProvider`:

frontend/index.tsx

```tsx
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider } from 'react-router';
import { router } from 'Frontend/generated/routes.js';
// tag::snippet[]
import { AuthProvider } from "Frontend/security/auth";
// end::snippet[]

function App() {
// tag::snippet[]
    return <AuthProvider><RouterProvider router={router} /></AuthProvider>;
// end::snippet[]
}
...
```

Create the Login View

In the `src/main/frontend/views` directory, create a new file `login.tsx`:

frontend/views/login.tsx

```tsx
import { LoginForm } from "@vaadin/react-components";
import { ViewConfig } from "@vaadin/hilla-file-router/types.js";
import { useSearchParams } from "react-router";

export const config: ViewConfig = {
    skipLayouts: true,
    menu: {
        exclude: true
    }
}

export default function LoginView() {
    const [searchParams] = useSearchParams()
    const hasError = searchParams.has("error");

    return (
        <main className="flex justify-center items-center w-full h-full">
            <LoginForm error={hasError}
                       action="login"/>
        </main>
    )
}
```

Update the Spring Security Configuration

Modify `SecurityConfig` to reference the new login view:

`SecurityConfig.java`

```java
@EnableWebSecurity
@Configuration
@Import(VaadinAwareSecurityContextHolderStrategyConfiguration.class)
class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // Configure Vaadin's security using VaadinSecurityConfigurer
        http.with(VaadinSecurityConfigurer.vaadin(), configurer -> {
// tag::snippet[]
            configurer.loginView("/login");
// end::snippet[]
        });

        return http.build();
    }
    ...
}
```

Require Authentication

By default, Vaadin **grants access to all Hilla views**. Require authentication by adding the following to `src/main/frontend/views/@layout.tsx`:

frontend/views/@layout.tsx

```tsx
// tag::snippet[]
import { ViewConfig } from "@vaadin/hilla-file-router/types.js";
// end::snippet[]
...
// tag::snippet[]
export const config: ViewConfig = {
    loginRequired: true
}
// end::snippet[]

export default function MainLayout() {
    // ...
}
```

Test the Application

Restart your application to make sure all your changes have been applied. Navigate to: <http://localhost:8080>

You should now see the login screen. Login with one of the following credentials:

- **User:** user / **Password:** user

- **Admin:** admin / **Password:** admin

After logging in, you should be able to access the task list view.

Final Thoughts

You have now successfully added authentication to your Vaadin application. Next, learn how to **log out users** by reading the [Add Logout](https://vaadin.com/docs/v24/building-apps/security/add-logout.md) guide.
