Table of Contents
Every product we build deals with user authorization. Users may only access certain features or data based on their permissions within the app. While we want to ensure users can access everything they should, we also want to ensure they can’t access anything they shouldn’t. This is why we default to deny all user privileges when starting a new app.
What is Default to Deny?
Network security practitioners are familiar with a similar concept. The National Institute of Standards and Technology (NIST) issued security guidance that describes a “deny by default / allow by exception” security control. The idea is simple: deny all network traffic by default and only allow traffic that is explicitly authorized. This concept is more broadly known as the principle of least privilege.
“Default to deny” is our application of this principle to mobile and web apps. When we start a new app, users are initially denied access to all features and data. We then explicitly grant access to the things the user is permitted to access.
Why is Default to Deny Important?
Defaulting to deny is an important safeguard for user data. Let’s say we have an app that lets users purchase books. Users save their credit card information in the app so they don’t have to re-enter it every time they make a purchase.
If we let every user access everything by default, what might happen? We could miss all of the ways that credit card data could be accessed. A malicious user could probably find a way to steal credit card numbers from other user accounts. We certainly don’t want that! So why don’t we start with the complete opposite position?
If we deny all access by default, no one can steal credit card information. Users also wouldn’t be able to access their own credit card details. That isn’t great, but it’s much better than letting anyone see all credit card numbers. And opening up user permissions just a little bit is much easier than trying to find all the ways sensitive data might be accessed.
By defaulting to deny, we can be confident that no one is doing anything unless it is explicitly allowed. Our users’ data is protected from accidental exposure and malicious attacks.
How to Implement Default to Deny
As an example of how to default to deny, consider a Ruby on Rails app (as we tend to do). The primary way a user interacts with the app is through API endpoints powered by controllers. We use Pundit, a popular authorization library for Rails, to manage user permissions.
Using a BaseController
class, we can define an after_action
that ensures an authorization check is performed on all requests by default.
class BaseController < ActionController::Base
include Pundit::Authorization
after_action :verify_authorized
end
Then we can define a base Pundit policy that denies all access by default. Note that we don’t need to define any actions in the policy. Pundit will automatically check for a policy method that matches the controller action. However, actions are defined in our example to make it clear that we are denying access by default.
class BasePolicy
def index?
false
end
def show?
false
end
def create?
false
end
def update?
false
end
def destroy?
false
end
end
Now, we can have any controller inherit from BaseController
and Pundit will deny all access to all actions. For each model class we define, we can add a new Pundit policy that inherits from BasePolicy
. Then we can explicitly allow access to the actions we want to allow. Consider the following policy example for a Book
model. All users can use the index
or show
actions, but only administrators can use the create
, update
, or destroy
actions.
class BookPolicy < BasePolicy
def index?
true
end
def show?
true
end
def create?
user.admin?
end
def update?
user.admin?
end
def destroy?
user.admin?
end
end
Starting With Security
Default to deny is a simple concept that can have a big impact on the security of your app. By denying all access by default, you can be confident that no one is doing anything unless it is explicitly allowed. This is especially important for apps that deal with sensitive user data. Consider defaulting to deny when starting your next app and see how much more confident you feel about your authorization rules.