Skip to main content

Group Role Restrictions - Integration Examples

This guide provides practical integration examples and advanced reference material for Group Role Restrictions.

Related: See Groups Role Restriction for the core documentation:

  1. What is Group Role Restriction?
  2. Hints in the token — why access was granted
  3. Real-time validation via POST /groups-srv/verifications

Overview

When Group Role Restrictions are configured as a Token Condition, the verification results are embedded as custom claims in the JWT access token. These claims (groupIds, rolesOfGroup, allowedGroups) can be used directly in your application for authorization decisions.

JWT Integration Pattern

The common pattern across all frameworks is:

// 1. Extract roles from JWT
const roles = jwt.claims.rolesOfGroup;

// 2. Map roles to permissions
const permissions = mapRolesToPermissions(roles);

// 3. Check permission
if (permissions.includes(requiredPermission)) {
// Allow access
} else {
// Deny access
}

Embedding rolesOfGroup in JWT tokens allows instant, secure access control without extra database lookups. It provides applications with a ready-to-use, flattened list of user permissions—enabling seamless integration with modern role-based systems that expect simple, direct role information.

Integration Examples

1. Spring Security (Java)

How it works:

  • Uses flat permission strings in @PreAuthorize annotations
  • No group hierarchy - just direct permission checks
  • Perfect for JWT role mapping
// Spring Security with JWT roles
@RestController
public class UserController {

@PreAuthorize("hasRole('ADMIN') or hasRole('USER_MANAGER')")
@GetMapping("/users")
public List<User> getUsers() {
return userService.getAllUsers();
}

@PreAuthorize("hasAuthority('USER_DELETE')")
@DeleteMapping("/users/{id}")
public void deleteUser(@PathVariable String id) {
userService.deleteUser(id);
}
}

// JWT Role to Permission Mapping
@Component
public class JwtRoleMapper {

private final Map<String, List<String>> rolePermissions = Map.of(
"admin", Arrays.asList("USER_READ", "USER_WRITE", "USER_DELETE", "PROJECT_MANAGE"),
"developer", Arrays.asList("PROJECT_READ", "PROJECT_WRITE", "CODE_COMMIT"),
"user", Arrays.asList("PROFILE_READ", "PROFILE_WRITE")
);

public List<String> getPermissionsForRoles(List<String> roles) {
return roles.stream()
.flatMap(role -> rolePermissions.getOrDefault(role, Collections.emptyList()).stream())
.distinct()
.collect(Collectors.toList());
}
}

2. Django REST Framework (Python)

How it works:

  • Uses flat permission strings in decorators
  • No inheritance - just direct permission checks
  • Easy JWT integration
# Django with JWT roles
from rest_framework.decorators import permission_classes
from rest_framework.permissions import BasePermission

class JwtPermission(BasePermission):
def has_permission(self, request, view):
# Extract roles from JWT token
roles = request.user.jwt_claims.get('rolesOfGroup', [])

# Map roles to permissions
permissions = self.get_permissions_for_roles(roles)

# Check if user has required permission
required_permission = getattr(view, 'required_permission', None)
return required_permission in permissions

def get_permissions_for_roles(self, roles):
role_permissions = {
'admin': ['users:read', 'users:write', 'users:delete', 'projects:manage'],
'developer': ['projects:read', 'projects:write', 'code:commit'],
'user': ['profile:read', 'profile:write']
}

permissions = []
for role in roles:
permissions.extend(role_permissions.get(role, []))
return list(set(permissions)) # Remove duplicates

# Usage in views
@permission_classes([JwtPermission])
class UserViewSet(viewsets.ModelViewSet):
required_permission = 'users:read'

def list(self, request):
# Only users with 'users:read' permission can access
return Response({'users': []})

3. Apache Shiro (Java)

How it works:

  • Uses flat permission strings like user:read, project:write
  • No hierarchical structure - just direct permission checks
  • Perfect for JWT role mapping
// Apache Shiro with JWT roles
@Component
public class JwtRealm extends AuthorizingRealm {

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();

// Extract roles from JWT token
List<String> roles = getRolesFromJwtToken(username);

// Map roles to permissions
Set<String> permissions = mapRolesToPermissions(roles);

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(new HashSet<>(roles));
info.setStringPermissions(permissions);

return info;
}

private Set<String> mapRolesToPermissions(List<String> roles) {
Map<String, List<String>> rolePermissions = Map.of(
"admin", Arrays.asList("user:read", "user:write", "user:delete", "project:manage"),
"developer", Arrays.asList("project:read", "project:write", "code:commit"),
"user", Arrays.asList("profile:read", "profile:write")
);

return roles.stream()
.flatMap(role -> rolePermissions.getOrDefault(role, Collections.emptyList()).stream())
.collect(Collectors.toSet());
}
}

4. Open Policy Agent (OPA)

How it works:

  • Uses Rego policies with flat permission lists
  • No hierarchical structure - just boolean permission checks
  • Perfect for JWT role mapping
# OPA Policy using flattened roles
package auth

import input.jwt.claims

# Map JWT roles to permissions
permissions := {
"admin": ["users:read", "users:write", "users:delete", "projects:manage"],
"developer": ["projects:read", "projects:write", "code:commit"],
"user": ["profile:read", "profile:write"]
}

# Check if user has permission
allow {
user_roles := claims.rolesOfGroup
required_permission := input.permission
user_permissions := {p | role := user_roles[_]; p := permissions[role]}
required_permission in user_permissions
}

Using Token Claims

Extracting rolesOfGroup Claim

The rolesOfGroup claim is the most commonly used claim, providing a flattened list of all roles:

// JavaScript/Node.js
const jwt = require('jsonwebtoken');
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.decode(token);
const roles = decoded.rolesOfGroup; // ["developer", "project-manager"]
# Python
import jwt
token = request.headers.get('Authorization').split(' ')[1]
decoded = jwt.decode(token, verify=False)
roles = decoded.get('rolesOfGroup', []) # ["developer", "project-manager"]

Extracting groupIds Claim

Use groupIds when you need to know which specific groups the user belongs to:

const groupIds = decoded.groupIds; // ["eng-group", "support-group"]

Extracting allowedGroups Claim

Use allowedGroups when you need detailed group-role mappings:

const allowedGroups = decoded.allowedGroups;
// [
// { "groupId": "eng-group", "roles": ["developer"] },
// { "groupId": "support-group", "roles": ["support-agent"] }
// ]

Match Conditions Reference

The verification system supports two match conditions between filters. Behavior described here applies to login-time token claims. For runtime API responses, see Section 3 of the overview.

  • "and" (AndCondition) — User must satisfy ALL filters
  • "or" (OrCondition) — User must satisfy ANY filter

OR Condition ("or")

  • Behavior: Returns immediately when any filter matches.
  • Groups added to token: Only groups from the first matching filter.
  • Roles added to token: Only roles from the first matching filter.

Example app configuration:

{
"groupRoleRestriction": {
"matchCondition": "or",
"filters": [
{"groupId": "eng-group", "roleFilter": {"matchCondition": "or", "roles": ["developer"]}},
{"groupId": "user-group", "roleFilter": {"matchCondition": "or", "roles": ["user"]}},
{"groupType": "project", "roleFilter": {"matchCondition": "or", "roles": ["project-manager"]}}
],
"hints": ["groupIds", "rolesOfGroup", "allowedGroups"]
}
}

If the user is in eng-group with the developer role:

  • Result: Login allowed (verified: true)
  • Token claims added:
    • groupIds: ["eng-group"]
    • rolesOfGroup: ["developer"]
    • allowedGroups: [{"groupId": "eng-group", "roles": ["developer"]}]
  • Processing: Stops after first match (OR condition)

AND Condition ("and")

  • Behavior: Must satisfy ALL filters; processes each filter separately.
  • Groups added to token: Groups from ALL matching filters.
  • Roles added to token: Roles from ALL matching filters (deduplicated).

Example app configuration:

{
"groupRoleRestriction": {
"matchCondition": "and",
"filters": [
{"groupId": "eng-group", "roleFilter": {"matchCondition": "or", "roles": ["project-manager"]}},
{"groupType": "project", "roleFilter": {"matchCondition": "or", "roles": ["developer"]}}
],
"hints": ["groupIds", "rolesOfGroup", "allowedGroups"]
}
}

If the user is in eng-group AND in a project group:

  • Result: Login allowed (verified: true)
  • Token claims added:
    • groupIds: ["eng-group", "project-group"]
    • rolesOfGroup: ["project-manager", "developer"]
    • allowedGroups:
      [
      {"groupId": "eng-group", "roles": ["project-manager"]},
      {"groupId": "project-group", "roles": ["developer"]}
      ]

Usage Scenarios

Multi-Tenant SaaS Application

Challenge: A SaaS platform serves multiple customers (tenants). Each customer's users should only see their own tenant's data.

Solution: Configure a group role restriction per tenant application:

  • Tenant A app: verify groupId: "tenant-a" with role tenant-member
  • Tenant B app: verify groupId: "tenant-b" with role tenant-member

Result: When users log in, their token only contains their tenant group ID. The application can route them to the correct tenant data without additional database queries.

Enterprise Application Suite

Challenge: A large enterprise has 20+ applications. Users belong to multiple departments but each app should only see relevant roles.

Solution: Configure group role restrictions per application:

  • Finance App: only verify finance-department groups
  • Engineering App: only verify engineering-department groups
  • HR App: only verify hr-department groups

Result: Users get application-specific tokens with only relevant roles, preventing role confusion and keeping tokens small.

Compliance and Audit Requirements

Challenge: Ensure users only access resources they're authorized for, with a full audit trail.

Solution: Group Role Restriction ensures:

  • Only verified group memberships appear in tokens
  • All verification attempts are logged
  • Failed verifications block access automatically

Use the allowedGroups hint for the most detailed group-role mapping in the token.

Result: Complete audit trail of who accessed what, when, and why access was granted or denied.

Flattened Role Lists

A flattened role list means all permissions are listed in a simple, flat structure — no hierarchies, no nested groups, and no role chaining. The rolesOfGroup JWT claim provides this format.

Why flattened role lists

  • Simplicity: Easy to understand and implement
  • Performance: Fast permission checks
  • Flexibility: Easy to add or remove permissions
  • Debugging: Clear what permissions a user has
  • Caching: Easy to cache permission lists
  • Microservices: No need for complex group hierarchies

The OPA integration example in Integration Examples shows how flattened roles map cleanly to policy checks.

Persistent Verification Requests (CRUD)

In addition to immediate verification via POST /groups-srv/verifications, you can create persistent Group Verification Requests stored by ID for reuse across applications or services.

APIDescriptionLink
Check User Group Restrictions (POST)Immediate verification with sub, matchCondition, filters, and optional hints for response fieldsView API
Verify User Access by Request ID (GET)Verify a user using a stored request id and sub query parameter; uses hints from the stored requestView API
Create Group Verification RequestCreate a persistent request with required id (returns 409 if duplicate)View API
Get Group Verification Request by IDRetrieve a specific request by idView API
Update Group Verification Request by IDUpdate matchCondition, filters, and hintsView API
Delete Group Verification Request by IDDelete a request by idView API
Find Group Verification RequestsList requests using graph filterView API

Persistent requests require a unique id (alphanumeric, underscores, hyphens), omit sub (supplied at verification time), and support optional hints for controlling API response fields on GET verify. Hints on app groupRoleRestriction affect JWT claims at login — see Groups Role Restriction.

For CRUD authentication requirements and filter rules, see Manage persistent verification requests.

Best Practices

  1. Use rolesOfGroup for most cases — It's the simplest and most efficient claim
  2. Map roles to permissions — Don't check roles directly; map them to specific permissions
  3. Cache permission mappings — Role-to-permission mappings rarely change
  4. Validate token signature — Always verify JWT signature before trusting claims
  5. Handle missing claims gracefully — Claims may not be present if verification failed
  6. Choose the right validation approach — Use JWT claims for fast post-login checks; call POST /groups-srv/verifications or GET /groups-srv/verifications/{verificationId} when permissions may have changed since login
  7. Use allowedGroups for audit trails — When you need to know both the group and role that granted access, prefer allowedGroups over rolesOfGroup alone
  8. Pass hints on POST when needed — Optional hints in the POST body control which fields appear in the API response (groupIds, rolesOfGroup, allowedGroups); they do not affect JWT claims at login