DEV Community

Mohamed Achaq
Mohamed Achaq

Posted on

Dead simple Access Control with iamjs

As web applications evolve and grow in complexity, the need for advanced security measures also increases. One particular area of focus is the authorization process, which controls who has access to what within an application. But how do you manage this efficiently?

Introducing iamjs, a robust, performance-driven authorization library engineered to streamline your authorization process through a schema-based approach. Composed of several modules such as @iamjs/core, @iamjs/express, @iamjs/koa, @iamjs/next and @iamjs/react, iamjs has something to accommodate a variety of programming environments.

🎉 Features:

iamjs is a feature-rich access control library that offers high performance and a wide range of capabilities. It revolutionizes the authorization process by empowering developers with efficient tools that simplify their workflow. Here are some key features that make iamjs stand out:

  • 🔑 Role-based Access Control: iamjs allows users to effortlessly assign roles with fine-grained permissions, enabling precise control over resource access and security.
  • 🌐 Versatile Programming Environment Support: With iamjs, you can seamlessly integrate access control into various programming environments, including Express.js, Koa.js, Next.js, and React. This flexibility ensures that you can use iamjs across your entire tech stack.
  • 📝 Customizable Permissions: Tailoring access permissions to your specific requirements is a breeze with iamjs. You have the freedom to define and configure custom permissions for individual resources and actions, empowering you to establish a robust security model.
  • 🎯 Type safety: iamjs is built with TypeScript, ensuring type safety and reducing the likelihood of errors in your code.
  • 🧪 Effortless Testing: iamjs is equipped with comprehensive testing tools that simplify the process of validating your authorization logic. This feature ensures that your application remains secure throughout development, giving you peace of mind.

🧠 @iamjs/core:

The @iamjs/core module provides the core logic for the library, empowering developers with a toolkit that makes building access control systems a simpler task.

For instance, let's begin by defining two roles, user and admin, both equipped with specific permissions for different actions and resources:

import { AuthManager, Role, Schema } from "@iamjs/core";

const roles = {
  user: new Role({
    name: "user",
    config: {
      user: {
        scopes: "-r--l",
        custom: {
          ban: false,
        },
      },
      post: {
        scopes: "crudl",
        custom: {
          publish: true,
        },
      },
    },
  }),
  admin: new Role({
    name: "admin",
    config: {
      user: {
        scopes: "crudl",
        custom: {
          ban: true,
        },
      },
      post: {
        scopes: "crudl",
        custom: {
          publish: true,
        },
      },
    },
  }),
};

const schema = new Schema(roles);
const auth = new AuthManager(schema);
Enter fullscreen mode Exit fullscreen mode

With the roles and authorization schema defined, you can now utilize the AuthManager class to perform authorization checks effortlessly:

const isAdminAuthorized = auth.authorize({
  role: "admin",
  actions: ["ban", "create"],
  resources: "user",
}); // true

const isUserAuthorized = auth.authorize({
  role: "user",
  actions: ["read", "create"],
  resources: ["post", "user"],
  strict: true,
}); // false
Enter fullscreen mode Exit fullscreen mode

🚀 @iamjs/express:

For Express.js enthusiasts, @iamjs/express provides seamless integration with the Express.js framework. Leveraging this module, you can easily set up middleware for assigning roles and checking authorization, amplifying your application's security layer in just a few lines of code:

import { Role, Schema } from '@iamjs/core';
import { ExpressRoleManager } from '@iamjs/express';
import express from 'express';

const role = new Role({
  name: 'role',
  config: {
    resource1: {
      scopes: 'crudl'
    },
    resource2: {
      scopes: 'cr-dl',
      custom: {
        'create a new user': false
      }
    }
  }
});

const schema = new Schema({
  role
});

const roleManager = new ExpressRoleManager({
  schema: schema,
  onError(_err, _req, res, _next) {
    res.status(403).send('Forbidden');
  },
  onSucess(_req, res, _next) {
    res.status(200).send('Hello World from the success handler!');
  }
});

const app = express();

app.get(
  '/resource1',
  roleManager.check({
    resources: 'resource1', // the resources to be accessed
    actions: ['create', 'update'], // the actions to be performed
    role: 'role',// the role to check
    strict: true // make the checks run in strict mode
    // additionnaly you can construct role from permissions by using the construct option
        construct: true,
        data: async(req){
            return schema.get('role').toObject()
        }
}),
  (_req, res) => {
    res.send('Hello World!');
  }
);

Enter fullscreen mode Exit fullscreen mode

☢️ @iamjs/react:

Finally, weaving this into a React setting, @iamjs/react simplifies checking permissions within your components. It introduces a nifty hook - useAuthorize, allowing you to control component rendering based on user roles:

Import useAuthorize and createSchema from the package

import { Role } from "@iamjs/core";
import { createSchema, useAuthorization } from "@iamjs/react";
import { useEffect, useState } from "react";
Enter fullscreen mode Exit fullscreen mode

Create the initial schema with a default roles

const schema = createSchema({
  user: new Role({
    name: 'user',
    description: 'User role',
    meta: {
      name: 'user'
    },
    config: {
      books: {
        scopes: 'crudl',
        custom: {
          upgrade: true,
          downgrade: false,
          sort: true
        }
      }
    }
  })

Enter fullscreen mode Exit fullscreen mode

Create your own hook to fetch the user's permissions from your backend

const useUser = () => {
  const { build } = useAuthorization(schema);
  const [userRole, setUserRole] = useState(null);

  useEffect(() => {
    // Call the API endpoint to fetch user permissions
    fetch("/permssions")
      .then((response) => response.json())
      .then((data) => {
        // Build the role based on the received permissions
        const builtRole = build(data);
        setUserRole(builtRole);
      })
      .catch((error) => {
        console.error("Error fetching user permissions:", error);
      });
  }, []);

  return userRole;
};
Enter fullscreen mode Exit fullscreen mode

Use your own hook to check the permissions or to check the user's permissions or render components conditionally

const Component = () => {
  const userRole = useUser();

  if (!userRole) {
    return <div>Loading...</div>;
  }

  const { can, Show } = userRole;

  return (
    <div>
      <div>{can("books", "create").toString()}</div>
      <Show resources="books" actions="create">
        <div>Rendered if user has 'create' permission for 'books'</div>
      </Show>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Additionally your use the Show and can methods directly from useAuthorize and use the roles you provided in the schema to render conditionally or check the users permissions

const { can, Show } = useAuthorization(schema);

const canDo = can("user", "books", "create").toString(); // 'true'

<Show role="user" resources="books" actions="create">
  <div>can show</div>
</Show>;
Enter fullscreen mode Exit fullscreen mode

📔Conclusion

iamjs is a powerful and versatile library for access control that simplifies the authorization process using a schema-based approach. With features such as role-based access control, customizable permissions, and support for a variety of programming environments, iamjs provides a comprehensive solution for building secure and robust web applications.

To learn more about iamjs, check out the official documentation here. And if you're interested in contributing to the project, head over to the repository on GitHub.

Top comments (0)