fbpx logo-new mail facebook Dribble Social Icon Linkedin Social Icon Twitter Social Icon Github Social Icon Instagram Social Icon Arrow_element diagonal-decor rectangle-decor search arrow circle-flat
Development

Applying Authorization: Conditionally Rendering UI Elements in a React/Relay Application

Chris Cortez Tandem Alum

last updated April 25, 2019

If you’ve ever built an admin dashboard in Rails, then you are familiar with the concept of authorization and controlling what areas of an application a user can access. The idea is simple: Admins can access the admin dashboard and everyone else cannot. But sometimes this control needs to happen on the application level, outside of an admin interface.

Furthermore, sometimes you need to conditionally display elements like a button or a link based on the user’s access level. In these cases, implementing this can get a little tricky, especially if you’re using Pundit to handle permissions and Relay to pass data between a GraphQL API and a React client.

Let’s imagine we’re building a React interface for an academic university. We’ll be using GraphQL and Relay to get data from the API to our interface. The types of users who will use this interface are Students, Professors, Department Heads, and the Dean. Now, let’s say in this interface you needed to render a button to edit a student’s grade. Obviously, students should not be allowed to do this, but let’s say that Professors, Department Heads, and the Dean can. We could implement this by using a simple check on the role of the current user before rendering the button:

render() => {
  const role = this.props.viewer.currentUser.role
  return(
    // code omitted
    {role == ‘dean’ || role == ‘department_head’ || role == ‘professor' && (
        // render the edit button
      )
    }
  )
}

And we could pass down the role by having a GraphQL type with a role field for a user:

module Types
  class UserType < Types::BaseObject
    description "A user in the system"
    # other fields omitted
    field :role, String
  end
end

And we could access the current user by having a ViewerType which uses context:

module Types
  class ViewerType < Types::BaseObject
    description “The root node for queries"
    # other fields omitted
    field :current_user, UserType
    def current_user
      context[:current_user]
    end
  end
end

And a query for Relay which asks for it all:

fragment User_viewer on Viewer {
  id
  current_user {
    role
  }
}

But there’s a problem with this solution: We should avoid tying the permission logic into the presentation logic. The client shouldn’t care or even know about the role of the current user – permissioning is a back-end concern. The front-end and back-end should follow the single responsibility principle, and it’s best to make our client extensible so if requirements change, we only need to change the back-end.The client should just do one thing and do it well, which is rendering the interface correctly. It is a requirement, however, that we conditionally show the button based on the role somehow, so let’s use Pundit to define a policy and make the interface a little simpler.

Starting with the idea that we have a Grade model in our system, we can create a GradePolicy:

class GradePolicy
  attr_reader :user, :grade
  def initialize(user, grade)
    @user = user
    @grade = grade
  end
  def update?
    role = user.role
    role == ‘dean’ || role == ‘department_head’ || role == ‘professor'
  end
end

We’ll then create a GradePolicyType so our new class can be handled by GraphQL and pass along the current user from context:

module Types
  class GradePolicyType < Types::BaseObject
    description "References the policy used for authorizations on Grades”
    # code omitted
    field :can_update, Boolean, null: false
    def can_update
      GradesPolicy.new(context[:current_user], nil).update?
    end
  end
end

Then our ViewerType can have a grade_policy field:

module Types
  class ViewerType < Types::BaseObject
    description “The root node for queries"
    # other fields omitted
    field :grade_policy, GradePolicyType
  end
end

And we can change our query on the front-end to access the can update field:

fragment User_viewer on Viewer {
  id
  gradePolicy {
    canUpdate
  }
}

This ultimately lets us use a much simpler and role-independent check for rendering the button on the client:

render() => {
  return(
    // code omitted
    {this.props.viewer.gradePolicy.canUpdate && (
        // render the edit button
      )
    }
  )
}

You might have noticed that we passed nil as the grade parameter in GradesPolicy.new, and we’re also not doing anything with grade in the GradePolicy. That’s because all along, we’ve been assuming the policy is universal. In other words, if you can change one grade, you can change them all. This isn’t how this should work, however. Instead, we can get creative and pass in a reference to a grade (instead of nil) and then return the correct boolean based on things like a student’s class or other attributes of the current user. The important thing is that the update? method on the GradePolicy returns the correct value and all of the permission logic lives in the policy instead of the client.

One other bonus of this method is a flexible implementation of authorization. You can use Pundit, CanCanCan, or any other authorization library of your choosing. All that’s required is having a PolicyType return the correct values for whatever is you need to render.

 

Let’s do something great together

We do our best work in close collaboration with our clients. Let’s find some time for you to chat with a member of our team.

Say Hi