Skip to content

docs: move React view security to the Flow security reference - #6622

Merged
peholmst merged 4 commits into
mainfrom
claude/react-views-security
Nov 11, 2026
Merged

peholmst merged 4 commits into
mainfrom
claude/react-views-security

Conversation

@peholmst

@peholmst peholmst commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Review risk: two-way door. Docs and examples only: five new reference pages, ten removed pages with redirects, merges into three Flow security pages, and link updates. Reverting brings the old pages back.

Summary

Moves the security reference for React views from the Hilla section to the Flow security reference. The new pages are under articles/flow/security/react-views/, including stateless authentication. This is the sixth step of dissolving the Hilla section, after #6611, #6616, #6618, #6620 and #6621. Full-stack signals follow in the next pull request.

The new pages are reference only: what the access rules are, how Vaadin checks them, and what the APIs do. They link to other reference pages. The step-by-step setup stays in Building Apps > Secure React Views, which now links to the reference instead of the reference linking to it.

Every statement is checked against main of vaadin/flow and vaadin/hilla. The behavior is also checked in a scratch 25.4.0-alpha5 application with Spring Security, using curl and a browser. This pull request targets main only, so it gets no target/v25.3 label.

What changed

  • New pages in flow/security/react-views/:
    • index.adoc: the security model, the default access of services, React views and Flow views, what VaadinSecurityConfigurer does for React views and services, and data kept in the browser.
    • service-access-control.adoc: the access annotations and their precedence, role checks, 401 versus 403, the current user, auditing denied calls, and CSRF.
    • authentication.adoc: the React login view, the user information method, configureAuth(), useAuth(), login() and logout(), and expired sessions.
    • view-access-control.adoc: the three places where loginRequired and rolesAllowed are checked (the router, the menu, page loads), role names, hasAccess(), protectRoutes(), and Flow and React views together.
  • Moved page: flow/security/react-views/stateless-authentication.adoc, rewritten as a reference for VaadinStatelessSecurityConfigurer, its cookies, and the contents of the token. It states that stateless authentication is intended for React views only, and that its behavior with Flow views is undefined.
  • Merges:
    • spring/security-configuration.adoc: the static getDefaultHttpSecurityPermitMatcher() and getDefaultWebSecurityIgnoreMatcher() matchers.
    • protections/vulnerabilities.adoc: the double-submit cookie check that services use without Spring Security's CSRF protection, and its relation to disable-xsrf-protection.
  • Examples: the user information service now allows anonymous calls and returns null when nobody is logged in, using AuthenticationContext. Its record has a roles property, which configureAuth() reads by default. A new ProjectService shows the access annotations. The login view, the session-expiration overlay and the connect client are rewritten. The examples that only the removed pages used are deleted.
  • Corrections and additions compared with the old pages, each confirmed in the scratch application:
    • A service without annotations is denied to everyone. Controlling Service Access and Best Practices also said that it needs only a logged-in user.
    • The session-expiration example said that the failed call continues after the login. It doesn't: login() returns no token, so InvalidSessionMiddleware doesn't send the call again, and login() reloads the page at /. The new example reloads the current page. A user information method that allows only logged-in users also opened the overlay for every visitor who isn't logged in.
    • After a session expires, calls to anonymous methods fail with 401 as well, because the CSRF token belonged to the old session.
    • When Spring Security has already rejected an anonymous call, no EndpointInvocation event is published. Spring Security publishes its own event, with the request as its object. The Auditing section said that the anonymous user arrives in the EndpointInvocation event.
    • The old role example used rolesAllowed: ['ROLE_USER'] with roles that never contained that name.
    • The old pages showed the removed useRouteMetadata, the deprecated requiresLogin, and VaadinRequest.getCurrent().getUserPrincipal().
  • Removed pages, each with a redirect: the ten hilla/guides/security pages. Offline authentication, which was built on Lit-era APIs, and the best-practices, introduction and vulnerabilities pages go to the matching Flow pages. The nine Lit redirects that led to these pages now point straight at the new pages.
  • Links: Routing, Browser-Callable Services, Client and Security Configuration now link to the new reference pages instead of the Building Apps guide. Secure React Views links to the reference. The full-stack signals page linked to the removed introduction.

Test summary

  • npm run check-redirects passes.
  • mvn spotless:apply and mvn compile pass. npx eslint frontend/demo/auth passes.
  • The frontend examples type-check in the scratch 25.4.0-alpha5 application against the generated services. The login view and the session-expiration overlay ran there.
  • Checked with curl and in the browser in the scratch application:
    • service calls as an anonymous user, a user, and an administrator;
    • page loads, client-side navigation and menus;
    • expired sessions;
    • audit events;
    • stateless authentication across a server restart;
    • the double-submit cookie without Spring Security.
  • Every cross reference in the new and changed lines resolves to an existing page and anchor.
  • Vale: no errors or warnings on the added and changed lines.
  • Asciidoctor renders the new and changed pages, with all includes and tags resolved.
  • I didn't run a full dspublisher build locally.

Stateless authentication is documented as intended for applications whose views are all React views, with undefined behavior for Flow views. Flow views keep their UI state in the HTTP session in any case.

Behaviors that came up during testing, which the pages describe as they are:

Not changed here: the role example in Building Apps > Secure React Views hits the 403 above on reload, and its getRoles code doesn't type-check. That guide gets a separate pull request.

🤖 Generated with Claude Code

Move the security reference for React views and browser-callable
services from the Hilla section to articles/flow/security/react-views/,
and stateless authentication to articles/flow/security/spring/. The new
pages are reference only and link to other reference pages; the setup
steps stay in Building Apps > Secure React Views, which now links to
them.

Merge the static request matchers into Security Configuration, and the
double-submit CSRF cookie check into Common Vulnerabilities. Remove the
ten Hilla security pages with redirects, and point the Lit redirects
straight at the new pages.

Correct what the old pages got wrong for 25.4, verified in a secured
25.4.0-alpha5 application: services without annotations are denied to
everyone, the session-expiration example didn't retry the failed call,
anonymous denied calls aren't published as EndpointInvocation events,
and role names are compared differently on page loads than in the
browser and the menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changes — left 1 comment.

Finding
🧩 After a second failed login, the login view's form stays disabled and shows no error.

Comment thread frontend/demo/auth/LoginView.tsx Resolved
LoginForm clears its error and disables itself on submit. Without
onErrorChanged, a second failed login leaves the error state true in
React, so nothing re-renders and the form stays disabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

This PR has been deployed for preview.

URL: https://docs-preview-pr-6622.fly.dev

Changed pages

Added content is highlighted in green; removed content is marked in red on each page.

Other changed files

  • articles/hilla/guides/security/authentication.adoc (deleted)
  • articles/hilla/guides/security/best-practices.adoc (deleted)
  • articles/hilla/guides/security/configuring.adoc (deleted)
  • articles/hilla/guides/security/index.adoc (deleted)
  • articles/hilla/guides/security/intro.adoc (deleted)
  • articles/hilla/guides/security/offline-auth.adoc (deleted)
  • articles/hilla/guides/security/session-expiration.adoc (deleted)
  • articles/hilla/guides/security/spring-login.adoc (deleted)
  • articles/hilla/guides/security/spring-stateless.adoc (deleted)
  • articles/hilla/guides/security/vulnerabilities.adoc (deleted)
  • articles/hilla/_shared/guides/security/_index.adoc (deleted)
  • articles/hilla/_shared/guides/security/_session-expiration.adoc (deleted)
  • articles/hilla/_shared/guides/security/_spring-login.adoc (deleted)
  • frontend/demo/auth/AboutView.tsx (deleted)
  • frontend/demo/auth/App.tsx (deleted)
  • frontend/demo/auth/MainLayout.tsx (deleted)
  • frontend/demo/auth/authRoles.ts (deleted)
  • frontend/demo/auth/index.tsx (deleted)
  • frontend/demo/auth/routes.tsx (deleted)
  • frontend/demo/auth/routing.ts (deleted)
  • frontend/demo/fusion/authentication/auth.ts (deleted)
  • src/main/java/com/vaadin/demo/fusion/security/access/package-info.java (added)
  • src/main/java/com/vaadin/demo/fusion/security/authentication/SecurityConfigDemo.java (deleted)
  • src/main/java/com/vaadin/demo/fusion/security/authentication/package-info.java (added)

Built from 6ccf233

State that stateless authentication is intended for applications whose
views are all React views, and that its behavior with Flow views is
undefined. Flow views keep their UI state in the HTTP session in any
case. Move the page into the React views security section, and point
the old Hilla URLs at its new location.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@vaadin-review-bot vaadin-review-bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changes — left 1 comment.

Finding
💬 The page no longer tells the reader to render the session-expiration overlay. Its wording suggests that the main layout renders it automatically.

The page described the main layout as rendering the overlay, which
reads as if that happens automatically. Without a component rendering
it, the overlay never opens and the failed calls wait forever.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@peholmst
peholmst merged commit 06678c0 into main Nov 11, 2026
10 of 11 checks passed
@peholmst
peholmst deleted the claude/react-views-security branch November 11, 2026 09:30
peholmst added a commit that referenced this pull request Nov 11, 2026
Fixes the role-based access example in **Secure React Views**
(`articles/building-apps/react/security.adoc`), a follow-up to
#6622.

## Problems

1. **Reloading a role-protected view gave 403.** The guide stripped the
`ROLE_` prefix in `getRoles` and wrote `rolesAllowed: [ "ADMIN" ]`.
Client-side navigation and the menu worked. But when the page of a view
with `loginRequired: true` loads, Spring Security compares
`rolesAllowed` with the granted authorities exactly, so an administrator
who reloaded the view, or came back to it after logging in, got `403
Forbidden`. The underlying mismatch is reported as vaadin/flow#26962.
Related but separate: vaadin/flow#22280.
2. **The `getRoles` example didn't type-check.** `@NonNull
Collection<String> authorities`, in a package without `@NullMarked`,
generates `authorities: Array<string | undefined>`, so
`s.startsWith(...)` fails with `TS18048: 's' is possibly 'undefined'`.
In dev mode the type-check overlay also covers the login form.

## Changes

- `UserInfo` is now `record UserInfo(String name, List<String> roles)`,
plus a `package-info.java` step with `@NullMarked` and a link to Type
Nullability. The redundant `@NonNull` on `getUserInfo()` is gone.
- **Expose Roles to the Context** no longer shows `getRoles` code:
`configureAuth()` reads the `roles` property by default. The section
links to the Authentication Context reference for user information that
keeps its roles elsewhere.
- `rolesAllowed` and `hasAccess()` use `ROLE_ADMIN`. The callout says
what goes wrong without the prefix and links to **Role Names** in View
Access Control, which recommends the same approach.

The guide stays a how-to: the details are in the Flow reference pages it
links to.

## Testing

A scratch app on Vaadin 25.4.0-alpha5 and Spring Boot 4.1.2 (Spring
Security 7), built from the guide's snippets as written. In-memory
users: `admin` (ADMIN, USER) and `user` (USER).

| Check | Before | After |
|---|---|---|
| `tsc --noEmit` | 2 × TS18048 in `auth.ts` | Passes; generated `roles:
Array<string>`, `Promise<UserInfo>` |
| admin: log in from `/admin` | Redirected to `/admin`, then 403 | Back
on `/admin?continue`, view renders |
| admin: menu | Home, Account, Admin | Home, Account, Admin |
| admin: client navigation to `/admin` | Renders | Renders |
| admin: reload `/admin` | **403** | 200, renders |
| admin: `hasAccess()` in account view | Admin-only text shown |
Admin-only text shown |
| user: menu | — | Home, Account |
| user: client navigation to `/admin` | — | Sent to `/login` |
| user: reload `/admin` | — | 403 |
| user: `hasAccess()` in account view | — | Admin-only text hidden |

Vale reports nothing on the changed lines.

## Not included

No `target/v25.3` label. v25.3 has the same example, but the reference
pages this change links to (`flow/security/react-views/...`, Type
Nullability) don't exist there, since #6622 wasn't picked to v25.3. A
v25.3 fix needs its own links, and verification on 25.3.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
peholmst added a commit that referenced this pull request Nov 11, 2026
**Review risk:** two-way door. Docs and examples only: one new reference
page, one removed page with a redirect, a new section on Shared Signals,
and link updates. Reverting brings the old page back.

## Summary
Moves full-stack signals from the Hilla guides to the Flow UI State
section, as the new page Shared Signals in React Views
(`flow/ui-state/full-stack-signals`). This is the seventh step of
dissolving the Hilla section, after #6611, #6616, #6618, #6620, #6621
and #6622.

The page is a reference: which shared signal types a service can return,
the client API of the signals, how they connect and synchronize, and how
the access annotations apply. It's rewritten for 25.4, because most of
the old page described an API that no longer exists. Every statement is
checked against `main` of vaadin/hilla and vaadin/flow, and the behavior
is checked in a scratch 25.4.0-alpha5 application with two browser tabs.
This pull request targets `main` only, so it gets no `target/v25.3`
label.

## What changed
- **New page** `flow/ui-state/full-stack-signals.adoc`, marked as a
preview feature behind the `fullstackSignals` feature flag:
- the supported types (`SharedValueSignal`, `SharedNumberSignal`,
`SharedListSignal`) and their TypeScript counterparts;
- returning one instance from the service, and service methods with
parameters;
- creating, connecting and reading the signals in a view, and default
values;
- the members of `ValueSignal`, `NumberSignal` and `ListSignal`, and
operation results;
  - changes made on the server;
  - access control.
- **Shared Signals:** a new Validating Changes section for
`withValidator()` and `CommandValidator`, which the Flow page didn't
cover. The UI State index links to the new page.
- **Feature Flags:** `fullstackSignals` moves to the list of
experimental flags, and the Hilla Feature Flags heading goes away.
- **Examples:** a compiled `MeetingService` and a view that uses a
value, number and list signal, replacing the inline examples.
- **Removed page, with a redirect:** `hilla/guides/full-stack-signals` →
`flow/ui-state/full-stack-signals`.
- **Links:** Type Conversion now links to the new page.
- **Corrections** compared with the old page, each checked in the
scratch application:
- `ValueSignal` has no `replace()` or `update()` anymore
(vaadin/hilla#3500), and `ListSignal` has `insertFirst()` as well.
- The operation-validator API (`withOperationValidator`,
`ValidationResult`, `IncrementOperation`, …) no longer exists. Flow's
shared signals use `withValidator(CommandValidator)`, which is now on
the Shared Signals page.
- `new SharedValueSignal<>(false, Boolean.class)` doesn't compile. The
constructors take an initial value, a type, or both as a
`TypeReference`.
- The list example used the index as the React `key`. Each item signal
has a stable `id`.
- `SharedMapSignal` and `SharedNodeSignal` aren't supported. The
generator produces a plain method for them.

## Test summary
- `npm run check-redirects` passes.
- `mvn spotless:apply` and `mvn compile` pass. `npx eslint
frontend/demo/fusion/signals` passes.
- The example service and view type-check in the scratch 25.4.0-alpha5
application. Two tabs share the topic, the votes, and the agenda items:
adding, checking and removing items all sync.
- Checked with the browser and the generated client:
  - the generated types and default values;
  - write before read;
  - access denied on connecting and on changing;
  - `result` and `error` of a failed change;
- a server-side change, which the connected browsers don't get and a new
connection does (vaadin/hilla#5722);
  - assigning the value of a list signal.
- `withValidator()`, chained validators, child signals, transactions and
`asReadonly()` checked with a small program against the 25.4.0-alpha5
classpath.
- Every cross reference in the new and changed lines resolves.
- Vale: no errors or warnings on the added and changed lines with the
pull request configuration.
- Asciidoctor renders the new and changed pages, with all includes and
tags resolved.
- I didn't run a full dspublisher build locally.

Behaviors that came up during testing, which the page describes as they
are:
- Changes that server-side code makes to a shared signal don't reach the
connected browsers, only new connections (vaadin/hilla#5722).
- When a change fails, its `result` promise doesn't settle; the signal's
`error` holds the error. Related to vaadin/hilla#3967.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
peholmst added a commit that referenced this pull request Nov 11, 2026
**Review risk:** two-way door. Docs and examples only: one new page, two
rewritten or extended pages, five removed pages with redirects, and link
and guideline updates. Reverting brings the old pages back.

## Summary
Moves React i18n, the connection indicator and React styling from the
Hilla section to the Flow Reference and the Styling section. This is the
eighth step of dissolving the Hilla section, after #6611, #6616, #6618,
#6620, #6621, #6622 and #6624.

Every statement is checked against `main` of vaadin/hilla and
vaadin/flow, and the behavior is checked in a scratch application on
25.4.0-alpha5 with Flow 25.4.0-alpha6, the first Flow release with
vaadin/flow#26714. This pull request targets `main` only, so it gets no
`target/v25.3` label.

## What changed
- **New page** `flow/i18n/react-views.adoc`, Localization in React
Views, rewritten from the Hilla i18n guide with compiled examples:
- loading translations with `i18n.configure()`, and how the initial
language is chosen;
- `translate()` and the `key` tag, the missing-translation marker, and
ICU parameters compared with Flow's `MessageFormat`;
- translating view titles and menu items with `translateDynamic()`,
including the `@Menu` titles of Flow views;
- changing the language, the file fallback, and that the React language
is separate from the Flow `UI` locale;
- development and production loading, and what a custom `I18NProvider`
must implement.
- **Loading and Connection Indicator**
(`flow/advanced/loading-indicator.adoc`) now also covers what the Hilla
connection indicator page covered:
- it covers Flow and React views, the loading bar stages, and the
connection status messages;
- configuration from Java, including `ReconnectDialogConfiguration`,
which no page documented, with a compiled example;
- configuration from TypeScript, with the `jar-resources` import that
replaces `@vaadin/common-frontend` after vaadin/flow#26714;
  - styling, with the element structure and states.
- **Stylesheets:** a new Stylesheets in React Views section: global
`@StyleSheet` styles, importing CSS files and CSS modules from a view,
and the type declarations these imports need.
- **Localization and Custom I18N Provider** link to the new page.
- **Removed pages, with redirects:**
  - `hilla/guides/i18n` → `flow/i18n/react-views`
- `hilla/reference/connection-indicator` →
`flow/advanced/loading-indicator` (the Lit redirect points there too)
  - `hilla/guides/styling` → `styling/stylesheets#react-views`
- `hilla/reference`, now empty → `flow/react-views` (the Lit redirect
points there too). This was planned for the last step.
  - The last partial in `articles/hilla/_shared` is removed.
- **Links:** the Routing page links to the new i18n page.
- **Guidelines:** the Hilla paragraph no longer mentions the `_shared`
partials, and the feature flag guideline no longer names the Hilla
Feature Flags section that #6624 removed.
- **Corrections** compared with the old pages, each checked in the
scratch application:
  - A missing translation shows as `!de: key`, not as the key.
- The old guide wrapped `i18n.configure()` in an effect. It returns a
`Promise`, and the page awaits it once in the layout.
  - The default first delay of the loading bar is 450 ms, not 300 ms.
- The old CSS examples didn't show anything with the default theme
turned off: the indicator's functional style, which is always present,
comes after the application's stylesheets and sets `opacity: 0` and
`pointer-events: none`. The examples now use
`vaadin-connection-indicator .v-loading-indicator` and set `opacity`.
- The `@vaadin/common-frontend` import no longer works after
vaadin/flow#26714.

## Test summary
- `npm run check-redirects` passes.
- `mvn spotless:apply` and `mvn compile` pass. `npx eslint
frontend/demo/fusion/i18n` passes.
- The two TypeScript examples type-check and run in the scratch
application, in English and German.
- Checked with the browser:
- translations, the ICU plural form, `{0}` placeholders, the missing-key
marker, on-demand keys and their warning;
- the cookie, the fallback from `de-DE` to `de` and the root file, and
the separate Flow locale;
- a custom provider without `getAllTranslations()` in development mode
(no translations; `initialized` stays `false`);
- the loading bar stages during a slow service call, the offline message
after a failed call and after the browser's `offline` event, and the
online message afterwards;
  - the reconnecting message of a Flow view with the server stopped;
- the TypeScript configuration, the Java configuration overriding only
the properties it sets, and the corrected curtain CSS;
- global styles, a plain CSS import that stays loaded after navigating
away, and a CSS module.
- Every cross reference in the new and changed lines resolves.
- Vale: no errors or warnings on the added and changed lines with the
pull request configuration.
- Asciidoctor renders the changed pages, with all includes and tags
resolved.
- I didn't run a full dspublisher build locally.

The TypeScript configuration snippet for the indicator is inline: the
docs build is on 25.4.0-alpha5, whose `Flow.js` still uses
`@vaadin/common-frontend`, so a compiled example importing the new
module wouldn't build yet.

Product bugs found during testing, reported separately:
- vaadin/flow#26970: `types.d.ts` doesn't declare CSS imports, so with
TypeScript 6 the imports in React views fail the type check. The
Stylesheets section documents the declarations to add.
- vaadin/hilla#7050: after `setLanguage()`, a key that
`translateDynamic()` loaded on demand stays empty. The page doesn't
describe this behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants