FullStack Labs

Please Upgrade Your Browser.

Unfortunately, Internet Explorer is an outdated browser and we do not currently support it. To have the best browsing experience, please upgrade to Microsoft Edge, Google Chrome or Safari.
Welcome to FullStack Labs. We use cookies to enable better features on our website. Cookies help us tailor content to your interests and locations and provide many other benefits of the site. For more information, please see our Cookies Policy and Privacy Policy.

Role-Based User Authorization in JavaScript with CASL

Written by 
Decker Brower
Senior Software Engineer
Role-Based User Authorization in JavaScript with CASL
blog post background
Recent Posts
Getting Started with Single Sign-On
Choosing the Right State Management Tool for Your React Apps
Accessibility in Focus: The Intersection of Screen Readers, Keyboards, and QA Testing

I was recently tasked with implementing role-based user authorization in a client's application and after some research, decided to try a new library that I had never worked with before called CASL.

Table of contents

I enjoyed working with it and would like to show you just how easy it is to set up and start using it in your projects.

What is CASL?


CASL (pronounced /ˈkæsəl/, like castle) is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the Ability class) and not duplicated across UI components, API services, and database queries.

CASL, because of its isomorphic nature, can be used together with any data layer, any HTTP framework, and even any frontend framework.

Let's Do This.

For this example, we'll be defining two roles for our users, Admin and Pleb.

An Admin user will be able to "manage" posts. This means that they can perform any action on the given resource.

A Pleb will be able to read and update posts, but not create or delete them.

Server Side

First, we define a function to create and return an ability for each role.

-- CODE language-javascript keep-markup --
/* roles.js */

import { AbilityBuilder, Ability } from '@casl/ability'

exportconst PERMISSIONS = {  
  MANAGE: 'manage',  
  CREATE: 'create',  
  READ: 'read',  
  UPDATE: 'update',  
  DELETE: 'delete'

exportconst MODEL_NAMES = { POST: 'Post' }

exportfunctiondefineAbilitiesForAdmin() {  
  const { rules, can } = AbilityBuilder.extract()  

  returnnew Ability(rules)

exportfunctiondefineAbilitiesForPleb() {  
  const { rules, can, cannot } = AbilityBuilder.extract()  
  can(PERMISSIONS.MANAGE, MODEL_NAMES.POST) /* start with full permissions */  

    .because('Only Admins can create Posts')  

    .because('Only Admins can delete Posts')  

  returnnew Ability(rules)


We could pass some data to our functions to conditionally add/remove abilities for the role but let's keep it simple for now.

Next, let's add a function to fetch the abilities for a user based upon their role.

We'll define a default role with no permissions in case the user doesn't have a role set yet.

-- CODE language-javascript keep-markup --
/* utils.js */

import { AbilityBuilder, Ability } from '@casl/ability'
import {  
from './roles'

const USER_ROLES = {  
  ADMIN: 1,  
  PLEB: 2

const DEFAULT_ABILITIES = new Ability() //defaults to no permissions

exportfunctiongetRoleAbilityForUser({ user = {} }) {  
  let ability  
  switch (user.role) {    
    case USER_ROLES.ADMIN:      
      ability = defineAbilitiesForAdmin()      
      ability = defineAbilitiesForPleb()      
      ability = DEFAULT_ABILITIES      
  return ability


Great! Now we have both our Admin and Pleb roles defined and we have a function to fetch the abilities for a given user.

Let's put them to use by checking if the user has permission to create a post in a controller action.

CASL has a nice ForbiddenError helper that we can use to throw an error with a helpful message if the user doesn't have the correct permissions.

-- CODE language-javascript keep-markup --
/* PostController.js */  

import { ForbiddenError } from '@casl/ability'
import { PERMISSIONS, MODEL_NAMES } from './roles'
import { getRoleAbilityForUser } from './utils'

classPostController {  
  async createPost(req, res) {    
    const { user = {} } = req    

    try {      
      const ability = getRoleAbilityForUser({ user })      
        .throwUnlessCan(PERMISSIONS.CREATE, MODEL_NAMES.POST)      
      //create the post!    
    } catch (error) {      
      console.log(error.message) /* "Only Admins can create Posts" */   

exportdefault PostController

Finally, let's add a controller action to fetch the ability for the current user so we can check their permissions client-side.

CASL has some helper functions to pack/unpack the rules for the abilities to reduce the size for storage in a jwt token. We'll skip the token part for now but keep the optimization.

-- CODE language-javascript keep-markup --
/* controller.js */

import { packRules } from '@casl/ability/extra'
import { getRoleAbilityForUser } from './utils'

classUserController {  
  getUserRoleAbility(req, res) {    
    const { user = {} } = req    

    try {      
      const ability = getRoleAbilityForUser({ user })      
      const packedRules = packRules(ability.rules)      
      return res.status(200).send(packedRules)    
    } catch (error) {  
      /* handle the error  */    

exportdefault UserController


We'll use React in this example but we can just as easily use CASL by itself as we did on the server.

-- CODE language-javascript keep-markup --
import { Ability } from '@casl/ability'
const ability = new Ability() /* defaults to no permissions  */
ability.can('create', 'Post') /* returns false */

Notice that we are using the same @casl/ability packages on the client as we did on the server!

There are also complementary libraries for other major frontend frameworks which makes integration of CASL super easy in your application.

First, let's add a hook to define and update our users' abilities.

-- CODE language-javascript keep-markup --
/* useAbility.js  */

import { Ability } from '@casl/ability'
import { unpackRules } from '@casl/ability/extra'
import { UserApi } from './api'

const userAbility = new Ability()

exportfunctionuseAbility() {  
  asyncfunctionfetchUserAbility() {    
    try {      
      const { data: packedRules } = await UserApi.fetchAbility()      
    } catch (error) {      
      /* handle the error  */

    return userAbility  

  return {    


Now, let's take a look at how to conditionally render a button if the user has the correct permissions to create a new post.

-- CODE language-javascript keep-markup --
/* CreatePostButton.js  */

import { Can } from '@casl/react'
import { useAbility, usePost } from './hooks'

functionCreatePostButton() {  
  const { fetchUserAbility, userAbility } = useAbility()  
  const { createPost } = usePost()  
  const [fetched, setFetched] = useState(false)  

  useEffect(() => {    
    if (!fetched) {      
  }, [fetched, setFetched, fetchUserAbility])  

  /* shown for admins, hidden by default and for plebs  */
return (    <
    Can I="create" a="Post" ability={userAbility}>
      <button onclick="{createPost}">Create Post</button>


That's it! Simple and very non-intimidating, right?

Check out the CASL documentation for a deeper dive.


Thanks for reading!

Using techniques like what is listed above, we have had the opportunity to address our clients’ concerns and they love it! If you are interested in joining our team, please visit our Careers page.

At FullStack Labs, we are consistently asked for ways to speed up time-to-market and improve project maintainability. We pride ourselves on our ability to push the capabilities of these cutting-edge libraries. Interested in learning more about speeding up development time on your next form project, or improving an existing codebase with forms? Contact us.

Decker Brower
Written by
Decker Brower
Decker Brower

As a Senior Software Engineer at FullStack Labs I'm focused on building custom software applications using a variety of technologies, including React.js, Ember.js, Ruby on Rails and Node.js. Over the course of my career I've held a variety of roles, including Senior Software Developer at Techmonster, and Web Developer at Markit On Demand.

People having a meeting on a glass room.
Join Our Team
We are looking for developers committed to writing the best code and deploying flawless apps in a small team setting.
view careers
Desktop screens shown as slices from a top angle.
Case Studies
It's not only about results, it's also about how we helped our clients get there and achieve their goals.
view case studies
Phone with an app screen on it.
Our Playbook
Our step-by-step process for designing, developing, and maintaining exceptional custom software solutions.
VIEW OUR playbook
FullStack Labs Icon

Let's Talk!

We’d love to learn more about your project.
Engagements start at $75,000.