Using CASL and roles with persisted permissions

How do you implement user groups, roles and permissions in a multitenant environment where you have multiple organizations using the same application and each have own users and groups and roles? There are different approaches to the issue and one is to implement Attributes-based access control (ABAC) in addition with roles (RBAC). But what does that actually mean?

In short: you've groups, groups have roles and roles have permission attributes. Well, read forward and I'll explain how to tie things together using CASL: an isomorphic authorization JavaScript library which restricts what resources a given client is allowed to access.

Authorization mechanisms

First let's start with short introduction to authorization: "Authorization is the process of determining which action a user can perform in the system." Authorization mechanism grants proper privileges for a user defined by some role or other factor. Two popular types of authorization are: Role-based access control (RBAC) and Attributes-based access control (ABAC)

Role based access control (RBAC) is a simple and efficient solution to manage user permissions in an organization. We have to create roles for each user group and assign permissions for certain operations to each role. This is commonly used practice with roles suchs as "System admin", "Administrator", "Editor" and "User". The drawbacks of RBAC, especially in a complex system, is that a user can have various roles, which require more time and effort to organize. Also the more roles you create, the more security holes will be born.

Attributes-based access control (ABAC) is an authorization mechanism granting access dynamically based on user characteristics, environment context, action types, and more. Here we can have some action which is permitted to certain user, e.g. "{ action: 'edit', subject: 'Registration' }", "{ action: 'manage', subject: 'User', fields: ['givenName', 'familyName', 'email', 'phone', 'role'] }". For example, a manager can view only the users in their department; an user can not access his project’s information. ABAC is sometimes referred to as claims-based access control (CBAC) or policy-based access control (PBAC). You often see ABAC authorization mechanism in cloud computing services such as AWS and Azure.

The difference between ABAC and RBAC is that ABAC supports Boolean logic, if the user meets this condition he is allowed to do that action. Attribute-based Access Control (ABAC) enables more flexible control of access permissions as you can grant, adjust or revoke permissions instantly.

RBAC is a simple but sufficient solution to manage access in an uncomplicated system. However, it is not flexible and scalable and that is why ABAC becomes an efficient approach in this case. In the contrast, ABAC may be costly to implement. There is no all-in-one solution and it depends on your business requirements. Remember that use the right tool for the right job.

RBAC table structure can be visualized with:

Role basec access control in multitenant system

And you can add attribute based permissions to RBAC model:

Attribute based permissions for roles (CASL)

Using CASL with roles and permissions

CASL is a robust authorization library for JavaScript and including RBAC it also supports ABAC implementation by adding condition expression to the permission. That's exactly what we need in this case where we have a requirement to define different permissions for custom groups: need for roles and groups.

CASL has good resources and shows a great cookbook how to implement roles with persisted permissions. To supplement that you can check Nest.js and CASL example which also shows table structures in different stages.

The code for the following part can be seen in Gist

Table structure

  • Global Users table
  • Users are grouped by tenant in TenantUsers
  • TenantUsers have a role or roles
  • Users can be added to a group
  • Groups and roles are tenant specific
  • Permissions can be added to a role

Table structure in DBDiagram syntax

Next we create the table structure to our code with help of Sequelize ORM which describes the models, relations and associations.

We have the following models for our table structure:

  • models/rolepermissions.js
  • models/role.js
  • models/permission.js
  • models/group.js
  • models/groupuser.js

The Sequelize modeled class looks like this:

const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
class Permission extends Model {
static associate() {}
toJSON() {
return {
id: this.id,
action: this.action,
subject: this.subject,
fields: this.fields,
conditions: this.conditions,
inverted: this.inverted,
system: this.system,
};
}
}
Permission.init(
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
allowNull: false,
},
action: DataTypes.STRING,
subject: DataTypes.STRING,
fields: DataTypes.ARRAY(DataTypes.TEXT),
conditions: DataTypes.JSON,
inverted: DataTypes.BOOLEAN,
system: DataTypes.BOOLEAN,
},
{
sequelize,
modelName: 'Permission',
},
);
return Permission;
};

You can see all the models on the Gist.

Defining CASL permissions to user

In this example we are adding permissions directly to the JWT we are generating and signing so we don't have to fetch user's permissions every time from the database (or cache) when user makes a request. We get the claims.role from the authentication token and it can be e.g. "admin", "user" or whatever you need.

services/token.js is the class where we fetch the permissions for the role and generate the JWT. It fetches the permissions for the user's roles, gets the user groups and those groups' roles and permissions. Then it merges the permissions to be unique and adds them to the JWT.

The JWT looks something like this:

{
"sub": "1234567890",
"role": "user",
"groups": [1, 3],
"permissions": [
{
"action": "read",
"subject": "Users",
"fields": null,
"conditions": {
"id": "${user.tenant}"
}
},
{
"action": "edit",
"subject": "User",
"fields": null,
"conditions": {
"id": "${user.id}",
"TenantId": "${user.tenant}"
}
},
]
}
view raw jwt-sample.json hosted with ❤ by GitHub

Now we have all the permissions for the user in JWT which we can use in our services and routes. The solution isn't optimal as the JWT can grow large if user has lots of permissions.

In the JWT you can see that the conditions attribute has a placeholder. middleware/abilities.js class parses the user JWT which we added permissions in JSON format, converts the condition to CASL’s condition and creates a CASL Ability so that we can add permission checks later on to our actions.

const { AbilityBuilder, Ability } = require('@casl/ability');
const parseCondition = (template, vars) => {
if (!template) {
return null;
}
JSON.parse(JSON.stringify(template), (_, rawValue) => {
if (rawValue[0] !== '$') {
return rawValue;
}
const name = rawValue.slice(2, -1);
const value = get(vars, name);
if (typeof value === 'undefined') {
throw new ReferenceError(`Variable ${name} is not defined`);
}
return value;
});
return null;
};
const defineAbilitiesFor = (jwtToken) => {
const { can: allow, build } = new AbilityBuilder(Ability);
jwtToken.permissions.forEach((item) => {
const parsedConditions = parseCondition(item.conditions, { jwtToken });
allow(item.action, item.subject, item.fields, parsedConditions);
});
return build();
};
module.exports = { defineAbilitiesFor };

Now you can check the permissions in e.g. route.

const ForbiddenOperationError = {
  from: (abilities) => ({
    throwUnlessCan: (...args) => {
      if (!abilities.can(...args)) {
        throw Forbidden();
      }
    },
  }),
};

...
try {
    const abilities = defineAbilitiesFor(jwtToken);
    ForbiddenOperationError.from(abilities).throwUnlessCan('read', 'Users');
    const users = await UserService.getAll(user.tenant);
    return res.send({ entities: users });
} catch (e) {
    return next(e);
}
...

Summary

Implementing authorization is an important part of software system and you've several approaches to solve it. RBAC is a simple but sufficient solution to manage access in an uncomplicated system. ABAC in the other hand is flexible and scalable approach but may be costly to implement. There is no all-in-one solution and it depends on your business requirements.

Using CASL with roles, groups and permissions makes the handling of the authorization side of things easier but as you can see from the database diagram the overal complexity of the system is still quite high and we're not even touched the user interface of managing the roles, groups and permissions.

Short notes on tech 15/2022

Week 15 of 2022

Before leaving for a short Easter holiday here are some links to go through.

Security

Top10 CI/CD Security Risks

Automagically Auditing GitHub (Actions) Security using OpenSSF Scorecards "How to use the OpenSSF Scorecards GitHub Action to audit your GitHub and GitHub Actions configuration, and a breakdown of some of the issues raised by it." (from Cloud Security Reading List)

Software development

Please put units in names
"There is one code readability trap that is easy to avoid once you are aware of it, yet the trap is pervasive: omitting units." (from Hacker Newsletter)

The Catalog of Design Patterns
Creational, Structural and Behavioral Patterns.

Java Development on an Apple M1 – A One Year Review
"Initial pitfalls when working with the Apple M1 and a collection of valuable tricks and workarounds for developing and testing Java applications." (from Hacker Newsletter)

An up-to-date guide on running Java applications in Docker containers
(from DevOps weekly)

Tools

A list of new(ish) command line tools
"Like ripgrep and fd and fzf and exa and bat."

Work

Maybe you should do less 'work'

Short notes on tech 13/2022

Learning

Personal Goal Setting Playbook
"Setting personal goals can be used in many contexts to help people achieve tasks, objectives or improvements of any kind, big or small."

Design

A Designer’s Guide to Documenting Accessibility & User Interactions

Understanding Figma’s interactive components feature
(from WDRL)

Backend

How to design better APIs
15 language-agnostic, actionable tips on REST API design. (from WDRL)

PHP: The Right Way
"An easy-to-read, quick reference for modern coding standards in PHP, trying to fight all the outdated and partially wrong solutions found on the web." (from WDRL)

Database

Postgres Auditing in 150 lines of SQL
Or you can use pgAudit. (from Hacker Newsletter)

Testing

How to test if there is any element outside the viewport with Cypress
"How to use Cypress to create an automated test that checks if there is an element out of the viewport." (from CSS Weekly)

Tools

Healthchecks
"Healthcheck is a dedicated monitoring and alerting system for cron, with a nice looking dashboard and various alerting options." (from DevOps Weekly #585)

Who watches the watchers?
"A look at using a dead-man's-switch to monitor other monitoring systems, with code examples for implementing on AWS with Prometheus and PagerDuty." (from DevOps Weekly #586)

Learning secure code by identifying vulnerable code and solutions

The DevOps Conference was held this week and on the Expo there were companies showing their services. One of those was Secure Code Warrior which provides a learning platform for teaching developers the skills they need to produce secure code. Last year I wrote about their bootcamp but now it was time to participate in their "The DEVOPS Secure Coding Tournament!"

Secure Coding Tournament

The DevOps Conference Secure Coding Tournament

Identifying and fixing vulnerable code is an important skill in software development and there are different ways to enhance your skills. During the The DevOps Conference Secure Code Warrior organized the The DEVOPS Secure Coding Tournament in their learning platform which allowed you to improve your secure coding skills.

"The tournament allows you to compete against the other participants in a series of vulnerable code challenges that ask you to identify a problem, locate insecure code, and fix a vulnerability."

The challenges were based on the OWASP Top 10 and there were good explanations for the vulnerabilities and solutions to get better understanding of the underlying problems. You get to use your preferred software language such as JavaScript, Java EE / Spring, C#, Go, Ruby on Rails, Python Django & Flask, Scala Play, Node.JS, React, and both iOS and Android development languages.

I chose to use JavaScript and Node.js (Express) as it's the recent language I've used with different libraries and concepts. From previous experience from Secure Coding Bootcamp it helps to know not just the chosen language and framework but also other libraries from the ecosystem. For example with Node.js (Express) there were questions relating to use of Handlebars, LDAP, Mongoose, XML, etc. But most of the challenges were logical to deduce even if you didn't know e.g. XPath.

The missions were presented as a world map and there was total of 8 levels to compete. The usage of the map and potentical attackers was a bit confusing in my mind but might fascinate some.

Challenge map

In each one of the mission there were 5 different challenges with locating vulnerability by selecting one or more code rows or identifying what kind of vulnerability was shown. You got 3 lives to get it right. Then you got four possible solutions to identify which would fix the issue.

Locate vulnerability
Identify vulnerability

After you either got it right or not, the solution was shown with short explanation to teach you more about it.

Identify the correct solution

Tournament participants had two days to complete the challenges but I noticed the tournament on the last day around four hours before it ended. Fortunately it was said that it should took around two or three hours to complete.

I've to admit that at first I wasn't so sure if I would do more than a few challenges regarding the timeframe for completing all of them but it was actually quite addictive to see how well you could identify the vulnerable parts of the code and what the solutions would be although some of the libraries were not so familiar. And seeing that I did quite well on the leaderboard helped to finish it.

The DevOps Conference Secure Coding Tournament leaderboard

On the leaderboard I managed to gather enough points for the third position and won a Secure Code Warrior T-shirt. Yay! The total points available was 12000 so you could say that there was a lot of points (easily) missed.

The overall experience of the tournament was great and it was fun. There were several challenges which weren't quite clear if you didn't know the used library and couple of solutions were tricky as although the solution was "right" the other solution was better (in terms of e.g. better algorithm, etc.).

I've now done two Secure Code Warriors' OWASP Top-10 learning challenges (Bootcamp and this tournament) and can say that although they don't actually teach you to write secure code, they teach you to read code and identify vulnerable parts. They also have more practical and interactive approaches to build secure coding skills systematically but I haven't tried those missions.

Short notes on tech 10/2022

Week 10 of 2022

Web development

The State of JavaScript 2021 Survey Results
"69% use TypeScript; React held the top spot for 6 years; Vue.js is on track to overtake Angular as the second place framework; 2021 has been the year of Vite with 98% satisfaction." (from WDW)

The baseline for web development in 2022
"Now that Internet Explorer seems to die really in June this year, so now we should focus on low-spec Android devices, older Safari versions or slow networks." (from WDRL)

Caching Header Best Practices
"Understanding caching is still one of the harder parts of the web and often disregarded." (from WDRL)

Containers

Buildpacks vs Jib vs Dockerfile: Comparing containerization methods
(from DevOps weekly)

Just say no to :latest
Good reminder why :latest is bad practice.

Learning

Series: Unpacking Interview Questions
"A series sharing some of the questions I use when I interview for technical roles. I’ll unpack the question, when to ask it, and how to evaluate answers."

Something different

How Ikea tricks you into buying more stuff

Short notes on tech 7/2022

Short notes on tech, week 7 of 2022

Software development

Frontend Predictions for 2022
The return of micro-frontends, functional JavaScript & the death of Jamstack as we know it. (from Web Design Weekly)

Cloud

AWS Elastic Kubernetes Service (EKS) Review
"If you are considering going with EKS, understand you are going to need to spend a lot of time reading before you touch anything. You need to make hard-to-undo architectural decisions early in the setup process." tl;dr; "If I were a very small company new to AWS I wouldn't touch this with a ten foot pole."

Tools

An Overview of Docker Desktop Alternatives
tl;dr; "minikube, microk8s, and podman". But "Is it really worth your team's time to deal with an alternative stack?"

FalsiScan: Make it look like a PDF has been hand signed and scanned

Professional life

Career Advice Nobody Gave Me: Never Ignore a Recruiter
tl;dr; Good template for replying to recruiters. Or just use "Hey __. Before we move forward, can you provide me with the company name, a job description, and the expected compensation."

Web Design

Atomic Design Methodology
Methodology to craft interface design systems: "Atoms, molecules, organisms, templates, and pages.

Component Driven User Interfaces
"The development and design practice of building user interfaces with modular components. UIs are built from the “bottom up” starting with basic components then progressively combined to assemble screens."

Short notes on tech 5/2022

Software development

How to tame the devDependencies of your project?
tl;dr; Use mrm.

trpc
"tRPC allows you to easily build & consume fully typesafe APIs, without schemas or code generation."

Cypress vs Selenium vs Playwright vs Puppeteer speed comparison
tl;dr; Playwright is faster vs. Cypress. There's a good thread of Playwright vs. Puppeteer and about Cypress in the side on Hacker News.

Learning

How not to learn TypeScript
"Some mistakes people do when getting started with TypeScript." (from WDW)

Stories from the field

How I Got Pwned by My Cloud Costs
Troy Hunt keeps "Have I Been Pwned" service in Azure and is experienced with cloud but things doesn't always go like planned. Good story of setting safe guards. (from hackernewsletter)

Tools

CopyChar
"Find and copy special characters to your clipboard." (from WDW)

Recruiting

7 front-end interview processes I did in December 2021
"Several lessons and what front-end interviewing looks like today. Useful for those in search of a new job and teams who are looking to hire." (from WDW)

Short notes on tech 2/2022

Week 2 of 2022

JavaScript for impatient programmers
"This book makes JavaScript less challenging to learn for newcomers, by offering a modern view that is as consistent as possible."

Software architecture patterns
Take a deep dive into several common software architecture patterns.

Checklist Design
A collection of the best design practices. (from Web Design Weekly)

How to mentor software engineers
(from Hacker Newsletter)

Hacker laws
Laws, Theories, Principles and Patterns that developers will find useful. (from Hacker Newsletter)

Documentation Guide
"Collective wisdom of the Write the Docs community around best practices for creating software documentation."

misbrands
"The world's most hated IT stickers"

Careen ladders
For a quick look what the career ladder could look like it's worth to check Rent the Runway (spreadsheet) which takes a fun D&D inspired Dex/Str/Wis/Cha stats based evaluation, corresponding to technical skill, productivity, impact, and communication/leadership. Management track is also included, with more focus on architecture, hiring, organizational skills, and leadership/salesmanship.

Notes from Microsoft Ignite Azure Developer Challenge

Microsoft Azure cloud computing service has grown steadily to challenge Amazon Web Services and Google Cloud Platform but until now I hadn't had a change to try it and see how it compares to other platforms I've used. So when I came across the Microsoft Ignite: Cloud Skills Challenge November 2021 I was sold and took the opportunity to go through one of the available challenges: Azure Developer Challenge. Here are my short notes about learning minor part of Azure.

The Azure Developer Challenge was for developers interested in designing, building, testing, and maintaining cloud applications and services on Microsoft Azure. Each challenge was based on a collection of Microsoft Learn modules. If you completed your challenge before it ended, you got one free Microsoft Certification exam like "AZ-204: Developing Solutions for Microsoft Azure".

Microsoft Ignite: Azure Developer Challenge

"This challenge is for developers interested in designing, building, testing, and maintaining cloud applications and services on Microsoft Azure."

Microsoft Ignite

The Azure Developer Challenge consisted of following products in Azure:

  • Azure App Service
  • Azure Functions
  • Azure Cosmos DB
  • Azure Blob storage
  • Virtual machines in Azure
  • Azure Resource Manager templates
  • Azure Container Registry
  • Azure Service Bus
  • Azure Queue storage
  • Azure Event Hubs
  • Event Grid

Learning to use those different products were done by different exercises which showed you how to do things and checked that you had done it correctly. The exercises used Azure portal where the Learn module gave you free learning environment to use. Towards the end I got my free development environment credits used for the day and had to skip some of the practicalities.

Exercise with Sandbox

After going through the introduction to different parts of Azure the Learn module practically teached you to use Azure Functions. And not much more. With Azure Functions the exercises teached to create serverless logic, execute functions with triggers, chain functions and have durable functions. You also learned to develop functions on your local machine. Azure Functions were used i.a. with Cosmos DB, webhooks and for creating an (serverless) API. The last module was about building serverless apps with Go.

In overall the learning experience was nice and the practical exercises forced you to click through the Azure Portal and get the hang of how things work. I was in a bit of a hurry to go through all of the 33 modules which was calculated to take around 21 hours. I think it took me about 10-12 hours.

Now the last step is to actually take the Certification exam. Also as the learning modules for different topics are still available I will maybe go through some more. At least the "Azure Admin Challenge" looked interesting for my purposes.

Azure Portal with Console and VS Code

Short notes on tech 50/2021

Week 50 of 2021

Developer Tools secrets that shouldn’t be secrets
Write-up of a talk at CityJS covering i.a. console.log and VS Code. (from Web Design Weekly)

2021 Design Tools Survey
Overview of the most used design tools during 2021

Meet The Man Who Shoots At Birds All Day To Keep Them Off A Toxic Pit
"If migrating species land on the Berkeley Pit for more than a few hours, they get cooked from the inside out. Now, miners use a rifle, drones, and lasers to scare the birds away."