<%= link_to post.title, post_path(post) %>
<% end %> ``` ## Ensuring policies and scopes are used When you are developing an application with Pundit it can be easy to forget to authorize some action. People are forgetful after all. Since Pundit encourages you to add the `authorize` call manually to each controller action, it's really easy to miss one. Thankfully, Pundit has a handy feature which reminds you in case you forget. Pundit tracks whether you have called `authorize` anywhere in your controller action. Pundit also adds a method to your controllers called `verify_authorized`. This method will raise an exception if `authorize` has not yet been called. You should run this method in an `after_action` hook to ensure that you haven't forgotten to authorize the action. For example: ``` ruby class ApplicationController < ActionController::Base include Pundit::Authorization after_action :verify_authorized end ``` Likewise, Pundit also adds `verify_policy_scoped` to your controller. This will raise an exception similar to `verify_authorized`. However, it tracks if `policy_scope` is used instead of `authorize`. This is mostly useful for controller actions like `index` which find collections with a scope and don't authorize individual instances. ``` ruby class ApplicationController < ActionController::Base include Pundit::Authorization after_action :verify_pundit_authorization def verify_pundit_authorization if action_name == "index" verify_policy_scoped else verify_authorized end end end ``` **This verification mechanism only exists to aid you while developing your application, so you don't forget to call `authorize`. It is not some kind of failsafe mechanism or authorization mechanism. You should be able to remove these filters without affecting how your app works in any way.** Some people have found this feature confusing, while many others find it extremely helpful. If you fall into the category of people who find it confusing then you do not need to use it. Pundit will work fine without using `verify_authorized` and `verify_policy_scoped`. ### Conditional verification If you're using `verify_authorized` in your controllers but need to conditionally bypass verification, you can use `skip_authorization`. For bypassing `verify_policy_scoped`, use `skip_policy_scope`. These are useful in circumstances where you don't want to disable verification for the entire action, but have some cases where you intend to not authorize. ```ruby def show record = Record.find_by(attribute: "value") if record.present? authorize record else skip_authorization end end ``` ## Manually specifying policy classes Sometimes you might want to explicitly declare which policy to use for a given class, instead of letting Pundit infer it. This can be done like so: ``` ruby class Post def self.policy_class PostablePolicy end end ``` Alternatively, you can declare an instance method: ``` ruby class Post def policy_class PostablePolicy end end ``` ## Plain old Ruby Pundit is a very small library on purpose, and it doesn't do anything you can't do yourself. There's no secret sauce here. It does as little as possible, and then gets out of your way. With the few but powerful helpers available in Pundit, you have the power to build a well structured, fully working authorization system without using any special DSLs or funky syntax. Remember that all of the policy and scope classes are plain Ruby classes, which means you can use the same mechanisms you always use to DRY things up. Encapsulate a set of permissions into a module and include them in multiple policies. Use `alias_method` to make some permissions behave the same as others. Inherit from a base set of permissions. Use metaprogramming if you really have to. ## Generator Use the supplied generator to generate policies: ``` sh rails g pundit:policy post ``` ## Closed systems In many applications, only logged in users are really able to do anything. If you're building such a system, it can be kind of cumbersome to check that the user in a policy isn't `nil` for every single permission. Aside from policies, you can add this check to the base class for scopes. We suggest that you define a filter that redirects unauthenticated users to the login page. As a secondary defence, if you've defined an ApplicationPolicy, it might be a good idea to raise an exception if somehow an unauthenticated user got through. This way you can fail more gracefully. ``` ruby class ApplicationPolicy def initialize(user, record) raise Pundit::NotAuthorizedError, "must be logged in" unless user @user = user @record = record end class Scope attr_reader :user, :scope def initialize(user, scope) raise Pundit::NotAuthorizedError, "must be logged in" unless user @user = user @scope = scope end end end ``` ## NilClassPolicy To support a [null object pattern](https://en.wikipedia.org/wiki/Null_Object_pattern) you may find that you want to implement a `NilClassPolicy`. This might be useful where you want to extend your ApplicationPolicy to allow some tolerance of, for example, associations which might be `nil`. ```ruby class NilClassPolicy < ApplicationPolicy class Scope < ApplicationPolicy::Scope def resolve raise Pundit::NotDefinedError, "Cannot scope NilClass" end end def show? false # Nobody can see nothing end end ``` ## Rescuing a denied Authorization in Rails Pundit raises a `Pundit::NotAuthorizedError` you can [rescue_from](https://guides.rubyonrails.org/action_controller_overview.html#rescue-from) in your `ApplicationController`. You can customize the `user_not_authorized` method in every controller. ```ruby class ApplicationController < ActionController::Base include Pundit::Authorization rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized flash[:alert] = "You are not authorized to perform this action." redirect_back_or_to(root_path) end end ``` Alternatively, you can globally handle Pundit::NotAuthorizedError's by having rails handle them as a 403 error and serving a 403 error page. Add the following to application.rb: ```config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden``` ## Creating custom error messages `NotAuthorizedError`s provide information on what query (e.g. `:create?`), what record (e.g. an instance of `Post`), and what policy (e.g. an instance of `PostPolicy`) caused the error to be raised. One way to use these `query`, `record`, and `policy` properties is to connect them with `I18n` to generate error messages. Here's how you might go about doing that. ```ruby class ApplicationController < ActionController::Base rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized(exception) policy_name = exception.policy.class.to_s.underscore flash[:error] = t "#{policy_name}.#{exception.query}", scope: "pundit", default: :default redirect_back_or_to(root_path) end end ``` ```yaml en: pundit: default: 'You cannot perform this action.' post_policy: update?: 'You cannot edit this post!' create?: 'You cannot create posts!' ``` This is an example. Pundit is agnostic as to how you implement your error messaging. ## Manually retrieving policies and scopes Sometimes you want to retrieve a policy for a record outside the controller or view. For example when you delegate permissions from one policy to another. You can easily retrieve policies and scopes like this: ``` ruby Pundit.policy!(user, post) Pundit.policy(user, post) Pundit.policy_scope!(user, Post) Pundit.policy_scope(user, Post) ``` The bang methods will raise an exception if the policy does not exist, whereas those without the bang will return nil. ## Customize Pundit user On occasion, your controller may be unable to access `current_user`, or the method that should be invoked by Pundit may not be `current_user`. To address this, you can define a method in your controller named `pundit_user`. ```ruby def pundit_user User.find_by_other_means end ``` For instance, Rails 8 includes a built-in [authentication generator](https://github.com/rails/rails/tree/8-0-stable/railties/lib/rails/generators/rails/authentication). If you choose to use it, the currently logged-in user is accessed via `Current.user` instead of `current_user`. To ensure compatibility with Pundit, define a `pundit_user` method in `application_controller.rb` (or another suitable location) as follows: ```ruby def pundit_user Current.user end ``` ### Handling User Switching in Pundit When switching users in your application, it's important to reset the Pundit user context to ensure that authorization policies are applied correctly for the new user. Pundit caches the user context, so failing to reset it could result in incorrect permissions being applied. To handle user switching, you can use the following pattern in your controller: ```ruby class ApplicationController include Pundit::Authorization def switch_user_to(user) terminate_session if authenticated? start_new_session_for user pundit_reset! end end ``` Make sure to invoke `pundit_reset!` whenever changing the user. This ensures the cached authorization context is reset, preventing any incorrect permissions from being applied. ## Policy Namespacing In some cases it might be helpful to have multiple policies that serve different contexts for a resource. A prime example of this is the case where User policies differ from Admin policies. To authorize with a namespaced policy, pass the namespace into the `authorize` helper in an array: ```ruby authorize(post) # => will look for a PostPolicy authorize([:admin, post]) # => will look for an Admin::PostPolicy authorize([:foo, :bar, post]) # => will look for a Foo::Bar::PostPolicy policy_scope(Post) # => will look for a PostPolicy::Scope policy_scope([:admin, Post]) # => will look for an Admin::PostPolicy::Scope policy_scope([:foo, :bar, Post]) # => will look for a Foo::Bar::PostPolicy::Scope ``` If you are using namespaced policies for something like Admin areas, we recommend defining a `pundit_namespace` hook in your `ApplicationController` and overriding it in namespaced controllers: ```ruby class ApplicationController < ActionController::Base include Pundit::Authorization private def pundit_namespace(record) = record def authorize(record, ...) = super(pundit_namespace(record), ...) def policy_scope(scope, ...) = super(pundit_namespace(scope), ...) end class AdminController < ApplicationController private # Override the pundit namespace in Admin. def pundit_namespace(record) = [:admin, record] end class Admin::PostController < AdminController def index policy_scope(Post) end def show post = authorize Post.find(params[:id]) end end ``` ## Additional context Pundit strongly encourages you to model your application in such a way that the only context you need for authorization is a user object and a domain model that you want to check authorization for. If you find yourself needing more context than that, consider whether you are authorizing the right domain model, maybe another domain model (or a wrapper around multiple domain models) can provide the context you need. Pundit does not allow you to pass additional arguments to policies for precisely this reason. However, in very rare cases, you might need to authorize based on more context than just the currently authenticated user. Suppose for example that authorization is dependent on IP address in addition to the authenticated user. In that case, one option is to create a special class which wraps up both user and IP and passes it to the policy. ``` ruby class UserContext < Data.define(:user, :ip) end class ApplicationController include Pundit::Authorization def pundit_user = UserContext.new(current_user, request.ip) end ``` ## Strong parameters In Rails, [mass-assignment protection is handled in the controller](https://guides.rubyonrails.org/action_controller_overview.html#strong-parameters). With Pundit you can control which attributes a user has access to update via your policies. You can set up an `expected_attributes_for_action(action_name)` method in your policy like this: ```ruby # app/policies/post_policy.rb class PostPolicy < ApplicationPolicy def expected_attributes_for_action(_action_name) if user.admin? || user.owner_of?(post) [:title, :body, :tag_list] else [:tag_list] end end end ``` You can now retrieve these attributes from the policy: ```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController def update @post = Post.find(params[:id]) if @post.update(post_params) redirect_to @post else render :edit end end private def post_params params.expect(policy(@post).expected_attributes_for_action(action_name)) end end ``` However, this is a bit cumbersome, so Pundit provides a convenient helper method with `#expected_attributes`: ```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController def update @post = Post.find(params[:id]) if @post.update(expected_attributes(@post)) redirect_to @post else render :edit end end end ``` ### Permitted Parameters Pundit still support the old `params.require.permit()` style of permitting attributes, although `params.expect()` is preferred. If you need to fetch parameters based on namespaces different from the suggested one, override the below method, in your controller, and return an instance of `ActionController::Parameters`. ```ruby def pundit_params_for(record) params.require(pundit_param_key(record)) end ``` For example: ```ruby # If you don't want to use require def pundit_params_for(record) params.fetch(pundit_param_key(record), {}) end # If you are using something like the JSON API spec def pundit_params_for(_record) params.fetch(:data, {}).fetch(:attributes, {}) end ``` ## RSpec ### Policy Specs > [!TIP] > An alternative approach to Pundit policy specs is scoping them to a user context as outlined in this [excellent post](https://thunderboltlabs.com/blog/2013/03/27/testing-pundit-policies-with-rspec/) and implemented in the third party [pundit-matchers](https://github.com/punditcommunity/pundit-matchers) gem. Pundit includes a mini-DSL for writing expressive tests for your policies in RSpec. Require `pundit/rspec` in your `spec_helper.rb`: ``` ruby require "pundit/rspec" ``` Then put your policy specs in `spec/policies`, and make them look somewhat like this: ``` ruby describe PostPolicy do subject { described_class } permissions :update?, :edit? do it "denies access if post is published" do expect(subject).not_to permit(User.new(admin: false), Post.new(published: true)) end it "grants access if post is published and user is an admin" do expect(subject).to permit(User.new(admin: true), Post.new(published: true)) end it "grants access if post is unpublished" do expect(subject).to permit(User.new(admin: false), Post.new(published: false)) end end end ``` ### Custom matcher description By default rspec includes an inspected `user` and `record` in the matcher description, which might become overly verbose: ``` PostPolicy update? and show? is expected to permit #