Role-Based Access Control for Views

This page describes how to restrict access for selected Fusion views based on roles defined for the logged-in user.

Starting Point

To follow the examples in this article, you need a Fusion application with authentication enabled. Follow Authentication With Spring Security to get started.

Define Roles for Users with Spring Security

Roles are a set of string attributes representing the authorities that are assigned to a user.

In Spring Security, the user details used for authentication also specify roles. Typically, roles are defined in authority strings prefixed with ROLE_. After successful authentication, these are accessible via the GrantedAuthority objects returned by Authentication.getAuthorities(). See Authentication With Spring Security for examples of configuration.

Using Roles in TypeScript

A convenient way to use roles for access control in TypeScript views is to add a Fusion endpoint that gets user information, including roles, from Java during authentication.

First, define a bean representing information about the user:

public class UserInfo {

    @Nonnull
    private String name;
    @Nonnull
    private Collection<String> authorities;

    public UserInfo(String name, Collection<String> authorities) {
        this.name = name;
        this.authorities = Collections.unmodifiableCollection(authorities);
    }

    public String getName() {
        return name;
    }

    public Collection<String> getAuthorities() {
        return authorities;
    }

}

Next, add the endpoint to get a UserInfo containing authorities for the logged-in user on the client side:

@Endpoint
public class UserInfoEndpoint {

    @PermitAll
    @Nonnull
    public UserInfo getUserInfo() {
        Authentication auth = SecurityContextHolder.getContext()
                .getAuthentication();

        final List<String> authorities = auth.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList());

        return new UserInfo(auth.getName(), authorities);
    }

}

Then, change the authentication implementation in TypeScript to get the user information from the endpoint. Change the auth.ts defined in Authentication With Spring Security as follows:

import { UserInfoEndpoint } from 'Frontend/generated/endpoints';
import UserInfo from 'Frontend/generated/com/vaadin/demo/fusion/security/authentication/UserInfo';

interface Authentication {
  user: UserInfo;
}

let authentication: Authentication | undefined = undefined;

export async function login(username: string, password: string): Promise<LoginResult> {
  const result = await loginImpl(username, password);
  if (!result.error) {
    // Get user info from endpoint
    const user = await UserInfoEndpoint.getUserInfo();
    authentication = {
      user,
    };
  }

  return result;
}

Add isUserInRole helper, which enables role-based access control checks for the UI.

export function isUserInRole(role: string) {
  if (!authentication) {
    return false;
  }

  return authentication.user.authorities.includes(`ROLE_${role}`);
}

Routes With Access Control

To enable allowed roles to be specified on the view routes, define an extended type ViewRoute that has a rolesAllowed string, as follows:

export type ViewRoute = Route & {
  title?: string;
  children?: ViewRoute[];
  rolesAllowed?: string[];
};

Add a method to check access for the given route by iterating rolesAllowed using isUserInRole(), as follows:

export function isAuthorizedViewRoute(route: ViewRoute) {
  if (route.rolesAllowed) {
    return route.rolesAllowed.find((role) => isUserInRole(role));
  }

  return true;
}

Then use the method added in the route action to redirect on unauthorized access, as follows:

export const routes: ViewRoute[] = [
  {
    path: 'protected',
    component: 'protected-view',
    title: 'Protected',
    rolesAllowed: ['ADMIN'],
    action: async (context, commands: Router.Commands) => {
      const route = context.route as ViewRoute;
      if (!isAuthorizedViewRoute(route)) {
        return commands.prevent();
      }
      await import('./protected-view');
      return undefined;
    },
  },
];

Hiding Unauthorized Menu Items

Filter the route list using the isAuthorizedViewRoute() helper defined above. Then use the filtered list of routes as menu items:

private get menuRoutes() {
    return routes.filter((route) => route.title).filter(isAuthorizedViewRoute);
  }