{"id":13878993,"url":"https://github.com/0x7466/active_entry","last_synced_at":"2025-09-11T13:44:15.070Z","repository":{"id":56842127,"uuid":"343681323","full_name":"0x7466/active_entry","owner":"0x7466","description":"A flexible access control system for your Rails app","archived":false,"fork":false,"pushed_at":"2021-09-12T08:25:11.000Z","size":283,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-29T14:18:02.009Z","etag":null,"topics":["access-control","authentication","authorization","rails","rails-gem","ruby-gem","ruby-on-rails"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/0x7466.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null}},"created_at":"2021-03-02T07:19:49.000Z","updated_at":"2023-10-10T13:29:07.000Z","dependencies_parsed_at":"2022-08-29T05:10:12.968Z","dependency_job_id":null,"html_url":"https://github.com/0x7466/active_entry","commit_stats":null,"previous_names":["tfm-agency/active_entry"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x7466%2Factive_entry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x7466%2Factive_entry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x7466%2Factive_entry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x7466%2Factive_entry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0x7466","download_url":"https://codeload.github.com/0x7466/active_entry/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246178379,"owners_count":20736136,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["access-control","authentication","authorization","rails","rails-gem","ruby-gem","ruby-on-rails"],"created_at":"2024-08-06T08:02:06.345Z","updated_at":"2025-03-29T11:31:00.812Z","avatar_url":"https://github.com/0x7466.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/TFM-Agency/active_entry\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/TFM-Agency/active_entry/main/active_entry_logo.svg\" alt=\"Active Entry Logo\" width=\"350px\"/\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n# Active Entry - Simple and flexible authentication and authorization\n[![Gem Version](https://badge.fury.io/rb/active_entry.svg)](https://badge.fury.io/rb/active_entry)\n[![Ruby](https://github.com/TFM-Agency/active_entry/actions/workflows/ci-rspec.yml/badge.svg)](https://github.com/TFM-Agency/active_entry/actions/workflows/ci-rspec.yml)\n![Coverage](https://raw.githubusercontent.com/TFM-Agency/active_entry/main/coverage/coverage_badge_total.svg)\n[![Maintainability](https://api.codeclimate.com/v1/badges/3db0f653be6bdfe0fdac/maintainability)](https://codeclimate.com/github/TFM-Agency/active_entry/maintainability)\n[![Documentation](https://img.shields.io/badge/docs-rdoc.info-blue.svg)](https://rubydoc.info/github/TFM-Agency/active_entry/main)\n\nActive Entry is a secure way to check for authentication and authorization before an action is performed. It's currently only compatible with Rails. But in later versions will ActiveEntry be Framework independent.\n\nActive Entry works like many other Authorization Systems like [Pundit](https://github.com/varvet/pundit) or [Action Policy](https://github.com/palkan/action_policy) with **Policies**. However in Active Entry it's all about the method calling the auth mechanism. For every method that needs authentication or authorization, a decision maker method counterpart has to be created in the policy of the class.\n\n## Example\n\nLet's say we have an Users controller in our application:\n\n```ruby\n# app/controllers/users_controller.rb\nclass UsersController \u003c ApplicationController\n  include ActiveEntry::ControllerConcern   # Glue for the controller and Active Entry\n\n  def index\n    pass!   # The auth happens here\n    load_users\n  end\nend\n```\n\nWe have to create the UsersPolicy in order for Active Entry to know who is authenticated and authorized and who not.\n\n```ruby\n# app/policies/users_policy.rb\nmodule UsersPolicy\n  class Authentication \u003c ActiveEntry::Base::Authentication\n    def index?\n      Current.user_signed_in?  # Only signed in users are considered to be authenticated.\n    end\n  end\n\n  class Authorization \u003c ActiveEntry::Base::Authorization\n    def index?\n      Current.user.admin?  # Only admins are authorized to perform this action\n    end\n  end\nend\n```\n\nNow every time somebody calls the `users#index` endpoint, he or she has to be signed in and an admin. Otherwise `ActiveEntry::NotAuthenticatedError` or `ActiveEntry::NotAuthorizedError` are raised.\nYou can catch them easily in your controller by using Rails' `rescue_from`.\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  rescue_from ActiveEntry::NotAuthenticatedError, with: :not_authenticated\n  rescue_from ActiveEntry::NotAuthorizedError, with: :not_authorized\n\n  def not_authenticated\n    flash[:danger] = \"Not authenticated. Please sign in.\"\n    redirect_to sign_in_path\n  end\n\n  def not_authorized\n    flash[:danger] = \"Not authorized.\"\n    redirect_to root_path\n  end\nend\n```\n\n## Installation\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'active_entry'\n```\n\nOr install it without bundler:\n```bash\n$ gem install active_entry\n```\n\nRun Bundle:\n```shell\n$ bundle\n```\n\nAnd then install Active Entry:\n```shell\n$ rails g active_entry:install\n```\n\nThis will generate `app/policies/application_policy.rb`.\n\n## Usage\nActive Entry works with Policies. You can generate policies the following way:\n\nLet's consider the example from above.\nWe have an UsersController and we want a policy for that:\n\n```shell\n$ rails g policy Users\n```\n\nThis generates a policy called `UsersPolicy` and is located in `app/policies/users_policy.rb`.\n\nThe above generator call would generate something like this, but with a few comments to help you get started:\n\n```ruby\nmodule UsersPolicy\n  class Authentication \u003c ActiveEntry::Base::Authentication\n  end\n\n  class Authorization \u003c ActiveEntry::Base::Authorization\n  end\nend\n```\n\n### Verify authentication and authorization\nYou probably want to control authentication and authorization for every controller action you have in your app. As a safeguard to ensure, that auth is performed in every request and the auth call is not forgotten in development, add the `verify_authentication!` and `verify_authorization!` to your `ApplicationController`:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  verify_authentication!\n  verify_authorization!\n  # ...\nend\n```\nThis ensures, that you perform auth in all your controllers and raises errors if not.\n\n### Perform authentication and authorization\nin order to do the actual authentication and authorization, you have to use `authenticate!` and `authorize!` or `pass!` as in your actions.\n\n```ruby\nclass UsersController \u003c ApplicationController\n  def authentication_only_action\n    authenticate!\n  end\n\n  def authorization_only_action\n    authorize!\n  end\n\n  def both_authentication_and_authorization_action\n    pass!\n  end\nend\n```\n\nIf you try to open a page, Active Entry will raise `ActiveEntry::DecisionMakerMethodNotDefinedError`. This means we have to define the decision makers in our policy.\n\n```ruby\nmodule UsersPolicy\n  class Authentication \u003c ApplicationPolicy::Authentication\n    def authentication_only_action?\n      success   # == true | Everybody is allowed\n    end\n\n    def both_authentication_and_authorization_action?\n      success\n    end\n  end\n\n  class Authorization \u003c ApplicationPolicy::Authorization\n    def authorization_only_action?\n      success\n    end\n\n    def both_authentication_and_authorization_action?\n      success\n    end\n  end\nend\n```\n\nEvery decision maker ends with an `?`. The name has to be the same as the name of the controller action. So `index` is going to be `index?`.\n\nIn order for Active Entry to not raise an auth error, the decision makers have to return `true`. In our above example we used `success`, which simply returns `true`.\n\n**Note:** It has to be an explicit `true` and not just a truthy value. A string or object return value would raise an auth error.\n\n### Rescuing from errors\nCatch the errors in your controllers to redirect the user or show them a message.\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n  # ...\n\n  rescue_from ActiveEntry::NotAuthenticatedError, with: :not_authenticated\n  rescue_from ActiveEntry::NotAuthorizedError, with: :not_authorized\n\n  private\n\n  def not_authenticated\n    flash[:danger] = \"You are not authenticated!\"\n    redirect_to login_path\n  end\n\n  def not_authorized\n    flash[:danger] = \"You are not authorized to call this action!\"\n    redirect_to root_path\n  end\nend\n```\n\nIn this example above, the user will be redirected with a flash message. But you can do whatever you want. For example logging.\n\n### Authenticate/authorize outside the action\nYou can authenticate and authorize outside the action:\n\n```ruby\nclass UsersController \u003c ApplicationController\n  authenticate_now!\n  authorize_now!\n  # pass_now!  # Does both, authentication and authorization\nend\n```\n\nAccess control on class level will ensure that every action performs it.\n\n**Note:** Don't use the class methods if the controller is inherited in other controllers. Best, don't use them at all and use the methods in the actions conciously.\n\n## Variables\nYou can pass variables to the decision maker.\n\n```ruby\nclass UsersController \u003c ApplicationController\n  def show\n    @user = User.find params[:id]\n    pass! user: @user\n  end\nend\n```\n\nYou can now access the user object as instance variable in your decision maker.\n\n```ruby\nmodule Users\n  class Authentication \u003c ApplicationPolicy::Authentication\n    def show?\n      @user  # == \u003cUser:Instance\u003e\n    end\n  end\n\n  class Authorization \u003c ApplicationPolicy::Authorization\n    def show?\n      @user  # == \u003cUser:Instance\u003e\n    end\n  end\nend\n```\n\n## Custom error data\nIf you write something into `@error` in our decision maker, you can access it in your rescue methods in the controller:\n\n```ruby\nmodule UsersPolicy\n  class Authentication \u003c ApplicationPolicy::Authentication\n    def show?\n      @error = { code: 100 }\n    end\n  end\n\n  class Authorization \u003c ApplicationPolicy::Authorization\n    def show?\n      @error = { code: 100 }\n    end\n  end\nend\n\nclass ApplicationController \u003c ActionController::Base\n  # ...\n\n  rescue_from ActiveEntry::NotAuthenticatedError, with: :not_authenticated\n  rescue_from ActiveEntry::NotAuthorizedError, with: :not_authorized\n\n  private\n\n  def not_authenticated exception\n    flash[:danger] = \"You are not authenticated! Code: #{exception.error[:code]}\"\n    redirect_to root_path\n  end\n\n  def not_authorized exception\n    flash[:danger] = \"You are not authorized to call this action! Code: #{exception.error[:code]}\"\n    redirect_to root_path\n  end\nend\n```\n\nBut you can pass in whatever you want into your error hash.\n\n## Testing\nYou can easily test your policies in RSpec.\n\nWe've created some helpers for your tests. Import them first:\n```ruby\n# spec/support/active_entry.rb\nrequire \"active_entry/rspec\"\n```\n\nNow let's start with the generator:\n\n```shell\n$ rails g rspec:policy Users\n```\n\nThis will generate a spec for the `UsersPolicy` located in `spec/policies/users_policy_spec.rb`\n\n```ruby\nrequire \"rails_helper\"\n\nRSpec.describe UsersPolicy, type: :policy do\n  pending \"add some examples to (or delete) #{__FILE__}\"\nend\n```\n\nNow you can easily test every decision maker with the `be_authenticated_for` and `be_authorized_for` matchers.\n\n```ruby\nrequire \"rails_helper\"\n\nRSpec.describe UsersPolicy, type: :policy do\n  describe UsersPolicy::Authentication do\n    subject { UsersPolicy::Authentication }\n\n    context \"anonymous\" do\n      it { is_expected.to_not be_authenticated_for :index }\n      it { is_expected.to be_authenticated_for :new }\n      it { is_expected.to be_authenticated_for :create }\n      it { is_expected.to_not be_authenticated_for :edit }\n      it { is_expected.to_not be_authenticated_for :update }\n      it { is_expected.to_not be_authenticated_for :destroy }\n      it { is_expected.to_not be_authenticated_for :restore }\n    end\n\n    context \"signed in\" do\n      before { Current.user = build :user }\n      \n      it { is_expected.to be_authenticated_for :index }\n      it { is_expected.to be_authenticated_for :new }\n      it { is_expected.to be_authenticated_for :create }\n      it { is_expected.to be_authenticated_for :edit }\n      it { is_expected.to be_authenticated_for :update }\n      it { is_expected.to be_authenticated_for :destroy }\n      it { is_expected.to be_authenticated_for :restore }\n    end\n  end\n\n  describe UsersPolicy::Authorization do\n    subject { UsersPolicy::Authorization }\n\n    let(:user) { build :user }\n    \n    context \"anonymous\" do\n      it { is_expected.to be_authorized_for :index }\n      it { is_expected.to be_authorized_for :new }\n      it { is_expected.to be_authorized_for :create }\n      it { is_expected.to be_authorized_for :show, user: user }\n      it { is_expected.to_not be_authorized_for :edit, user: user }\n      it { is_expected.to_not be_authorized_for :update, user: user }\n      it { is_expected.to_not be_authorized_for :destroy, user: user }\n      it { is_expected.to_not be_authorized_for :restore, user: user }\n    end\n\n    context \"if @user is Current.user\" do\n      before { Current.user = user }\n      \n      it { is_expected.to be_authorized_for :show, user: user }\n      it { is_expected.to be_authorized_for :edit, user: user }\n      it { is_expected.to be_authorized_for :update, user: user }\n      it { is_expected.to be_authorized_for :destroy, user: user }\n      it { is_expected.to be_authorized_for :restore, user: user }\n    end\n\n    context \"if @user is not Current.user\" do\n      before { Current.user = build :user }\n\n      it { is_expected.to be_authorized_for :show, user: user }\n      it { is_expected.to_not be_authorized_for :edit, user: user }\n      it { is_expected.to_not be_authorized_for :update, user: user }\n      it { is_expected.to_not be_authorized_for :destroy, user: user }\n      it { is_expected.to_not be_authorized_for :restore, user: user }\n    end\n  end\nend\n```\n\n## Differences to Action Policy\n[Action Policy](https://github.com/palkan/action_policy) is an awesome gem which works pretty similar to Active Entry. But there are some differences:\n\n### Action Policy expects a performing subject and a target object\n```ruby\nclass PostPolicy \u003c ApplicationPolicy\n  def update?\n    # `user` is a performing subject,\n    # `record` is a target object (post we want to update)\n    user.admin? || (user.id == record.user_id)\n  end\nend\n```\n\nIn Active Entry you can pass in anything you want into the decision maker, which is accessible as instance variables. See Variables.\n\nOne strategy is not better than the other. It's just our preference.\n\n### Policies in Action Policy are for Resources/Models\nIf you have a `Post` model, you have a `PostPolicy` in Action Policy. In Active Entry you create policies for controllers. So if you have a `PostsController`, you have a `PostsPolicy`.\nWe like to build access control logic around controller endpoints.\n\n### Action Policy performs only authorization\nActive Entry does technically also not provide authentication mechanisms. It's just that you place your authentication logic in an authentication decision maker.\nWe like both authentication and authorization logic in the same place but seperated hence `UsersPolicy::Authentication` and `UsersPolicy::Authorization`.\n\n## Contributing\nCreate pull requests on Github and help us to improve this Gem. There are some guidelines to follow:\n\n * Follow the conventions\n * Test all your implementations\n * Document methods that aren't self-explaining (we are using [YARD](http://yardoc.org/))\n\n## License\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0x7466%2Factive_entry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0x7466%2Factive_entry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0x7466%2Factive_entry/lists"}