CanCanCommunity/cancancan · error · CanCan::AccessDenied

You are not authorized to access this page.

Error message

You are not authorized to access this page.

What it means

This is CanCanCan's core authorization failure: Ability#authorize! (lib/cancan/ability.rb:180) raises CanCan::AccessDenied when cannot?(action, subject) is true. The generic 'You are not authorized to access this page.' text is used when no custom :message was passed and no unauthorized_message/i18n entry resolved. The raised exception carries action, subject, and conditions accessors, so you can inspect exactly which check failed.

Source

Thrown at lib/cancan/ability.rb:180

    end

    # User shouldn't specify targets with names of real actions or it will cause Seg fault
    def validate_target(target)
      error_message = "You can't specify target (#{target}) as alias because it is real action name"
      raise Error, error_message if aliased_actions.values.flatten.include? target
    end

    def model_adapter(model_class, action)
      adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
      adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
    end

    # See ControllerAdditions#authorize! for documentation.
    def authorize!(action, subject, *args)
      message = args.last.is_a?(Hash) && args.last.key?(:message) ? args.pop[:message] : nil
      if cannot?(action, subject, *args)
        message ||= unauthorized_message(action, subject)
        raise AccessDenied.new(message, action, subject, args)
      end
      subject
    end

    def attributes_for(action, subject)
      attributes = {}
      relevant_rules(action, subject).map do |rule|
        attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
      end
      attributes
    end

    def has_block?(action, subject)
      relevant_rules(action, subject).any?(&:only_block?)
    end

    def has_raw_sql?(action, subject)
      relevant_rules(action, subject).any?(&:only_raw_sql?)

View on GitHub (pinned to 8c1bf153a3)

Solutions

  1. Handle the exception globally: rescue_from CanCan::AccessDenied in ApplicationController and redirect or render 403.
  2. Add or fix the ability rule in app/models/ability.rb (e.g., can :manage, Article for the failing role), using the exception's action/subject to pinpoint it.
  3. Verify the ability wiring: current_user is set, the correct Ability class (Ability.new(current_user)) is used, and rule conditions match real column names.
  4. Pass a custom message (authorize! :edit, @article, message: '...') or add i18n entries under unauthorized_message for clearer UX.

Example fix

# before (app/models/ability.rb)
class Ability
  include CanCan::Ability
  def initialize(user)
    can :read, Article
    # no rule for :edit -> authorize! :edit raises AccessDenied
  end
end

# after
class Ability
  include CanCan::Ability
  def initialize(user)
    return unless user
    can :manage, Article, owner_id: user.id
    can :read, Article
  end
end

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  rescue_from CanCan::AccessDenied do |exception|
    redirect_to root_path, alert: exception.message
  end
end
Defensive patterns

Strategy: try-catch

Validate before calling

# guard before authorizing (e.g., for soft-gated UI instead of an exception)
return render_forbidden unless current_ability.can?(:edit, @article)

Type guard

def access_denied?(exception)
  exception.is_a?(CanCan::AccessDenied)
end

Try / catch

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  rescue_from CanCan::AccessDenied do |exception|
    Rails.logger.warn("ACCESS DENIED: #{exception.action} on #{exception.subject.inspect}")
    respond_to do |format|
      format.json { render json: { error: 'Forbidden' }, status: :forbidden }
      format.html { redirect_to root_path, alert: exception.message }
    end
  end
end

Prevention

When it happens

Trigger: Calling authorize! :edit, @article when the current ability has no matching 'can' rule; load_and_authorize_resource on a controller action whose rules deny access (e.g., a role Ability missing can :manage, Article); a rule whose condition hash or block evaluates false for that specific record.

Common situations: Forgot can :manage, :all for admin users; conditions hash key not matching record attributes (e.g., owner_id vs user_id); Devise session expired so current_user is nil and the default Ability denies everything; abilities defined per-role but the role column returned an unexpected value; nested or :through resources failing the parent check.

Related errors


AI-assisted analysis of CanCanCommunity/cancancan@8c1bf153a3 (2026-08-21). Data as JSON: /api/errors/5569ef5d7323fb81. Report an issue: GitHub.