CASL Documentation

repository·master·Indexed 27 days ago

https://github.com/stalniy/casl

An isomorphic authorization library for JavaScript to define, manage, and share permissions across the frontend, backend, and database. Includes core functionality via @casl/ability and official integrations for Angular (@casl/angular), React (@casl/react), Vue (@casl/vue), and MongoDB/Mongoose (@casl/mongoose).

Tokens
34.1K
Snippets
88
Records
188
Agent score
91%

What's inside CASL

  1. Overview of CASL

    master
    CASL is an isomorphic authorization JavaScript library used to restrict access to resources. It is designed to be incrementally adoptable, scaling from simple claim-based authorization to complex subject and attribute-based authorization. It can be used on both the frontend and backend, making it easy to share permissions across UI components, API services, and database queries.
  2. Understand the CASL Cookbook vs the Guide

    master

    The CASL documentation is split into two distinct styles to serve different needs:

    • The Guide: A narrative-driven introduction that builds knowledge incrementally. It uses simple examples to explain core features and assumes intermediate ES2015 JavaScript knowledge.
    • The Cookbook: A collection of standalone recipes focused on specific, complex use cases. Recipes are deeper, more complex, and frequently use TypeScript, frontend/backend frameworks, and real-world scenarios. Each recipe is self-contained and does not assume knowledge from previous sections.

    Prerequisites for Cookbook recipes: A basic understanding of JavaScript, npm/yarn, CommonJS, ES import/export, and Node.js module resolution is expected. Depending on the specific recipe, familiarity with certain frontend or backend libraries may be required.

  3. Configure the deprecated Accessible Records plugin

    master

    The accessibleRecordsPlugin adds accessibleBy methods to Mongoose queries and model statics. Note: This plugin is deprecated. The recommended approach is to use the accessibleBy helper function instead.

    // Global registration
    const { accessibleRecordsPlugin } = require('@casl/mongoose');
    const mongoose = require('mongoose');
    
    mongoose.plugin(accessibleRecordsPlugin);
    
    // OR Local registration on a specific schema
    const mongoose = require('mongoose')
    const { accessibleRecordsPlugin } = require('@casl/mongoose')
    
    const Post = new mongoose.Schema({
      title: String,
      author: String
    })
    
    Post.plugin(accessibleRecordsPlugin)
    
    module.exports = mongoose.model('Post', Post)
  4. Use ForbiddenError to provide reasons for denied permissions

    master

    You can attach a reason to an inverted rule using the .because() method. When a permission check fails, you can catch a ForbiddenError to retrieve this specific explanation.

    import { defineAbility, ForbiddenError } from '@casl/ability';
    
    export default defineAbility((can, cannot) => {
      can('read', 'all');
      cannot('read', 'all', { private: true })
        .because('You are not allowed to read private information');
    });
    
    // Usage
    import ability from './defineAbility';
    
    try {
      ForbiddenError.from(ability).throwUnlessCan('read', { private: true });
    } catch (error) {
      if (error instanceof ForbiddenError) {
        console.log(error.message); // "You are not allowed to read private information"
      }
    }
  5. Distinguish between defining and checking permissions using custom names

    master

    To avoid confusion between the can/cannot methods used to define rules and the ability.can() method used to check permissions, you can use object destructuring to rename the rule-definition functions. This makes it explicitly clear whether you are currently in a definition context (using names like allow and forbid) or a checking context (using ability.can()).

    You can apply this pattern using either defineAbility or the AbilityBuilder class.

    import { defineAbility } from '@casl/ability';
    
    // define abilities using explicit names
    const ability = defineAbility((allow, forbid) => {
      allow('read', 'Post');
      forbid('read', 'Post', { private: true });
    });
    
    // check abilities using the standard can method
    ability.can('read', 'Post');
  6. Implement logical AND/OR/NOT using CASL rules

    master

    CASL does not include logical operators like $and, $or, $nor, or $not in its condition language. Instead, you achieve this logic through rule composition:

    • AND: Combine multiple fields within a single condition object, or use multiple can rules. All properties in a single condition object are checked with AND logic.
    • OR: Define multiple can rules for the same action and subject. If any rule matches, the permission is granted.
    • NOT: Use cannot rules to negate permissions.

    Note: $nor cannot be natively reproduced with standard rules; if required, you must customize the ability.

  7. Configure TypeScript for CASL Vue

    master

    To get full type safety with your custom AppAbility, you can use several approaches:

    1. Augment Vue types: For global properties like $can and $ability to work with TypeScript, create a shim file.
    2. Composition API: Pass your AppAbility type to the useAbility hook: useAbility<AppAbility>().
    3. Options API: Cast the ABILITY_TOKEN to your custom InjectionKey<AppAbility> to ensure injected instances are correctly typed.
    // Augmenting Vue types (shims-ability.d.ts)
    import { AppAbility } from './AppAbility'
    
    declare module 'vue' {
      interface ComponentCustomProperties {
        $ability: AppAbility;
        $can(this: this, ...args: Parameters<this['$ability']['can']>): boolean;
      }
    }
    // Options API with custom token
    import { InjectionKey } from 'vue';
    import { ABILITY_TOKEN } from '@casl/vue';
    
    export const TOKEN = ABILITY_TOKEN as InjectionKey<AppAbility>;
  8. When to avoid roles with persisted permissions

    master

    The pattern of persisting permissions in a database (roles with persisted permissions) should be avoided in the following scenarios:

    1. Static Permissions: If you have a predefined set of permissions for every role that is very unlikely to change.
    2. Project Uncertainty: If you are starting a new project and are unsure whether you will actually need to dynamically configure permissions.
  9. Configure AppModule for CASL

    master

    To use CASL pipes or services in your templates, import the necessary modules and provide the Ability instance in your AppModule. You typically use createMongoAbility() to initialize the ability instance.

    import { NgModule } from '@angular/core';
    import { AblePipe } from '@casl/angular';
    import { createMongoAbility, Ability } from '@casl/ability';
    
    @NgModule({
      imports: [
        // other modules
        AblePipe
      ],
      providers: [
        { provide: Ability, useValue: createMongoAbility() }
      ]
    })
    export class AppModule {}