Scaling Bitmasking: Mastering Permissions Beyond 64 Bits with Multiple Masks
Posted on July 27, 2025 • 7 minutes • 1298 words
Remember our last chat about how bitmasking can rescue your heavy JWTs? (If you haven’t read it yet, you can catch up on the basics and benefits here: “Fixing Heavy JWTs: Bitmasking for Lean & Scalable Permissions”). We saw how a single 64-bit integer could compactly represent up to 64 distinct permissions, slashing your token size from hefty kilobytes down to a few hundred bytes. That’s a game-changer!
But what if you’re building a massive system with hundreds, or even thousands, of super-granular permissions across multiple modules or services? The thought of hitting that 64-bit ceiling might make your palms sweaty. Don’t sweat it! As we hinted before, the beauty of bitmasking is its inherent scalability. You don’t just stop at one number.
This is where advanced multi-mask architectures come into play. We’re going to dive into how you can manage permissions far beyond a single 64-bit integer, keeping your JWTs compact and your system logically organized.
Why Go Multi-Mask?
There are two primary reasons why you’d venture into the realm of multiple permission masks:
Exceeding the 64-bit Limit: This is the most obvious one. If your application genuinely has more than 64 unique, distinct permissions that a single user might possess, you simply need more bits. Using multiple 64-bit integers extends your capacity exponentially.
Logical Grouping by Service/Module: Even if you could technically cram all 100 permissions into two 64-bit integers, it might make more sense to group them logically. Think about a large SaaS platform with separate modules like a Blog, an E-commerce Store, and a User Management System. It’s cleaner to have:
blogPermissionsMask
eCommercePermissionsMask
userManagementPermissionsMask
This compartmentalization makes your code more readable, easier to debug, and simpler to manage as your application grows.
Defining Your Multi-Mask Architecture
The core concept remains the same: each permission gets a unique bit position. The difference is that you now define these positions within specific categories or domains.
Let’s redefine our PERMISSION_FLAGS
into logical groups. For instance, if you have a Blog
service and a User
service:
// app/Permissions/BlogPermissions.php
const BLOG_PERMISSION_FLAGS = [
"BLOG_CREATE" => 1 << 0, // 1
"BLOG_READ" => 1 << 1, // 2
"BLOG_UPDATE" => 1 << 2, // 4
"BLOG_DELETE" => 1 << 3, // 8
"BLOG_PUBLISH" => 1 << 4, // 16
// ... up to 64 blog-specific permissions
];
// app/Permissions/UserPermissions.php
const USER_PERMISSION_FLAGS = [
"USER_CREATE" => 1 << 0, // 1
"USER_READ" => 1 << 1, // 2
"USER_UPDATE" => 1 << 2, // 4
"USER_DELETE" => 1 << 3, // 8
"USER_RESET_PASSWORD" => 1 << 4, // 16
// ... up to 64 user-specific permissions
];
// And your JWT payload might now look like this:
{
"iss": "your-auth-server.com",
"exp": 1735689600,
"userid": "user123",
"email": "user@example.com",
"blogMask": 27, // Example: BLOG_CREATE (1), BLOG_READ (2), BLOG_PUBLISH (16) + 8 = 27
"userMask": 7 // Example: USER_CREATE (1), USER_READ (2), USER_UPDATE (4) = 7
}
Notice how we now have blogMask
and userMask
instead of a single permissionMask
. Each is independently calculated and stored.
Backend Implementation: Handling Multiple Masks (PHP Example)
Your backend logic needs to adapt to calculate and check these separate masks.
<?php
// Assume these constants are loaded/imported from their respective files
require_once 'app/Permissions/BlogPermissions.php';
require_once 'app/Permissions/UserPermissions.php';
// A single function to calculate a mask for a given set of permissions and a flag group
function calculateSpecificPermissionMask(array $permissionNames, array $flagGroup): int {
$mask = 0;
foreach ($permissionNames as $name) {
if (isset($flagGroup[$name])) {
$mask |= $flagGroup[$name];
}
}
return $mask;
}
// Example usage to get user's total masks (e.g., from database)
function getUserAllPermissions(string $userId): array {
// In a real app, you'd fetch user roles/permissions from DB,
// then map them to the correct flag groups.
// This is just a conceptual example:
$rawBlogPermissions = ["BLOG_READ", "BLOG_CREATE", "BLOG_PUBLISH"];
$rawUserPermissions = ["USER_READ", "USER_UPDATE"];
$blogMask = calculateSpecificPermissionMask($rawBlogPermissions, BLOG_PERMISSION_FLAGS);
$userMask = calculateSpecificPermissionMask($rawUserPermissions, USER_PERMISSION_FLAGS);
return [
'blogMask' => $blogMask,
'userMask' => $userMask
];
}
// Function to check a specific permission within a specific mask
function hasSpecificPermission(int $userMask, string $permissionName, array $flagGroup): bool {
if (!isset($flagGroup[$permissionName])) {
return false; // Permission not defined in this group
}
return ($userMask & $flagGroup[$permissionName]) !== 0;
}
// --- Let's see it in action! ---
$userMasks = getUserAllPermissions("user123");
$userBlogMask = $userMasks['blogMask'];
$userUserMask = $userMasks['userMask'];
echo "User Blog Mask: " . $userBlogMask . "\n"; // e.g., 27
echo "User User Mask: " . $userUserMask . "\n"; // e.g., 7
echo "Can user create a blog post? " . (hasSpecificPermission($userBlogMask, "BLOG_CREATE", BLOG_PERMISSION_FLAGS) ? "Yes!" : "No.") . "\n";
echo "Can user delete a blog post? " . (hasSpecificPermission($userBlogMask, "BLOG_DELETE", BLOG_PERMISSION_FLAGS) ? "Yes!" : "No.") . "\n";
echo "Can user reset another user's password? " . (hasSpecificPermission($userUserMask, "USER_RESET_PASSWORD", USER_PERMISSION_FLAGS) ? "Absolutely!" : "Nope.") . "\n";
// When creating the JWT payload:
$jwtPayload = [
// ... other claims ...
'blogMask' => $userBlogMask,
'userMask' => $userUserMask
];
// Encode this payload into your JWT
?>
Frontend Implementation: Consuming Multiple Masks (Vanilla JavaScript)
Your frontend will similarly adapt to check the appropriate mask based on the UI element or feature it’s trying to render.
// Ensure these flag groups match your backend definitions!
const BLOG_PERMISSION_FLAGS_JS = {
"BLOG_CREATE": 1 << 0, "BLOG_READ": 1 << 1, "BLOG_UPDATE": 1 << 2,
"BLOG_DELETE": 1 << 3, "BLOG_PUBLISH": 1 << 4
};
const USER_PERMISSION_FLAGS_JS = {
"USER_CREATE": 1 << 0, "USER_READ": 1 << 1, "USER_UPDATE": 1 << 2,
"USER_DELETE": 1 << 3, "USER_RESET_PASSWORD": 1 << 4
};
// Assume this is your decoded JWT payload
const decodedJwtPayload = {
"iss": "your-auth-server.com",
"exp": 1735689600,
"userid": "user123",
"email": "user@example.com",
"blogMask": 27, // Example from backend
"userMask": 7 // Example from backend
};
// Generic function to check permission against a specific mask type
function checkPermission(maskValue, permissionName, flagGroup) {
if (!(permissionName in flagGroup)) {
return false;
}
return (maskValue & flagGroup[permissionName]) !== 0;
}
// --- How the Frontend uses it for UI! ---
const userBlogMask = decodedJwtPayload.blogMask;
const userUserMask = decodedJwtPayload.userMask;
// Example for a blog-related button
const createBlogPostButton = document.getElementById('createBlogPostButton');
if (createBlogPostButton) {
if (checkPermission(userBlogMask, "BLOG_CREATE", BLOG_PERMISSION_FLAGS_JS)) {
createBlogPostButton.style.display = 'block';
} else {
createBlogPostButton.style.display = 'none';
}
}
// Example for a user management feature
const resetUserPasswordFeature = document.getElementById('resetUserPasswordFeature');
if (resetUserPasswordFeature) {
if (checkPermission(userUserMask, "USER_RESET_PASSWORD", USER_PERMISSION_FLAGS_JS)) {
resetUserPasswordFeature.style.display = 'block';
} else {
resetUserPasswordFeature.style.display = 'none';
}
}
console.log("Can view blog posts? ", checkPermission(userBlogMask, "BLOG_READ", BLOG_PERMISSION_FLAGS_JS) ? "Yes!" : "No.");
console.log("Can create users? ", checkPermission(userUserMask, "USER_CREATE", USER_PERMISSION_FLAGS_JS) ? "Yes!" : "No.");
Considerations for Multi-Mask Architectures
While highly effective, managing multiple masks introduces a few considerations:
- Flag Group Management: You’ll need a clear system for defining and maintaining your
PERMISSION_FLAGS
constants for each service or module. This is crucial for consistency between backend and frontend. - Version Control: As your permissions evolve, managing changes to these bit flags requires careful version control to avoid breaking existing assignments.
- Initial Setup: Setting up the initial assignment of permissions to users might become slightly more complex, as you’re calculating multiple masks instead of one. However, the benefits in runtime efficiency and JWT compactness far outweigh this.
- Extensibility: If you introduce a brand new service, you’ll add a new mask to your JWT. This keeps your system modular.
Conclusion: Scaling Elegantly with Bitmasks
By embracing a multi-mask architecture, you can truly unlock the full potential of bitmasking for permission management. You’re no longer limited by a single 64-bit integer, and you gain the added benefit of logical separation within your JWTs.
Your tokens remain incredibly compact instead of thousands of string characters, you’re sending just a handful of numbers, regardless of how many hundreds of permissions they represent. This ensures your applications stay fast, responsive, and free from those pesky “header too large” errors, even as your system scales to encompass vast and complex permission sets.
So, go forth and mask! Your leaner, more powerful JWTs (and happier users) will thank you.