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:
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
@PreAuthorizeannotations - 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 roletenant-member - Tenant B app: verify
groupId: "tenant-b"with roletenant-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-departmentgroups - Engineering App: only verify
engineering-departmentgroups - HR App: only verify
hr-departmentgroups
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.
| API | Description | Link |
|---|---|---|
| Check User Group Restrictions (POST) | Immediate verification with sub, matchCondition, filters, and optional hints for response fields | View 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 request | View API |
| Create Group Verification Request | Create a persistent request with required id (returns 409 if duplicate) | View API |
| Get Group Verification Request by ID | Retrieve a specific request by id | View API |
| Update Group Verification Request by ID | Update matchCondition, filters, and hints | View API |
| Delete Group Verification Request by ID | Delete a request by id | View API |
| Find Group Verification Requests | List requests using graph filter | View 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
- Use
rolesOfGroupfor most cases — It's the simplest and most efficient claim - Map roles to permissions — Don't check roles directly; map them to specific permissions
- Cache permission mappings — Role-to-permission mappings rarely change
- Validate token signature — Always verify JWT signature before trusting claims
- Handle missing claims gracefully — Claims may not be present if verification failed
- Choose the right validation approach — Use JWT claims for fast post-login checks; call
POST /groups-srv/verificationsorGET /groups-srv/verifications/{verificationId}when permissions may have changed since login - Use
allowedGroupsfor audit trails — When you need to know both the group and role that granted access, preferallowedGroupsoverrolesOfGroupalone - Pass
hintson POST when needed — Optionalhintsin the POST body control which fields appear in the API response (groupIds,rolesOfGroup,allowedGroups); they do not affect JWT claims at login
Related Documentation
- Groups Role Restriction - Complete feature documentation
- Token Conditions and Prechecks - How token conditions work
- Groups and Roles API - API reference