Default to Deny for More Secure Apps

Default to Deny for More Secure Apps

·

4 min read

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.