Skip to main content

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.

Key Concept

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

POSTgrantRole(bytes32 role, address account)

Grants a role to an account. Only callable by role admin.

rolebytes32required

The role identifier (use keccak256 hash of role name)

accountaddressrequired

The address to grant the role to

DELETErevokeRole(bytes32 role, address account)

Revokes a role from an account. Only callable by role admin.

rolebytes32required

The role identifier to revoke

accountaddressrequired

The address to revoke the role from

GEThasRole(bytes32 role, address account)

Checks if an account has a specific role.

rolebytes32required

The role identifier to check

accountaddressrequired

The 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
}
}
Best Practice

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);
}
}
Shared Storage Power

The LibAccessControl library accesses the same storage as AccessControlFacet, so permissions set through the facet are instantly available to your custom facets!

Security Considerations

Important

Always protect role management functions. The DEFAULT_ADMIN_ROLE has ultimate control over all roles.

Best Practices

  1. Use Role Modifiers: Create reusable modifiers for role checks
  2. Minimize Admin Privileges: Grant only necessary permissions
  3. Audit Role Changes: Emit events for all role grants/revokes
  4. Test Thoroughly: Verify access control in all scenarios
  5. 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);
}
}
}

Next Steps