Role Based Access Control (RBAC
) gives you
a human-friendly way of controlling access
to specific data/features in your App(s).
You want an easy way to restrict access to features for your Elixir/Phoenix App
based on a sane model of roles.
RBAC
lets you easily manage roles and permissions in any application
and see at a glance exactly which permissions a person has in the system.
It reduces complexity over traditional
Access Control List (ACL) based permissions systems.
The purpose of RBAC
is to provide a framework
for application administrators and developers
to manage the permissions assigned to the people using the App(s).
Anyone who is interested in developing secure applications
used by many people with differing needs and permissions
should learn about RBAC
.
Install by adding rbac
to your list of dependencies in mix.exs
:
def deps do
[
{:rbac, "~> 0.6.0"}
]
end
In order to use RBAC
you need to initialize
the in-memory cache with a list of roles.
If you prefer to manage your own list of roles you can simply supply your own list of roles e.g:
roles = [%{id: 1, name: "admin"}, %{id: 2, name: "subscriber"}]
RBAC.insert_roles_into_ets_cache(roles)
To initialize the list of roles once (at boot) for your Phoenix App,
open the application.ex
file of your project
and locate the def start(_type, _args) do
definition, e.g:
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Start the Ecto repository
App.Repo,
# Start the endpoint when the application starts
{Phoenix.PubSub, name: App.PubSub},
AppWeb.Endpoint
# Starts a worker by calling: Auth.Worker.start_link(arg)
# {Auth.Worker, arg},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: App.Supervisor]
Supervisor.start_link(children, opts)
end
Add the following code at the top of the start/2
function definition:
# initialize RBAC Roles Cache:
roles = [%{id: 1, name: "admin"}, %{id: 2, name: "subscriber"}]
RBAC.insert_roles_into_ets_cache(roles)
RBAC
is independent from our
auth
App
and it's corresponding helper library
auth_plug
.
However if you want a ready-made list of universally applicable roles
and an easy way to manage and create custom roles for your App,
auth
has you covered:
https://dwylauth.herokuapp.com
Once you have exported your
AUTH_API_KEY
Environment Variable
following these instructions:
https://github.com/dwyl/auth_plug#2-get-your-auth_api_key-
You can source your list of roles and initialize it with the following code:
# initialize RBAC Roles Cache:
RBAC.init_roles_cache(
"https://dwylauth.herokuapp.com",
AuthPlug.Token.client_id()
)
AuthPlug.Token.client_id()
expects the AUTH_API_KEY
Environment Variable to be set.
Once you have added the initialization code, you can easily check that a person has a required role using the following code:
# role argument as String
RBAC.has_role?([2], "admin")
> true
# role argument as Atom
RBAC.has_role?([2], :admin)
> true
# second argument (role) as Integer
RBAC.has_role?([2], 2)
> true
The first argument is a List
of role ids.
The second argument (role
) can either be
an String
, Atom
orInteger
corresponding to the name
of the role
or the id
respectively.
We prefer using String
because its more developer/maintenance friendly.
We can immediately see which role is required
Or if you want to check that the person has any role in a list of potential roles:
RBAC.has_role_any?([2,4,7], ["admin", "commenter"])
> true
RBAC.has_role_any?([2,4,7], [:admin, :commenter])
> true
If you are using auth_plug
to handle checking auth in your App.
It adds the person
map to the conn.assigns
struct.
That means the person's roles are listed in:
conn.assigns.person.roles
e.g:
%{
app_id: 8,
auth_provider: "github",
email: "[email protected]",
exp: 1631721211,
givenName: "Alex",
id: 772,
roles: "2"
}
For convenience, we allow the first argument
of both has_role/2
and has_role_any?/2
to accept conn
as the first argument:
RBAC.has_role?(conn, "admin")
> true
Check that the person has has any role in a list of potential roles:
RBAC.has_role_any?(conn, ["admin", "commenter"])
> true
We prefer to make our code as declarative and human-friendly as possible,
hence the String
role names.
However both the role-checking functions also accept a list of integers,
corresponding to the role.id
of the required role, e.g:
RBAC.has_role?(conn, 2)
> true
If the person does not have the superadmin
role,
has_role?/2
will return false
RBAC.has_role?(conn, 1)
> false
Or supply a list of integers to has_role_any?/2
if you prefer:
RBAC.has_role_any?(conn, [1,2,3])
> true
You can even mix the type in the list (though we don't recommend it...):
RBAC.has_role_any?(conn, ["admin",2,3])
> true
We recommend picking one, and advise using strings for code legibility. e.g:
RBAC.has_role?(conn, "building_admin")
Is very clear which role is required.
Whereas using an int
(especially for custom roles) is a bit more terse:
RBAC.has_role?(conn, 13)
It requires the developer/code reviewer/maintainer
to either know what the role is,
or look it up in a list.
Stick with String
as your role names in your code.
API/Function reference available at https://hexdocs.pm/rbac.
Each role granted just enough flexibility and permissions to perform the tasks required for their job, this helps enforce the principal of least privilege
The RBAC methodology is based on a set of three principal rules that govern access to systems:
-
Role Assignment: Each transaction or operation can only be carried out if the person has assumed the appropriate role. An operation is defined as any action taken with respect to a system or network object that is protected by RBAC. Roles may be assigned by a separate party or selected by the person attempting to perform the action.
-
Role Authorization: The purpose of role authorization is to ensure that people can only assume a role for which they have been given the appropriate authorization. When a person assumes a role, they must do so with authorization from an administrator.
-
Transaction Authorization: An operation can only be completed if the person attempting to complete the transaction possesses the appropriate role.