Overview
SuperTokens Core provides a flexible role-based access control (RBAC) system that supports:
Hierarchical role and permission structure
Multi-tenant role assignments
Role inheritance across user pool
Bulk role operations
Core Concepts
Roles vs Permissions
Roles Named collections of permissions (e.g., “admin”, “editor”, “viewer”)
Permissions Granular access rights (e.g., “user:read”, “post:write”, “admin:delete”)
Relationship : A role contains multiple permissions. A user can have multiple roles.
User → Roles → Permissions
Storage Architecture
From io/supertokens/userroles/UserRoles.java:46-51 :
Roles are stored in the public tenant storage (app-level), while role-to-user mappings are stored in each tenant’s storage. This allows roles to be shared across all tenants in an app.
Creating Roles
From io/supertokens/userroles/UserRoles.java:124-161 :
public static boolean createNewRoleOrModifyItsPermissions (
AppIdentifier appIdentifier,
Storage storage,
String role,
String [] permissions
) {
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
return userRolesStorage . startTransaction (con -> {
// Create role if it doesn't exist
boolean wasANewRoleCreated = userRolesStorage
. createNewRoleOrDoNothingIfExists_Transaction (
appIdentifier, con, role
);
// Add permissions to role
if (permissions != null ) {
for ( String permission : permissions) {
userRolesStorage
. addPermissionToRoleOrDoNothingIfExists_Transaction (
appIdentifier, con, role, permission
);
}
}
userRolesStorage . commitTransaction (con);
return wasANewRoleCreated;
});
}
Example: Create Role with Permissions
// Create admin role with all permissions
boolean created = UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier,
storage,
"admin" ,
new String []{
"user:read" ,
"user:write" ,
"user:delete" ,
"post:read" ,
"post:write" ,
"post:delete"
}
);
if (created) {
System . out . println ( "Admin role created" );
}
// Create editor role with limited permissions
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier,
storage,
"editor" ,
new String []{
"user:read" ,
"post:read" ,
"post:write"
}
);
// Create viewer role
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier,
storage,
"viewer" ,
new String []{ "post:read" }
);
Assigning Roles to Users
From io/supertokens/userroles/UserRoles.java:41-60 :
public static boolean addRoleToUser (
Main main,
TenantIdentifier tenantIdentifier,
Storage storage,
String userId,
String role
) throws UnknownRoleException {
// Verify role exists in app storage
Storage appStorage = StorageLayer . getStorage (
tenantIdentifier . toAppIdentifier (). getAsPublicTenantIdentifier (),
main
);
if ( ! doesRoleExist ( tenantIdentifier . toAppIdentifier (), appStorage, role)) {
throw new UnknownRoleException ();
}
try {
StorageUtils . getUserRolesStorage (storage)
. addRoleToUser (tenantIdentifier, userId, role);
return true ; // Role added
} catch ( DuplicateUserRoleMappingException e ) {
return false ; // User already has role
}
}
Example: Assign Roles
try {
// Assign admin role
boolean added = UserRoles . addRoleToUser (
main,
tenantIdentifier,
storage,
userId,
"admin"
);
if (added) {
System . out . println ( "Admin role assigned" );
} else {
System . out . println ( "User already has admin role" );
}
} catch ( UnknownRoleException e ) {
System . out . println ( "Role doesn't exist. Create it first." );
}
Bulk Role Assignment
From io/supertokens/userroles/UserRoles.java:62-109 :
For bulk imports and batch operations:
public static void addMultipleRolesToMultipleUsers (
Main main,
AppIdentifier appIdentifier,
Storage storage,
Map < TenantIdentifier, Map < String, List < String >>> rolesToUserByTenant
) {
Storage appStorage = StorageLayer . getStorage (
appIdentifier . getAsPublicTenantIdentifier (), main
);
UserRolesSQLStorage publicRoleStorage =
StorageUtils . getUserRolesStorage (appStorage);
// Verify all roles exist
publicRoleStorage . startTransaction (con -> {
Set < String > allRoles = new HashSet <>();
for ( Map < String , List < String >> tenantRoles : rolesToUserByTenant . values ()) {
for ( List < String > roles : tenantRoles . values ()) {
allRoles . addAll (roles);
}
}
List < String > rolesFound = publicRoleStorage
. doesMultipleRoleExist_Transaction (
appIdentifier, con, new ArrayList <>(allRoles)
);
// Check if any roles are missing
if ( ! new HashSet <>(rolesFound). containsAll (allRoles)) {
throw new UnknownRoleException ();
}
return null ;
});
// Add role-to-user mappings
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
userRolesStorage . startTransaction (con -> {
userRolesStorage . addRolesToUsers_Transaction (
con, rolesToUserByTenant
);
userRolesStorage . commitTransaction (con);
return null ;
});
}
Example: Bulk Assignment
// Prepare role assignments by tenant
Map < TenantIdentifier , Map < String , List < String >>> rolesToUserByTenant =
new HashMap <>();
TenantIdentifier tenant1 = new TenantIdentifier (
connectionUriDomain, appId, "tenant1"
);
// User1: admin + editor
// User2: editor + viewer
Map < String , List < String >> tenant1Users = new HashMap <>();
tenant1Users . put ( "user1" , Arrays . asList ( "admin" , "editor" ));
tenant1Users . put ( "user2" , Arrays . asList ( "editor" , "viewer" ));
rolesToUserByTenant . put (tenant1, tenant1Users);
// Bulk assign
UserRoles . addMultipleRolesToMultipleUsers (
main, appIdentifier, storage, rolesToUserByTenant
);
Retrieving User Roles
From io/supertokens/userroles/UserRoles.java:223-226 :
public static String [] getRolesForUser (
TenantIdentifier tenantIdentifier,
Storage storage,
String userId
) {
return StorageUtils . getUserRolesStorage (storage)
. getRolesForUser (tenantIdentifier, userId);
}
Example: Get User’s Roles
String [] roles = UserRoles . getRolesForUser (
tenantIdentifier,
storage,
userId
);
System . out . println ( "User has " + roles . length + " roles:" );
for ( String role : roles) {
System . out . println ( " - " + role);
}
// Check if user has specific role
boolean isAdmin = Arrays . asList (roles). contains ( "admin" );
Managing Permissions
Get Permissions for Role
From io/supertokens/userroles/UserRoles.java:257-270 :
public static String [] getPermissionsForRole (
AppIdentifier appIdentifier,
Storage storage,
String role
) throws UnknownRoleException {
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
if ( ! userRolesStorage . doesRoleExist (appIdentifier, role)) {
throw new UnknownRoleException ();
}
return userRolesStorage . getPermissionsForRole (appIdentifier, role);
}
Example: Check User Permissions
// Get all user's roles
String [] roles = UserRoles . getRolesForUser (
tenantIdentifier, storage, userId
);
// Collect all permissions
Set < String > allPermissions = new HashSet <>();
for ( String role : roles) {
String [] permissions = UserRoles . getPermissionsForRole (
appIdentifier, storage, role
);
allPermissions . addAll ( Arrays . asList (permissions));
}
// Check specific permission
if ( allPermissions . contains ( "post:delete" )) {
System . out . println ( "User can delete posts" );
}
Delete Permissions from Role
From io/supertokens/userroles/UserRoles.java:280-308 :
public static void deletePermissionsFromRole (
AppIdentifier appIdentifier,
Storage storage,
String role,
String [] permissions // null to delete all
) throws UnknownRoleException {
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
userRolesStorage . startTransaction (con -> {
if ( ! userRolesStorage . doesRoleExist_Transaction (
appIdentifier, con, role)) {
throw new UnknownRoleException ();
}
if (permissions == null ) {
// Delete all permissions
userRolesStorage . deleteAllPermissionsForRole_Transaction (
appIdentifier, con, role
);
} else {
// Delete specific permissions
for ( String permission : permissions) {
userRolesStorage . deletePermissionForRole_Transaction (
appIdentifier, con, role, permission
);
}
}
userRolesStorage . commitTransaction (con);
return null ;
});
}
Removing Roles
Remove Role from User
From io/supertokens/userroles/UserRoles.java:187-211 :
public static boolean removeUserRole (
TenantIdentifier tenantIdentifier,
Storage storage,
String userId,
String role
) throws UnknownRoleException {
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
return userRolesStorage . startTransaction (con -> {
// Verify role exists
boolean doesRoleExist = userRolesStorage
. doesRoleExist_Transaction (
tenantIdentifier . toAppIdentifier (), con, role
);
if ( ! doesRoleExist) {
throw new UnknownRoleException ();
}
// Remove role from user
return userRolesStorage . deleteRoleForUser_Transaction (
tenantIdentifier, con, userId, role
);
});
}
Remove All Roles from User
From io/supertokens/userroles/UserRoles.java:374-378 :
public static int deleteAllRolesForUser (
TenantIdentifier tenantIdentifier,
Storage storage,
String userId
) {
return StorageUtils . getUserRolesStorage (storage)
. deleteAllRolesForUser (tenantIdentifier, userId);
}
Delete Role Completely
From io/supertokens/userroles/UserRoles.java:336-353 :
Deletes role and all user associations across all tenants:
public static boolean deleteRole (
Main main,
AppIdentifier appIdentifier,
String role
) {
Storage [] storages = StorageLayer . getStoragesForApp (
main, appIdentifier
);
boolean deletedRole = false ;
// Delete user-role associations from all tenants
for ( Storage storage : storages) {
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
deletedRole = userRolesStorage
. deleteAllUserRoleAssociationsForRole (appIdentifier, role)
|| deletedRole;
}
// Delete role itself from public tenant
Storage appStorage = StorageLayer . getStorage (
appIdentifier . getAsPublicTenantIdentifier (), main
);
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (appStorage);
deletedRole = userRolesStorage . deleteRole (appIdentifier, role)
|| deletedRole;
return deletedRole;
}
Deleting a role removes it from all users across all tenants in the app. This operation cannot be undone.
Querying Roles
List All Roles
From io/supertokens/userroles/UserRoles.java:362-365 :
public static String [] getRoles (
AppIdentifier appIdentifier,
Storage storage
) {
return StorageUtils . getUserRolesStorage (storage)
. getRoles (appIdentifier);
}
Get Users with Role
From io/supertokens/userroles/UserRoles.java:236-247 :
public static String [] getUsersForRole (
TenantIdentifier tenantIdentifier,
Storage storage,
String role
) throws UnknownRoleException {
UserRolesSQLStorage userRolesStorage =
StorageUtils . getUserRolesStorage (storage);
if ( ! userRolesStorage . doesRoleExist (
tenantIdentifier . toAppIdentifier (), role)) {
throw new UnknownRoleException ();
}
return userRolesStorage . getUsersForRole (tenantIdentifier, role);
}
Get Roles with Permission
From io/supertokens/userroles/UserRoles.java:320-325 :
public static String [] getRolesThatHavePermission (
AppIdentifier appIdentifier,
Storage storage,
String permission
) {
return StorageUtils . getUserRolesStorage (storage)
. getRolesThatHavePermission (appIdentifier, permission);
}
Multi-Tenant Role Management
Tenant-Scoped Role Assignment
// Assign role in tenant1
UserRoles . addRoleToUser (
main,
new TenantIdentifier (cuDomain, appId, "tenant1" ),
storage,
userId,
"admin"
);
// Same user can have different roles in different tenants
UserRoles . addRoleToUser (
main,
new TenantIdentifier (cuDomain, appId, "tenant2" ),
storage,
userId,
"viewer" // Different role in tenant2
);
// Check roles per tenant
String [] tenant1Roles = UserRoles . getRolesForUser (
new TenantIdentifier (cuDomain, appId, "tenant1" ),
storage,
userId
);
// Returns: ["admin"]
String [] tenant2Roles = UserRoles . getRolesForUser (
new TenantIdentifier (cuDomain, appId, "tenant2" ),
storage,
userId
);
// Returns: ["viewer"]
Roles are defined at the app level but assigned at the tenant level . The same role name has the same permissions across all tenants.
Permission Naming Conventions
Examples:
user:read - Read user data
user:write - Create/update users
user:delete - Delete users
post:read - View posts
post:write - Create/edit posts
post:delete - Delete posts
admin:access - Access admin panel
billing:manage - Manage billing
Hierarchical Permissions
// Admin has all permissions
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "admin" ,
new String []{ "*:*" } // Wildcard for all
);
// Editor has all post permissions
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "editor" ,
new String []{ "post:*" , "user:read" }
);
// Viewer has read-only
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "viewer" ,
new String []{ "post:read" , "user:read" }
);
Complete Example
// 1. Create roles with permissions
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "admin" ,
new String []{ "user:*" , "post:*" , "admin:*" }
);
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "editor" ,
new String []{ "user:read" , "post:*" }
);
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "viewer" ,
new String []{ "user:read" , "post:read" }
);
// 2. Assign roles to users
UserRoles . addRoleToUser (
main, tenantIdentifier, storage, adminUserId, "admin"
);
UserRoles . addRoleToUser (
main, tenantIdentifier, storage, editorUserId, "editor"
);
UserRoles . addRoleToUser (
main, tenantIdentifier, storage, viewerUserId, "viewer"
);
// 3. Check user permissions
String [] userRoles = UserRoles . getRolesForUser (
tenantIdentifier, storage, editorUserId
);
Set < String > permissions = new HashSet <>();
for ( String role : userRoles) {
String [] rolePermissions = UserRoles . getPermissionsForRole (
appIdentifier, storage, role
);
permissions . addAll ( Arrays . asList (rolePermissions));
}
if ( permissions . contains ( "post:delete" )) {
// Allow delete
} else {
// Deny
}
// 4. Update role permissions
UserRoles . deletePermissionsFromRole (
appIdentifier, storage, "editor" ,
new String []{ "post:delete" } // Remove delete permission
);
// 5. Remove role from user
UserRoles . removeUserRole (
tenantIdentifier, storage, editorUserId, "editor"
);
// 6. List all users with a role
String [] admins = UserRoles . getUsersForRole (
tenantIdentifier, storage, "admin"
);
System . out . println ( "Admin users: " + admins . length );
Best Practices
Pre-Create Roles Create all roles before assigning them to users
Use Descriptive Names Role names should clearly indicate their purpose
Follow Least Privilege Grant minimum permissions necessary for each role
Document Permissions Maintain a list of all permissions and their meanings
Regular Audits Periodically review role assignments and permissions
Tenant Isolation Remember roles are app-wide but assignments are per-tenant
Common Patterns
Check Permission Helper
public boolean userHasPermission (
TenantIdentifier tenantIdentifier,
Storage storage,
String userId,
String requiredPermission
) {
try {
String [] roles = UserRoles . getRolesForUser (
tenantIdentifier, storage, userId
);
for ( String role : roles) {
String [] permissions = UserRoles . getPermissionsForRole (
appIdentifier, storage, role
);
if ( Arrays . asList (permissions). contains (requiredPermission)) {
return true ;
}
}
return false ;
} catch ( Exception e ) {
return false ;
}
}
// Usage
if ( userHasPermission (tenantIdentifier, storage, userId, "post:delete" )) {
// Allow delete
} else {
throw new ForbiddenException ( "Insufficient permissions" );
}
Role Inheritance
// Create role hierarchy
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "super_admin" ,
new String []{ "*:*" }
);
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "admin" ,
new String []{ "user:*" , "post:*" }
);
UserRoles . createNewRoleOrModifyItsPermissions (
appIdentifier, storage, "moderator" ,
new String []{ "post:*" , "user:read" }
);
// Assign multiple roles for inheritance
UserRoles . addRoleToUser (
main, tenantIdentifier, storage, userId, "admin"
);
UserRoles . addRoleToUser (
main, tenantIdentifier, storage, userId, "moderator"
);
// User now has combined permissions from both roles