Authorization
Introduction
Although I am fortunate enough to develop software during the week, I still get the pleasure of being employed as a server and bartender. My current service industry employer is making huge changes on structure, and I am very excited for it; however, with change there is always resistance. During one of our wine meetings, there was talk about having an anonymous box where employees can submit anonymously make comments about the changes and suggestions. Considering we have 4 locations, and I was speaking to one of the higher ups. I thought it would be a great idea to create a anonymous box backend.
Models
In terms of structuring the database, Users and Posts were a given. At first, I thought to myself since they were anonymous. I can just render all the posts to the admin and none to the user; however, I had to think ahead a bit. Although the suggestion was to only use it for the Oak Park location, I am hopeful that I will be used for our other locations. So I also made a restaurants table, to allow admin to view posts by location.
So what did I need to do to secure the posts from the outside world? Easy, authenticate user. If user isn't authenticated, make them login or register. It's miller time. No its not. We don't really care if some random person in the world reads what Bob had to say about his coworker Mallory (kinda), we care about making sure Mallory never reads what Bob said. Well lets find those vulnerabilities.
Routes, Controllers and Actions
There are a some routes that we want some users to access but not others:
- Post Routes: We want the user to either be admin or author view the post; otherwise, redirect to home. Although there is no direct link in the app to navigate to one's post. It doesn't take a rocket scientist to figure out the routes to a RESTful app.
There are a some routes that we want some users to access but not others:
- Restaurant Routes: We only want the admin to view these routes. Since not only do they contain the power to create restaurants and read all posts associated with the restaurant, but they also can delete a restaurant. That is a huge loss of data. Not good.
Implementation
-
Include Pundit in ApplicationController
#app/controllers/application_controller.rb include Pundit
Generate Pundit Policy Files
Run "rails g pundit:policy policy_name" in bash-
Write authorization logic in policy files. For posts, I actually used a scope.
# In app/policies/post_policy.rb class PostPolicy < ApplicationPolicy attr_reader :user, :post def initialize(user, post) @user = user @post = post end def index? user.present? end def show? scope.where(:id => post.id).exists? end def update? user.admin? || post.user == user end def destroy? user.admin? || post.user == user end class Scope < Scope def resolve user.admin? ? scope.all : scope.where(user: user) end end end
-
Implement Authorization in Controllers
#In app/controllers/post_controller.rb class PostController < ApplicationController ... # GET /posts or /posts.json def index # @posts = Post.all @posts = policy_scope(Post) end def show authorize @post end ... end
-
Handle Pundit::NotAuthorizedError in Application Controller by rescuing it.
#In app/controllers/application_controller.rb rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized flash[:alert] = "You are not authorized to perform this action." redirect_to(request.referrer || root_path) end
Conclusion
And just like that, the posts are protected from other users and the world. The only people allowed to see them are admin and author. Also I definitely recommend Vezzi Barbera. Had 2 glasses when writing this article. Nice medium body+, slightly acidic, dry wine.
Top comments (1)
That’s great that you’ve been able to use your programming skills for your job! Awesome!