Skip to content
Merged
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
8 changes: 5 additions & 3 deletions articles/building-apps/react/security.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ The real security boundary is on the server, around the *browser-callable servic

In practice you do both: protect views so users see only what's relevant to them, and protect services so the data is actually safe.

For how Vaadin checks the access to views and services, see the <<{articles}/flow/security/react-views#,React Views & Browser-Callable Services>> reference.


== Adding a Login View

Vaadin includes a client-side security extension that integrates with Spring Security. Setting it up takes three steps: a user-information endpoint, a client-side authentication context, and wrapping the application in that context.
Vaadin includes a client-side security extension that integrates with Spring Security. Setting it up takes three steps: a user-information endpoint, a client-side authentication context, and wrapping the application in that context. For the functions and their options, see <<{articles}/flow/security/react-views/authentication#,Authentication>>.

=== Create a User-Information Endpoint

Expand Down Expand Up @@ -202,7 +204,7 @@ A React view is public unless it, or a layout around it, sets `loginRequired`. A

== Restricting Access by Role

To grant access based on roles, first expose the current user's roles to the authentication context, then declare the required roles on each view.
To grant access based on roles, first expose the current user's roles to the authentication context, then declare the required roles on each view. For where Vaadin checks the roles, see <<{articles}/flow/security/react-views/view-access-control#,View Access Control>>.

=== Expose Roles to the Context

Expand Down Expand Up @@ -295,7 +297,7 @@ A relative logout URL must include the application's context path. If the applic

== Protecting Browser-Callable Services

This is the real security boundary. All browser-callable services are *inaccessible by default* and require an explicit annotation to grant access.
This is the real security boundary. All browser-callable services are *inaccessible by default* and require an explicit annotation to grant access. For the precedence of the annotations and the responses to denied calls, see <<{articles}/flow/security/react-views/service-access-control#,Service Access Control>>.

.Services Don't Use Spring Method Security
[CAUTION]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,4 @@ export async function stubMiddleware(context: MiddlewareContext, next: Middlewar
}
----

The `@vaadin/hilla-frontend` package also has an `InvalidSessionMiddleware`, which handles calls that fail because the session of the user has expired. See <<{articles}/hilla/guides/security/session-expiration#,Handling Session Expiration>>.
The `@vaadin/hilla-frontend` package also has an `InvalidSessionMiddleware`, which handles calls that fail because the session of the user has expired. See <<{articles}/flow/security/react-views/authentication#expired-sessions,Expired Sessions>>.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Annotate the service class with [annotationname]`@BrowserCallable`. The annotati
include::{root}/src/main/java/com/vaadin/demo/fusion/accessingbackend/CounterService.java[tags=snippet]
----

Access to services is denied by default. Give the class or each method an access annotation, such as [annotationname]`@AnonymousAllowed`, [annotationname]`@PermitAll`, or [annotationname]`@RolesAllowed`. See <<{articles}/building-apps/react/security#,Secure React Views>>.
Access to services is denied by default. Give the class or each method an access annotation, such as [annotationname]`@AnonymousAllowed`, [annotationname]`@PermitAll`, or [annotationname]`@RolesAllowed`. See <<{articles}/flow/security/react-views/service-access-control#,Service Access Control>>.

The [annotationname]`@Endpoint` annotation is an alias for [annotationname]`@BrowserCallable`. Its optional `value` sets the name of the service, which is otherwise the simple name of the class. The generated module and the requests use that name.

Expand Down
4 changes: 2 additions & 2 deletions articles/flow/react-views/routing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ If `true`, the browser loads the code of the view only when the view is first sh
`detail`::
Any further data about the view, for example for rendering the menu. Its type is the type parameter of `ViewConfig`.

The `loginRequired` and `rolesAllowed` options control navigation in the browser, and which views the menu shows. They don't protect any data. Protect the browser-callable services that the view calls on the server. See <<{articles}/building-apps/react/security#,Secure React Views>>.
The `loginRequired` and `rolesAllowed` options control which views the user can open, and which views the menu shows. They don't protect any data. Protect the browser-callable services that the view calls on the server. See <<{articles}/flow/security/react-views/view-access-control#,View Access Control>>.


=== Reading the Configuration
Expand Down Expand Up @@ -267,7 +267,7 @@ Adds a route for the paths that no other route matches, in every branch of the r
Wraps the views that have the `flowLayout` option in the given component, such as `Flow`. Only the first call has an effect.

`protect(redirectPath)`::
Checks the `loginRequired` and `rolesAllowed` options in the browser. When the user can't open a view, the router goes to `redirectPath` instead, or to `/login` without an argument. It gets the user from the authentication context. See <<{articles}/building-apps/react/security#,Secure React Views>>.
Checks the `loginRequired` and `rolesAllowed` options in the browser. When the user can't open a view, the router goes to `redirectPath` instead, or to `/login` without an argument. It gets the user from the authentication context. See <<{articles}/flow/security/react-views/view-access-control#browser,View Access Control>>.

`build(options)`::
Returns an object with the `router` and the `routes`. The optional `options` are passed to the `createBrowserRouter()` function of React Router.
Expand Down
6 changes: 5 additions & 1 deletion articles/flow/security/protections/vulnerabilities.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,14 @@ Upload and heartbeat requests aren't UIDL messages, so they don't carry `Vaadin-

Requests that React views send to browser-callable services aren't UIDL requests, so they're protected by Spring Security's CSRF protection instead. Vaadin adds Spring Security's CSRF token to the page, and the client sends it in the HTTP header that Spring Security expects, by default `X-CSRF-TOKEN`. This token is a different one from the `Vaadin-Security-Key`.

An application without Spring Security's CSRF protection uses the link:https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#alternative-using-a-double-submit-cookie-pattern[Double-Submit Cookie Pattern] instead. When the browser loads the page, the server sets a random token in the `csrfToken` cookie, which the browser code can read. The client sends the token of the cookie in the `X-CSRF-Token` header of every call, and the server checks that the header and the cookie match. A site on another domain can't read the cookie, so it can't send the header.

The server rejects a call with a missing or wrong token with `401 Unauthorized`.


=== Disabling the Check

The CSRF token check for UIDL requests can be disabled with the `disable-xsrf-protection` configuration parameter. See <<{articles}/flow/configuration/properties#,Configuration Properties>>. You might do this, for example, to enable repeatable load test scripts that use Gatling or similar tools. This is strongly discouraged, though, when running in production.
The CSRF token check for UIDL requests can be disabled with the `disable-xsrf-protection` configuration parameter. The parameter also disables the check of the `csrfToken` cookie for browser-callable services, but not Spring Security's CSRF protection. See <<{articles}/flow/configuration/properties#,Configuration Properties>>. You might do this, for example, to enable repeatable load test scripts that use Gatling or similar tools. This is strongly discouraged, though, when running in production.


[[xss]]
Expand Down
150 changes: 150 additions & 0 deletions articles/flow/security/react-views/authentication.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
title: Authentication
page-title: Authentication and the current user in React views | Vaadin
description: The login view of React views, the user information that the browser gets, the authentication context, and expired sessions.
meta-description: Reference for authentication in Vaadin React views - the login view, the user information service, the authentication context, logging in and out, and expiry.
order: 20
---


= Authentication
:toclevels: 2

Spring Security authenticates the users of React views on the server, with the same configuration as for Flow views. The `@vaadin/hilla-react-auth` package tells the browser which user is logged in, and logs users in and out.

section_outline::[]


[[login-view]]
== Login View

The [methodname]`loginView()` method of [classname]`VaadinSecurityConfigurer` takes the path of a React login view:

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

Spring Security sends the users who aren't logged in to that path, and expects the login requests at the same path. After logout, the browser goes to `/`, or to the URL that the optional second argument gives. See <<{articles}/flow/security/spring/security-configuration#login-view-configuration,Login View Configuration>>.


[[user-information]]
== User Information

The browser gets the logged-in user from a browser-callable service method of the application. The method returns information about the user, or `null` when nobody is logged in. Vaadin has no type for the information, so the application defines one with the properties that its views need:

[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/security/authentication/UserInfoService.java[tags=snippet,indent=0]
----

[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/security/authentication/UserInfo.java[tags=snippet,indent=0]
----

The `roles` property contains the granted authorities as they are, such as `ROLE_ADMIN`. See <<view-access-control#role-names,Role Names>> for why.

A method that allows only logged-in users works, too: the browser counts its `401 Unauthorized` response as no user. That response also starts the <<expired-sessions,`InvalidSessionMiddleware`>>, though, for every user who isn't logged in.


[[configure-auth]]
== Authentication Context

The [methodname]`configureAuth()` function of `@vaadin/hilla-react-auth` takes the generated function of the user information method:

[source,typescript]
.[filename]`auth.ts`
----
include::{root}/frontend/demo/auth/auth.ts[]
----

It returns two items:

`AuthProvider`::
A React component that provides the authentication context. It must be around every component that uses the context, so render it around the `RouterProvider` of the application, in an [filename]`index.tsx` file of your own. When it's first rendered, it calls the user information method.

`useAuth`::
The hook that returns the authentication context. Its type comes from the return type of the user information method.

The optional second argument of [methodname]`configureAuth()` is an object with a `getRoles` function. The function gets the user information, and returns the roles of the user as an array of strings. The access checks in the browser compare these roles with the `rolesAllowed` option of the views. By default, the roles are the `roles` property of the user information, when it's an array.


[[use-auth]]
== Authentication State

The object that [methodname]`useAuth()` returns has these properties:

`state`::
The authentication state. Its `user` property is the user information, or `undefined` when nobody is logged in. Until the user information has loaded, its `initializing` or `loading` property is `true`.

`login(username, password, options)`::
Logs the user in, as described in <<login,Logging In>>, and then loads the user information again.

`logout()`::
Logs the user out, as described in <<logout,Logging Out>>.

`hasAccess(options)`::
Returns whether the user meets the `loginRequired` and `rolesAllowed` options of the argument. See <<view-access-control#has-access,Checking Access in a View>>.


[[login]]
=== Logging In

The [methodname]`login()` function sends the user name and the password to the login URL. When the login succeeds, it reloads the page at the URL that the user tried to open before Spring Security sent them to the login view. Without such a URL, it uses the default success URL of Spring Security, which is `/` by default.

The function returns a promise of a [classname]`LoginResult`. When the login fails, the `error` property of the result is `true`, and the `errorTitle` and `errorMessage` properties describe the error for the user:

[source,tsx]
.[filename]`login.tsx`
----
include::{root}/frontend/demo/auth/LoginView.tsx[]
----

The third argument of [methodname]`login()` is an optional object with these properties:

`loginProcessingUrl`::
The URL of the login request. Defaults to `login`, relative to the base URL of the application.

`onSuccess`::
A function that's called after a successful login, before the navigation.

`navigate`::
A function that's called with the path to go to after a successful login. By default, the page reloads at that path.


[[logout]]
=== Logging Out

The [methodname]`logout()` function submits a logout request to `logout`, relative to the base URL of the application. The browser then goes to the URL that the server responds with, which is the logout success URL.

Data that the application keeps in the browser stays there after the logout. See <<index#cached-data,Data in the Browser>>.


[[expired-sessions]]
== Expired Sessions

When the session of the user expires, the calls to services fail with `401 Unauthorized`. With the default configuration of Spring Security, this happens also to the calls to methods that allow anonymous access. The token that protects the page from cross-site request forgery belonged to the expired session, so it's no longer valid.

The [classname]`InvalidSessionMiddleware` of `@vaadin/hilla-frontend` handles these calls. Add it to the `ConnectClient` of the application, as described in <<{articles}/flow/react-views/browser-callable-services/client#middleware,Middleware>>:

[source,typescript]
.[filename]`connect-client.ts`
----
include::{root}/frontend/demo/auth/session-expiration/connect-client.ts[tags=snippet,indent=0]
----

For each call that fails with `401 Unauthorized`, the middleware calls the function that its constructor got, and waits for the [classname]`LoginResult` that the function returns. When the result has a `token`, the middleware sends the call again with that token. Otherwise, the call fails with the original response. The [methodname]`login()` function doesn't return a token, so the failed calls aren't sent again after it.

This example asks the user to log in again, and then reloads the page:

[source,tsx]
.[filename]`SessionExpiredLoginOverlay.tsx`
----
include::{root}/frontend/demo/auth/session-expiration/SessionExpiredLoginOverlay.tsx[tags=snippet,indent=0]
----

Render `SessionExpiredLoginOverlay` once in the main layout, so that it's present in every view. Nothing renders it automatically. If no component renders it, the overlay never opens, and the failed calls wait without any feedback.

The `navigate` option reloads the page at its current URL. Without it, [methodname]`login()` goes to the default success URL, because Spring Security doesn't save the URL of a service call as a URL to return to.
58 changes: 58 additions & 0 deletions articles/flow/security/react-views/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: React Views & Browser-Callable Services
page-title: Securing React views and browser-callable services | Vaadin
description: How Vaadin protects React views and the browser-callable services that they call, and how their defaults differ from Flow views.
meta-description: Reference for the security of Vaadin React views and browser-callable services - the security model, default access rules, and Spring Security settings.
order: 25
---


= React Views & Browser-Callable Services

A React view runs in the browser. The user can read its code, and change what it does, with the developer tools of the browser. The data of the view comes from the browser-callable services that it calls, and the server checks every call to them. These checks are what protect the data.

Vaadin also checks access to React views, but only to decide what the user sees: which views the router opens, and which views the menu shows. These checks don't protect anything that the browser has already loaded.


[[defaults]]
== Default Access

Browser-callable services, React views, and Flow views have different defaults:

[cols="1,2"]
|===
| |Without an access rule

|Browser-callable service
|Denied to everyone. See <<service-access-control#,Service Access Control>>.

|React view
|Open to everyone. See <<view-access-control#,View Access Control>>.

|Flow view
|Denied to everyone. See <<{articles}/flow/security/access-control/annotations#,Annotation-Based Access Control>>.
|===

A React view that you add to an application whose Flow views all require login is therefore open to everyone, until it or a layout around it requires login.


[[spring-security]]
== Spring Security Configuration

React views and Flow views share the Spring Security configuration that [classname]`VaadinSecurityConfigurer` applies, as described in <<{articles}/flow/security/spring/security-configuration#,Security Configuration>>. For React views and browser-callable services, the configurer does more:

- It permits the requests for React views that don't require login, and for service methods that allow anonymous access.
- It requires a logged-in user for the other calls to services, and responds to them with `401 Unauthorized` when nobody is logged in.
- It checks the user when the browser loads the page of a React view that requires login, as described in <<view-access-control#page-loads,Page Loads>>.
- It configures the protection of service calls against cross-site request forgery, as described in <<{articles}/flow/security/protections/vulnerabilities#react-views,Cross-Site Request Forgery>>.


[[cached-data]]
== Data in the Browser

A React view can keep the data that it gets from services in the browser, for example in local storage, so that the view also works offline. Whoever uses the browser later can read that data. Decide whether the data may stay in the browser, and remove the data that may not when the user logs out.


== Topics

section_outline::[]
Loading
Loading