Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions articles/building-apps/react/add-login.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
---
title: Hilla
page-title: How to add a Hilla login view to a Vaadin application
meta-description: Learn how to create a secure login view using Hilla and React in Vaadin. This guide covers Spring Security integration, authentication setup, and best practices.
order: 10
---


= Hilla Login
:toclevels: 2

In this guide, you'll learn how to create a login view using Hilla and React.

.Never Use Hard-Coded Credentials In Production
[WARNING]
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 https://docs.spring.io/spring-security/reference/servlet/authentication/index.html[Spring Security Reference Manual] for more details.

Check warning on line 16 in articles/building-apps/react/add-login.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Abbr] 'LDAP' has no definition. Raw Output: {"message": "[Vaadin.Abbr] 'LDAP' has no definition.", "location": {"path": "articles/building-apps/react/add-login.adoc", "range": {"start": {"line": 16, "column": 150}}}, "severity": "WARNING"}


== 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*.


=== 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 [annotationname]`@BrowserCallable` service that returns user details:

// This assumes that the concept of browser callable services has been explained earlier.

[source,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 [methodname]`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:

[source,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.


=== Setup the Authentication Context

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

.frontend/security/auth.ts
[source,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*.


=== 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
[source,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[]
);
}
...
----


== The Login View

The login view is a standard <<../../views/add-view/hilla#,Hilla view>>. The easiest way to implement one is by using the <<{articles}/components/login#,LoginForm>> component:

.frontend/views/login.tsx
[source,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 <<../../views/add-router-layout#,router layout>>.
<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.


== Server Configuration

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

.`SecurityConfig.java`
[source,java]
----
@EnableWebSecurity
@Configuration
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#,Protect Views>> guide.

80 changes: 80 additions & 0 deletions articles/building-apps/react/add-logout.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
title: Hilla
page-title: How to logout from a Vaadin Hilla application
meta-description: Learn to implement logout functionality with Hilla views using the useAuth hook. This guide covers logout handling, redirect, and best practices.

Check failure on line 4 in articles/building-apps/react/add-logout.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vale.Spelling] Did you really mean 'useAuth'? Raw Output: {"message": "[Vale.Spelling] Did you really mean 'useAuth'?", "location": {"path": "articles/building-apps/react/add-logout.adoc", "range": {"start": {"line": 4, "column": 86}}}, "severity": "ERROR"}
order: 10
---


= Hilla Logout
:toclevels: 2

In this guide, you'll learn how to implement logout functionality in a Vaadin application with Hilla views.


== Logging Out

Vaadin provides a React hook `useAuth`, which includes a `logout()` function. Calling this function *logs out the user* and *redirects* them to a preconfigured *logout success URL*.

[NOTE]
You configured the `useAuth` hook in the <<../add-login/hilla#,Add Login>> guide.

You typically call `logout()` from a *button* or *menu item* click listener. Here's how to add a logout button to a view:

[source,tsx]
----
import { Button } from '@vaadin/react-components';
import { useAuth } from "Frontend/security/auth";

export default function LogoutView() {
const { logout } = useAuth();
return (
<main>
<Button onClick={logout}>Logout</Button>
</main>
);
}
----


== Configuring the Logout Success URL

By default, users are redirected to the root URL (`/`) after logging out. To change this, *specify a custom logout success URL* in your security configuration:

.`SecurityConfig.java`
[source,java]
----
@EnableWebSecurity
@Configuration
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", "/logged-out.html"); // <1>
// end::snippet[]
});
return http.build();
}
...
}
----

<1> Sets `/logged-out.html` as the *logout success URL*.

If your application runs at `\https://example.com`, users will be redirected to `\https://example.com/logged-out.html` after logging out.

Check warning on line 67 in articles/building-apps/react/add-logout.adoc

View workflow job for this annotation

GitHub Actions / lint

[vale] reported by reviewdog 🐶 [Vaadin.Will] Avoid using 'will'. Raw Output: {"message": "[Vaadin.Will] Avoid using 'will'.", "location": {"path": "articles/building-apps/react/add-logout.adoc", "range": {"start": {"line": 67, "column": 59}}}, "severity": "WARNING"}


=== Absolute vs. Relative URLs

The logout success URL can be either absolute or relative.

* *Absolute URLs* -- Start with `https://` or `http://` (e.g., `\https://example.com/logged-out`).
* *Relative URLs* -- Start with `/` (e.g., `/logged-out.html`).

.Relative logout URLs must include the context path
[IMPORTANT]
If your application is deployed at `\https://example.com/app`, the logout URL should be `/app/logged-out.html`.

Loading
Loading