Protect Views
In this guide, you’ll learn how to control access to specific Hilla views both declaratively and programmatically. A hands-on mini-tutorial at the end will help you apply these concepts in a real Vaadin application.
Client Security
In a Hilla view, the code is running in the browser and could easily be manipulated using the browser’s development tools. Client-side security enhances user experience by hiding inaccessible features and views, but it does not provide real security.
Warning
| When working with Hilla views, the real security layer protects the browser callable services. For more information about this, see the Protect Services guide. |
Requiring Authentication
By default, all Hilla views are accessible without authentication. To require login, add loginRequired: true
to the view configuration. This ensures that users must authenticate before accessing the view:
export const config: ViewConfig = {
title: 'Protected View',
menu: {
icon: 'vaadin:safe-lock',
order: 1,
title: 'Protected View',
},
loginRequired: true
}
export default function ProtectedView() {
return (
<main>
This view requires login
</main>
);
}
Important
|
The ViewConfig object is parsed on the server as JSON. Because of this, you cannot use constants or TypeScript code in it.
|
If you want all views to require login, set the loginRequired
attribute on the main layout:
export const config: ViewConfig = {
loginRequired: true
}
export default function MainLayout() {
return (
<AppLayout primarySection="drawer">
<Header/>
<Scroller slot="drawer">
<MainMenu/>
</Scroller>
<UserMenu/>
<Suspense fallback={<ProgressBar indeterminate={true} className="m-0"/>}>
<Outlet/>
</Suspense>
</AppLayout>
);
}
Now all views rendered inside the layout require authentication.
Role-Based Access
Vaadin supports granting access to views based on the roles of the user. To do this, you have to first make the roles of the current user available to the clients-side authentication context. Next, you have to declare which role or roles are required to access a certain view or router layout.
Adding Roles to Authentication Context
When you enabled authentication, you created an auth.ts
file that setup the client authentication context. To use role-based security, you have to change the setup with information about the current user’s roles. Pass a second argument to the configureAuth
helper function:
import { configureAuth } from '@vaadin/hilla-react-auth';
import { UserInfoService } from "Frontend/generated/endpoints";
const auth = configureAuth(UserInfoService.getUserInfo, {
getRoles: (user) => { 1
return user.authorities
.filter(s => s.startsWith("ROLE_")) 2
.map(s => s.substring(5)); 3
}
})
export const useAuth = auth.useAuth
export const AuthProvider = auth.AuthProvider
-
The
user
object is of the type returned byUserInfoService.getUserInfo()
. -
Includes only role authorities. In Spring Security, roles start with the
ROLE_
prefix by default. -
Strips the
ROLE_
prefix to get only the name of the role.
Declaring Required Roles
To make a view or layout available only to users with a specific role or roles, declare the roles in the configuration object:
export const config: ViewConfig = {
loginRequired: true,
rolesAllowed: [ "ADMIN" ], 1
...
}
export default function AdminView() {
...
}
-
The
rolesAllowed
attribute is an array of strings.
Checking Access Programmatically
Sometimes, you may want to control which actions a user can perform inside a view. Instead of blocking access entirely, you can conditionally render elements based on the user’s role. For instance, one role may have full read-write access whereas another role has only read-only access. To check the roles of the current user, use the useAuth
hook:
import { useAuth } from "Frontend/security/auth";
export default function MyView() {
const auth = useAuth();
const isAdmin = auth.hasAccess({
rolesAllowed: [ "ADMIN" ] 1
});
return (
<main>
{isAdmin && <p>Only admins see this</p>}
<p>Everyone can see this</p>
</main>
);
}
-
The
rolesAllowed
attribute is an array of strings.
Try It
In this mini-tutorial, you’ll learn how to control access to Hilla views both declaratively and programmatically. It uses the project from the Add Logout guide. If you haven’t completed that tutorial yet, do it now before proceeding.
Add Roles to Authentication Context
Open src/main/frontend/security/auth.ts
and make the following changes:
import { configureAuth } from '@vaadin/hilla-react-auth';
import { UserInfoService } from "Frontend/generated/endpoints";
const auth = configureAuth(UserInfoService.getUserInfo, {
getRoles: (user) => {
return user.authorities
.filter(s => s.startsWith("ROLE_"))
.map(s => s.substring(5));
}
})
export const useAuth = auth.useAuth
export const AuthProvider = auth.AuthProvider
Create Admin View
Create a new file src/main/frontend/views/admin.tsx
:
import { ViewConfig } from "@vaadin/hilla-file-router/types.js";
export const config: ViewConfig = {
title: "Task Admin",
menu: {
title: "Task Admin",
order: 10,
icon: "vaadin:wrench",
},
rolesAllowed: [
"ADMIN"
],
}
export default function AdminView() {
return (
<main>
Admin View
</main>
);
}
Now navigate to: http://localhost:8080
Log in as an ADMIN
. You should see Task Admin in the navigation menu. Clicking it should take you to the admin view.
Now log out and log back in as a USER
. The Task Admin menu item should no longer be visible.
Attempt to access http://localhost:8080/admin directly. You should end up on the login view again.
Make the Task List Read-Only For Users
So far all authenticated users have been able to add tasks to the todo view. You’ll now change it so that only users with the ADMIN
role can add tasks. Open src/main/frontend/views/index.tsx
and change it as follows:
import { useAuth } from "Frontend/security/auth";
...
export default function TodoView() {
const dataProvider = useDataProvider<Todo>({
list: (pageable) => TodoService.list(pageable),
});
const auth = useAuth();
const isAdmin = auth.hasAccess({ rolesAllowed: ["ADMIN"] });
return (
<main className="w-full h-full flex flex-col box-border gap-s p-m">
<ViewToolbar title="Task List">
{isAdmin && <Group> 1
<TodoEntryForm onTodoCreated={dataProvider.refresh} />
</Group>}
</ViewToolbar>
<Grid dataProvider={dataProvider.dataProvider}>
<GridColumn path="description" />
<GridColumn path="dueDate" header="DueDate">
{({ item }) => (item.dueDate ? dateFormatter.format(new Date(item.dueDate)) : 'Never')}
</GridColumn>
<GridColumn path="creationDate" header="Creation Date">
{({ item }) => dateTimeFormatter.format(new Date(item.creationDate))}
</GridColumn>
</Grid>
</main>
);
}
-
Only create the toolbar if the user is an
ADMIN
.
Go back to your browser and try the application. The toolbar should only be visible in the Task List when you are logged in as ADMIN
.
Final Thoughts
These techniques only improve the user experience and do not provide real security. Always secure your backend services to enforce true access control. Learn more in the Protect Services guide.