-
Notifications
You must be signed in to change notification settings - Fork 0
Authorization Rules
RailsIAM evaluates authorization based on what is defined in each authorization rule. It does not evaluate every role and permission a user owns; it only checks the requirements declared by the rule.
Each authorization rule is a possible path to access. First successful rule wins.
A useful way to think about authorization rules:
-
authorize roles:asks Does this user have the required role? -
authorize permissions:asks Does this user have the required permission? -
authorize roles + permissions:asks Does this user have both the required role and permission?
When a rule specifies roles, RailsIAM evaluates role membership.
The user must have one of the required roles (depending on role_match). Having additional permissions, including wildcard permissions (*), does not grant access if the required role is missing.
Example:
authorize roles: :adminCase: If a user has super_admin role and * permissions, he will be denied
Reason: The rule only checks for the admin role. The wildcard permission does not bypass a missing role.
When a rule specifies permissions, RailsIAM evaluates the user's effective permissions.
The user's role does not matter as long as the required permission exists.
authorize permissions: "user:update"Case 1: If a user has "sales" roles and * permissions, he will be granted.
authorize permissions: "user:update"Case 2: A user with user:* will be allowed
Reason: The permission requirement is satisfied. * works on globally no matter what the permission name for the controller action. user:* works on namespace. any permission start with user: will pass.
RailsIAM supports permission wildcards:
* → global permission, matches every permission
user:* → namespace permission, matches user:create, user:update, user:delete
product:* → matches product:create, product:update.
When both roles and permissions are specified in the same rule, RailsIAM requires both conditions to be satisfied.
authorize roles: :manager, permissions: "user:update"The user must:
- Have the manager role
- Have the user:update permission
A user having the permission is not enough because this rule also requires the manager role.
Multiple authorize declarations are evaluated using OR logic.
authorize roles: :manager, permissions: "user:update"
authorize permissions: "user:update"Evaluation:
Rule 1: manager role AND user:update permission
OR
Rule 2: user:update permission
Results:
| User | Roles | Permissions | Result | Reason |
|---|---|---|---|---|
| manager | manager | user:update | ✅ | First rule succeeds |
| employee | employee | user:update | ✅ | Second rule succeeds because permission exists |
| super_admin | super_admin | * | ✅ | Second rule succeeds because wildcard permission matches |
| manager | manager | user:view | ❌ | Neither rule is satisfied |
This allows us to combine different access strategies:
RailsIAM supports explicit permission denial through user_denied_permissions.
Denied permissions always override permissions granted from role permissions, direct user permissions or wildcard permissions.
Example:
authorize permissions: "user:delete"If user's role has permission user:delete or * or user:* but he also has denied permission user:delete, he will not be granted.
The following examples demonstrate the different ways RailsIAM authorizes access to controller actions.
For these examples, we'll use four different roles, each with its own role_permissions.
super_admin: ["*"]
admin: ["user:view", "user:create", "user:update", "user:delete", "user:activate", "user:deactivate"]
manager: ["user:*", "product:create", "product:update", "product:delete"]
sales: ["product:view", "product:analytics", "user:activity"]
We'll also use two users who both belong to the manager role to demonstrate how direct user permissions and denied permissions affect authorization.
| User | Role | Direct User Permission | Denied Permission |
|---|---|---|---|
manager1 |
manager |
product:analytics |
— |
manager2 |
manager |
— | product:delete |
As a result:
-
manager1inherits all permissions from themanagerrole and additionally gains theproduct:analyticspermission. -
manager2inherits all permissions from themanagerrole exceptproduct:delete, because user-denied permissions always take precedence over role and direct user permissions.
These users will be referenced throughout the examples below to illustrate how RailsIAM evaluates different authorization scenarios.
The following examples use the sample roles and users introduced above.
| # | Controller Action | Authorization Rule | Example User | Result | Why |
|---|---|---|---|---|---|
| 1 | AnyController#any_action |
authorize permissions: "any_permission" |
super_admin |
✅ | User's super_admin role provides the wildcard (*) permission, granting access to every permission check. |
| 2 | UsersController#create |
authorize roles: :super_admin |
super_admin |
✅ | User has the required super_admin role. No particular permission or action mentioned. so user will have access to every action. |
| 3 | UsersController#create |
authorize roles: :admin |
manager |
❌ | User does not have the required admin role. |
| 4 | UsersController#destroy |
authorize roles: [:admin, :manager] |
manager |
✅ | User has one of the allowed roles (role_match: :any, default behavior). |
| 5 | UsersController#destroy |
authorize roles: [:admin, :manager], role_match: :all |
manager |
❌ | User does not have all required roles. |
| 6 | UsersController#update |
authorize permissions: "user:update" |
manager |
✅ | Permission is inherited from the manager role. |
| 7 | ProductsController#analytics |
authorize permissions: "product:analytics" |
manager1 manager2
|
✅ ❌ |
Permission is granted directly to the manager1 through user_permissions. |
| 8 | ProductsController#destroy |
authorize permissions: "product:delete" |
manager2 manager1
|
❌ ✅ |
Permission is explicitly denied through user_denied_permissions for manager2. Denied permissions take precedence. |
| 9 | UsersController#create |
authorize permissions: "user:create" |
sales |
❌ | Permission is not granted by role permissions or direct user permissions. |
| 10 | UsersController#update |
authorize permissions: ["user:view", "user:update"] |
manager |
✅ | User has all required permissions (permission_match: :all, default behavior). |
| 11 | DashboardController#show |
authorize permissions: ["user:delete", "product:view"], permission_match: :any |
sales |
✅ | User has at least one of the required permissions. |
| 12 | ProductsController#update |
authorize roles: :manager, permissions: "product:update" |
manager |
✅ | User satisfies both the required role and permission. |
| 13 | ProductsController#update |
authorize roles: :sales, permissions: "product:update" |
sales |
❌ | User has the required role but does not have the required permission. |
| 14 | UsersController#update |
authorize permissions: "user:update" |
super_admin |
✅ | User's effective permissions contain *, which grants all permissions. |
| 15 | UsersController#update |
authorize permissions: "user:update" |
User with user:*
|
✅ | Wildcard permission (user:*) matches user:update. |
| 16 | UsersController#different_action |
authorize permissions: "user:update" |
User with user:*
|
✅ | Wildcard permissions apply regardless of controller action names. |
| 17 | UsersController#update |
authorize_resource |
manager |
✅ | Rails IAM automatically resolves the required permission as user:update. |
| 18 | UsersController#update |
authorize_resource |
sales |
❌ | Rails IAM resolves the required permission as user:update, which the user does not have. |
| 19 | SessionsController#create |
skip_authorization |
Anonymous | ✅ | Authorization is skipped for the entire controller. |
| 20 | SessionsController#create |
skip_authorization only: :create |
Anonymous | ✅ | The create action is explicitly excluded from authorization. |
| 21 | SessionsController#refresh |
skip_authorization except: :refresh |
Anonymous | ❌ | The refresh action raises an AuthorizationError
|
| 22 | UsersController#export |
(No authorization rule) | Anonymous | ❌ | Rails IAM follows a default-deny policy and raises an AuthorizationError. e.g., No authorization rule applied for users#export |
skip_authorization does not disable authentication checks unless skip_authentication is also applied.
By default, Rails IAM follows a default-deny approach.
Every protected controller action must have an authorization rule defined using authorize or authorize_resource.
Example:
class UsersController < ApplicationController
def show; end
endThe above action will be denied because no authorization rule has been applied.
RailsIAM will raise an authorization rule error to prevent accidentally exposing unprotected endpoints.
If your application follows the standard resource:action permission convention, you can use:
class UsersController < ApplicationController
authorize_resource
def show; end
def send_invite; end
endThe required permission is automatically inferred as: user:show user:send_invite (singular).
No additional authorize rule is required.
When using explicit authorization: authorize permissions: "user:view" the permission name in the authorization rule does not need to be identical to the permission description or database record name.
RailsIAM evaluates the permission code provided by the rule against the user's effective permissions.
However, when using authorize_resource, the inferred permission name must follow the convention: UsersController#show is always inferred as user:show.
The Permission model provides a description field.
When creating permissions, it is recommended to store additional context such as the related API endpoint.
RailsIam::Permission.create!( code: "user:show", description: "GET /api/users/:id")This makes it easier to understand where a permission is used and helps prevent unused or forgotten permissions as the application grows.
For better maintainability, you can document the required permission directly above controller actions.
class UsersController < ApplicationController
authorize_resource
# permission: user:show
def show
end
endKeeping permission references close to the endpoint makes authorization rules easier to discover during development and code review.