DEV Community

Cover image for Understanding how Role-based Access Control works
Oghenevwede Emeni
Oghenevwede Emeni

Posted on

Understanding how Role-based Access Control works

Key Takeaways

  • Role-based and resource-based authorisation entails approving a request in accordance with the roles that have been given to a user or a certain resource.
  • The right to control who can see and access information should belong to the person who created it. Access privileges for each user should be individually customizable by organisations.
  • The best way to explain this resource distribution is as a division of the system's resources among various administrative areas.
  • Helpers can make it easier to develop role-based systems by streamlining the process of granting access depending on the roles associated with each user profile.
  • Roles are given permissions, and maybe granted to people or user groups.

Introduction

RBAC (Role Based Access Control) allows you to use permissions to determine what may be accessed. It adheres to the principle of giving a system rights depending on a user's position within an organisation. It does away with the need to manually or individually control access rights to a whole system or a single resource.
In this article, we'll look at role-based access control, an architecture that lets several users inside a company share resources while limiting each user group's access to data that they are authorised to see. We will explore the idea of resource isolation and system security in relation to role-based authorization logic.
To fully follow this article, we will also require the following:

  1. Go installed, as well as a working knowledge of the language.
  2. A MongoDB account

A RBAC scenario

Let's sketch a picture. You were given the duty of developing a blog application for your company with features like writing blog posts, posting them, editing them, and removing them. Assume that the task's additional instructions asked you to create two user groupsβ€” an editor, and a writer. Specific responsibilities and duties being allocated to each user group. A user who belongs to the writer group, for instance, should only be able to create blog posts, while a user who belongs to the editor group, should only be able to edit.
You can choose to construct the system in a variety of ways. For instance, creating unique apps for every user or creating a single application and limiting access to certain functionalities based on roles. The role-based access control method would be the ideal if you choose the latter choice since it will allow you to construct a system that is more scalable and can support several users because all of the users will be using the same infrastructure and sharing the same resources. The idea is to provide a generic login system that directs users to the appropriate dashboards according to their roles.
MongoDB, Github, and Wix are examples of systems that operate with this access logic. These applications allow you to configure role-based authorization. For instance, WIX offers a variety of user categories, including a billing manager, administrator, site owner, blog owner, and more. These positions each have a different amount of permission. Similar rights may apply to some, such as the ability of an admin user to control billing operations similarly to a billing manager.

What to consider when building RBAC Systems

Before creating RBAC Systems, there are a few aspects to take into account. Things like price, product kind, and business decisions. Following are a handful of them:

  1. What are the system's characteristics? Is this sort of architecture truly necessary for the system?
  2. Who are my system users, and how will I classify them?
  3. What specific features are required by each user, and how will you manage them?
  4. What steps will you take to scale the system when you need to create additional user roles because your system is expanding with even more functionalities?
  5. What kind of infrastructure are you in need of?

Role-based access control Approaches

Several things take place when an access request is made in ant application. The user is initially authenticated in order to determine whether they even have permission to use the platform. The user's type must then be determined. If a user is a billing manager, they should only be able to view the dashboard's billing area; similarly, a blog post editor should only be able to access the various blog entries. Finally, after confirming the sort of user they are, you may grant them access to the information they require.
Modelling the many users that will reside in your system is quite crucial. Determining how each user will be segregated is also crucial. This article will place a lot of emphasis on logical segregation, where user groups use the same infrastructure and are identified by unique types and helper codes.

Role-based access control using the user type logic

In this context, the term "roles" is used to describe a person or set of users who carry out particular tasks within a system. When establishing roles, it's crucial to first list all the possible activities that may be taken in your application before grouping related actions together. Some user responsibilities overlap, thus it would be good practice to be aware of this. You may, for instance, have two roles in your blog application: reader and publisher. The sole right of a reader may be to read blog posts, while the publisher's primary duty may be to publish edited articles, however a publisher may also be permitted to read the blog posts. Therefore, rather than requiring them to register a whole new account in order to have reading access, this access permission may also be defined in his or her role.

Building a simple application that makes use of Role-based Access Control

To further understand how RBAC works, we will build a basic golang application.
To begin, start a new Golang project and connect it to a MongoDB database. Install mongodrivers and the gin package as well. We won't discuss project setup as the focus of this article is to illustrate Role-based Access Control. You'll also need to create your project structure and also create your authentication endpoints using the model structure shown below.

package models
import (
    "time"
    "go.mongodb.org/mongo-driver/bson/primitive"
)
type User struct {
    ID            primitive.ObjectID `bson:"_id"`
    Name          *string            `json:"name" validate:"required,min=4,max=100"`
    Password      *string            `json:"Password" validate:"required,min=8"`
    Email         *string            `json:"email" validate:"email,required"`
    Token         *string            `json:"token"`
    user_type     *string          `json:"user_type" validate:"required,eq=EDITOR|eq=WRITER"`
    Refresh_token *string            `json:"refresh_token"`
    Created_at    time.Time          `json:"created_at"`
    Updated_at    time.Time          `json:"updated_at"`
}
Enter fullscreen mode Exit fullscreen mode

The user model has a field called user_type. The two possible options for this field are WRITER, or EDITOR.
Our go application will include the following features:

  • Creating a blog post (only admins and writers can do this)
  • Edit a blogpost (only editors and writers can do this)

Now to set up our blog model. Our blog model will collect data such as the blog title, content, writer’s id and category.
(blog_model.go)

package models
import (
    "time"
    "go.mongodb.org/mongo-driver/bson/primitive"
)
type Post struct {
    ID            primitive.ObjectID `bson:"_id"`
         Title          *string            `json:"title" validate:"required,min=4,max=100"`
         Category       *string            `json:"category" validate:"required,min=4,max=100"`
         Story          *string            `json:"story" validate:"required,min=4,max=100"`
         Writer_id      *string            `json:"writer_id"`
         Created_at     time.Time          `json:"created_at"`
         Updated_at     time.Time          `json:"updated_at"`
}
Enter fullscreen mode Exit fullscreen mode

After we've finished our models, we'll need to make an auth_helper file. Simply put, our auth_helper file includes the functionality required to manage our various user types. It has two simple functions;

  • CheckUserType() - Which simply checks the user type of the user trying to access a specific resource and;
  • UserUid() - which checks to see the id of that user and then checks to see if it belongs to the user group that is expected. This is a very basic method of dealing with role-based access control. A more sophisticated method of managing role-based access control may entail adding elements like policies to various user groups or even a particular user id. The IAM and roles capabilities in AWS are a perfect illustration of this. Now to create our helper file (auth_helper.go).
Package helper
import (
    "errors"
    "github.com/gin-gonic/gin")

func CheckUserType(c *gin.Context, role string) (err error) {
    userType := c.GetString("user_type")
    err = nil
    if userType != role {
        err = errors.New("You are not authorised to access this!")
        return err
    }
    return err
}
//To match the user type to the id
func UserUid(c *gin.Context, userId string) (err error) {
    userType := c.GetString("user_type")
    uid := c.GetString("uid")
    err = nil

    if userType == "USER" && uid != userId {
        err = errors.New("Unauthorised to access this resource")
        return err
    }
    err = CheckUserType(c, userType)
    return err
}
Enter fullscreen mode Exit fullscreen mode

The CreatePost() function first checks to see if a user belongs to the group WRITER before processing the request. It does this using the CheckUserType() helper function and It throws an error message β€œYou are not authorised to access this!” if the user is not authorised,
(blogpost.go)

func CreatePost() gin.HandlerFunc {
    return func(c *gin.Context) {
        if err := helper.CheckUserType(c, "WRITER"); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        var post models.Post
        defer cancel()

        //validate the request body
        if err := c.BindJSON(&post); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "Status":  http.StatusBadRequest,
                "Message": "error",
                "Data":    map[string]interface{}{"data": err.Error()}})
            return
        }
        if validationErr := validate.Struct(&post); validationErr != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "Status":  http.StatusBadRequest,
                "Message": "error",
                "Data":    map[string]interface{}{"data": validationErr.Error()}})
            return
        }

        newPost := models.Post{
            Id:   primitive.NewObjectID(),
            Title: post.Title,
            Category: post.Category,
            Story: post.Story,
            Writer_id: post.Writer_id,

        }

        result, err := postCollection.InsertOne(ctx, newPost)

        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "Status":  http.StatusBadRequest,
                "Message": "error",
                "Data":    map[string]interface{}{"data": err.Error()}})
            return
        }

        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "Status":  http.StatusInternalServerError,
                "Message": "error",
                "Data":    map[string]interface{}{"data": err.Error()}})
            return
        }

        c.JSON(http.StatusCreated, gin.H{
            "Status":  http.StatusCreated,
            "Message": "success",
            "Data":    map[string]interface{}{"data": result}})
    }
}
Enter fullscreen mode Exit fullscreen mode

Next we will need to create the controller function for our edit blog post function. Only users who belong to the EDITOR group may perform this function.

func EditPost() gin.HandlerFunc {
    return func(c *gin.Context) {
        if err := helper.CheckUserType(c, "EDITOR"); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return}
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
        postId := c.Param("post_id")
        var post models.Post
        defer cancel()
        objId, _ := primitive.ObjectIDFromHex(postId)

        //validate the request body
        if err := c.BindJSON(&post); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "Status":  http.StatusBadRequest,
                "Message": "error",
                "Data":    map[string]interface{}{"data": err.Error()}})
            return }

        //use the validator library to validate required fields
        if validationErr := validate.Struct(&post); validationErr != nil {
            c.JSON(http.StatusBadRequest, gin.H{
                "Status":  http.StatusBadRequest,
                "Message": "error",
                "Data":    map[string]interface{}{"data": validationErr.Error()}})
            return}
        update := bson.M{
            "Title": post.Title,
            "Category": post.Category,
            "Story": post.Story,
            "Writer_id": post.Writer_id
    }
        filterByID := bson.M{"_id": bson.M{"$eq": objId}}
        result, err := postCollection.UpdateOne(ctx, filterByID, bson.M{"$set": update})
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{
                "Status":  http.StatusInternalServerError,
                "Message": "error",
                "Data":    map[string]interface{}{"data": err.Error()}})
            return
        }

        var updatedPost models.Post
        if result.MatchedCount == 1 {
            err := postCollection.FindOne(ctx, filterByID).Decode(&updatedPost)
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{
                    "Status":  http.StatusInternalServerError,
                    "Message": "error",
                    "Data":    map[string]interface{}{"data": err.Error()}})
                return
            }
        }

        c.JSON(http.StatusOK, gin.H{
            "Status":  http.StatusOK,
            "Message": "success",
            "Data":    updatedPost})
    }
}

Enter fullscreen mode Exit fullscreen mode

It is important to note that permission to resources in an environment with multiple users should be granted right after a user has been authenticated and checked to see if they have the required authorisation. Multi user applications typically operate on role and resource based authorisation, as stated earlier.

Identity and Access Management (IAM) and Policies

A popular idea in AWS is identity and access management (IAM). In essence, it is a framework for authorization and authentication, composed of policies that regulate who has access to what. According to the organisational structure and the roles of the users, many IAM systems employ role-based access control (RBAC) to issue permissions for who may do what within an application.
In addition to RBAC, IAM systems support other types of access control methods. These access control methods include;

  • Mandatory Access Control (MAC): regulates access by matching the users ids with existing clearance permission . Only the system administrators or policy administrators have the authority to allow or restrict access to certain files. When a user attempts to enter the system, the system looks at their security characteristics to evaluate if permission is granted.

  • Discretionary Access Control (DAC) - As the name suggests, this technique enables system administrators to provide another user access to an application at their discretion. However, DAC has a significant problem since it offers end users far more authority than they deserve, which poses a big security risk.

  • Rule-Based Access Control: This method grants users access to a system in accordance with predetermined rules. These restrictions are included in an Access Control List, which is associated with the user identity. It is quite similar to role-based access control, however their assignment procedures are different. For instance, if we were to design a blog system using role-based access control, we would create user groups and assign policies to each user group. If we were to accomplish this using rule-based access control, however, we would be able to take rights much farther down than just grouping. For example, you may go further and specify that only users who identify as "female" can access particular articles, as opposed to just declaring that all readers can access all categories.

Identity and Access Management (IAM) Components

Role-based access control is a feature of systems with IAM deployed that enables system administrators to manage access to systems based on the roles of certain individuals within the company. Everything pertaining to user access and management should be accessible via IAM systems. Key elements of IAM systems include:

  1. Verifying and authenticating user IDs
  2. Taking care of the many user IDs
  3. Using policies or other types of control to manage user rights distribution.
  4. Securing the system as a whole and safeguarding the system's sensitive data.

Best Practices

Here are some good practices to take note of;

  • Security and risks: The collection and storage of data only when necessary is a strategy to guarantee you have safe systems when designing systems with IAM functionalities. IAM has risks of its own, and because IAM systems are essentially the entrance to your data, it becomes somewhat more harmful if they are breached. Comprehensive provisioning and activity logging are two more solid strategies that may be used to provide complete security. Multi-factor authentication is also a smart technique to assure security since it requires many stages of security authentication before providing access. Getting rid of dormant accounts or accounts belonging to former workers is also a good idea to prevent those accounts from being used as gateways for unauthorised access.
  • Proper UI representation and communication: Users should see the appropriate messages and functionalities. For instance, if a person is not authorised to make a blog post, it would be inappropriate to display that screen to them. Even if you did, a well-designed screen that displays a warning like "You are not authorised to do this" may still prove valuable in communicating your message to the users.

  • Creating user groups and roles: Combining rights into roles is important and a better option. This method works well in situations when various user groups, such as marketing and finance, want access to the same resource. Instead of specifying that permission for each department individually, it would be much simpler to create a role that includes the appropriate permission and link it to each department.

Conclusion

A well designed multi-user system enables each environment to accommodate well-separated users with clearly specified configurations and policies associated with each, while also preventing the activities of one user from having an impact on those of another. This is highly significant. Less human error occurs when roles and permissions are preset, which reduces the possibility of mistakes that may occur if each user's configuration were done manually. RBAC makes it considerably less likely to accidentally grant users access to restricted resources.

Top comments (0)