Authentication
Authentication
Access control and permission management for your diamond contracts. Build secure systems with role-based access control.
Overview
Authentication in Compose is handled through composable facets that implement role-based access control (RBAC). The system is designed to be flexible, secure, and easy to integrate with your custom facets.
Authentication facets use the same shared storage pattern as all Compose components, allowing both standard and custom facets to check permissions consistently.
Core Features
Role-Based Access
Define custom roles with specific permissions for different user types.
Flexible Permissions
Grant and revoke permissions dynamically without redeployment.
Secure by Default
Built-in checks and modifiers to prevent unauthorized access.
Access Control Facet
The AccessControlFacet provides standard role-based access control functionality:
Key Functions
grantRole(bytes32 role, address account)Grants a role to an account. Only callable by role admin.
rolebytes32requiredThe role identifier (use keccak256 hash of role name)
accountaddressrequiredThe address to grant the role to
revokeRole(bytes32 role, address account)Revokes a role from an account. Only callable by role admin.
rolebytes32requiredThe role identifier to revoke
accountaddressrequiredThe address to revoke the role from
hasRole(bytes32 role, address account)Checks if an account has a specific role.
rolebytes32requiredThe role identifier to check
accountaddressrequiredThe address to check
Usage Example
Here's how to integrate access control in your custom facet:
import {LibAccessControl} from "compose/LibAccessControl.sol";
contract AdminFacet {
// Define your custom role
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
function adminOnlyFunction() external {
// Check if caller has admin role
require(
LibAccessControl.hasRole(ADMIN_ROLE, msg.sender),
"AccessControl: caller is not admin"
);
// Your admin logic here
performAdminAction();
}
function performAdminAction() internal {
// Implementation
}
}
Always define role constants using keccak256 hashes of descriptive names. This makes your code more readable and prevents typos.
Role Hierarchy
Compose supports hierarchical roles where certain roles can manage other roles:
// Setup role hierarchy
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
// DEFAULT_ADMIN_ROLE can grant/revoke MINTER_ROLE and BURNER_ROLE
// MINTER_ROLE can only mint
// BURNER_ROLE can only burn
Integration with Custom Facets
Your custom facets can use LibAccessControl to check permissions:
import {LibAccessControl} from "compose/LibAccessControl.sol";
import {LibERC20} from "compose/LibERC20.sol";
contract TokenMinterFacet {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
function mintTokens(address to, uint256 amount) external {
// Check minter permission
require(
LibAccessControl.hasRole(MINTER_ROLE, msg.sender),
"TokenMinter: caller is not minter"
);
// Mint using Compose's ERC20 library
LibERC20.mint(to, amount);
}
}
The LibAccessControl library accesses the same storage as AccessControlFacet, so permissions set through the facet are instantly available to your custom facets!
Security Considerations
Always protect role management functions. The DEFAULT_ADMIN_ROLE has ultimate control over all roles.
Best Practices
- Use Role Modifiers: Create reusable modifiers for role checks
- Minimize Admin Privileges: Grant only necessary permissions
- Audit Role Changes: Emit events for all role grants/revokes
- Test Thoroughly: Verify access control in all scenarios
- Document Roles: Clearly document what each role can do
Common Patterns
Multi-Signature Admin
// Use a multi-sig wallet as the admin role holder
address multiSigWallet = 0x...;
LibAccessControl.grantRole(DEFAULT_ADMIN_ROLE, multiSigWallet);
Time-Locked Roles
contract TimeLockFacet {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
mapping(address => uint256) public roleExpiry;
function grantTemporaryRole(
address account,
uint256 duration
) external {
require(
LibAccessControl.hasRole(DEFAULT_ADMIN_ROLE, msg.sender),
"Not admin"
);
LibAccessControl.grantRole(OPERATOR_ROLE, account);
roleExpiry[account] = block.timestamp + duration;
}
function revokeExpiredRoles(address account) external {
if (block.timestamp >= roleExpiry[account]) {
LibAccessControl.revokeRole(OPERATOR_ROLE, account);
}
}
}