How to manage user permissions in Aurelia
In this article I will show how to integrate authorization based on CASL in an Aurelia application.
If this is the first time you’ve heard about CASL, please read: “What is CASL?”.
I prepared a simple blog application based on Aurelia where users can login, logout, leave comments and manage their own posts. The model layer is built on top of js-data ORM and its js-data-localstorage adapter.
Now lets define who can do what:
- non authenticated users can only read posts and comments
- authenticated users can do the same and additionally manage their own posts
CASL allows to translate such permissions into DSL with help of
can function allows you to define which action user can do on a particular resource. All
can are logically OR-ed, so the order matters. See Defininng Abilities in the official documentation for details.
Registering Ability in Aurelia
First of all, lets add our abilities in
configureAbility is part of a custom Aurealia feature and run during application bootstrap, thus accepts instance of
FrameworkConfiguration which has a reference to aurelia dependency injection
container. I used this reference to dynamically register an empty
Now I need an instance of currently logged in
User. To get a reference to that object, I need somehow to hook into session management logic. As
Session was defined as js-data
Mapper, I can use
DataStore to listen to
Session management events.
As you can see from the code above I listen to 3 events of
DataStore that allows me to understand when
Session is created, destroyed or found and update user abilities accordingly.
Now everything seems ok, but if you try to check abilities on models, you will always receive
false because CASL can’t detect model’s type. We need to teach it by specifying
Now all good! Lets move on to actual integration with Aurelia templates.
There are 2 ways of how we can add ability checks into Aurelia templates:
Update: thanks to new Signalable Value Converters it’s possible to simply integration and remove binding behavior. So, the resulting code will look this (check updated source code on github):
<div if.bind="'Post' | can: 'create'">
<a route-href="route: newPost">Add Post</a>
Update 2: now there is a separate package which allows to integrate CASL seamlessly into any Aurelia application. See updated repository for an example
I’m going to implement both and later you will understand why. It’s easy to generate binding behavior and value converter using aurelia cli (specify
can as a name for both):
~/projects/blog$ au generate value-converter
~/projects/blog$ au generate binding-behavior
Generated classes I’m going to move into single
src/pipes/can.js because all this is basically a one thing.
In value converter, we just need to inject
Ability instance and use its
Pay attention how frameworkless code looks! This is possible thanks to Aurelia convetions.
Now lets make this converter to be global. To do this I just need to add a line of code into
.globalResources('./pipes/can') // <-- added
This allows me to bind ability checks in any template with help of
But when you test it, it doesn’t work, why? Because Aurelia tracks changes based on used expressions in the binding. The current
if binding has 2 static strings and has no knowledge about
rules array of
Ability instance. Thus don’t know that binding needs to be updated when rules are changed.
To solve this issue we could update binding with help of signal binding behavior and emit signal each time abilities are changed(basically each time user do login/logout):
It also requires a change in
Now everything works as expected but it’s a bit tedious to write
signal: ability-changed in every binding where we use
can. It’d better to write custom
can binding behavior which hides this complexity. To do that I will inject
SignalBindingBehavior which re-evaluates binding automatically when
ability-changed signal is received. Also I need automatically wrap all expression in
can value converter:
Using this binding behavior, templates looks simpler:
That’s it! 🙂
Now you can add
if bindings where you want and they will react to ability changes. The complete example you can find in master branch of Github project. Try to login under different users and see it by yourself.
If you like this article, please consider recommending it. 👏