{"id":15430998,"url":"https://github.com/kyuden/banken","last_synced_at":"2025-10-11T09:32:19.996Z","repository":{"id":45915643,"uuid":"45186501","full_name":"kyuden/banken","owner":"kyuden","description":"Simple and lightweight authorization library for Rails","archived":false,"fork":false,"pushed_at":"2024-02-12T14:56:45.000Z","size":58,"stargazers_count":269,"open_issues_count":2,"forks_count":15,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-01-17T10:34:52.635Z","etag":null,"topics":["authorization","banken","rails"],"latest_commit_sha":null,"homepage":"https://github.com/kyuden/banken","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/kyuden.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-10-29T13:57:40.000Z","updated_at":"2024-09-01T12:43:03.000Z","dependencies_parsed_at":"2024-06-18T21:23:03.729Z","dependency_job_id":"88ea54f4-6602-412d-9f1a-d96516fae092","html_url":"https://github.com/kyuden/banken","commit_stats":{"total_commits":90,"total_committers":6,"mean_commits":15.0,"dds":"0.12222222222222223","last_synced_commit":"f905bdbf670ff92ccbc52c1dedd9a9092dbcab44"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyuden%2Fbanken","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyuden%2Fbanken/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyuden%2Fbanken/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kyuden%2Fbanken/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kyuden","download_url":"https://codeload.github.com/kyuden/banken/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":236075664,"owners_count":19090971,"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":["authorization","banken","rails"],"created_at":"2024-10-01T18:19:52.833Z","updated_at":"2025-10-11T09:32:14.659Z","avatar_url":"https://github.com/kyuden.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg width=\"350\" src=\"https://raw.github.com/wiki/kyuden/banken/images/banken.png\"\u003e\n\n[![Build Status](https://img.shields.io/travis/kyuden/banken/master.svg)](https://travis-ci.org/kyuden/banken)\n[![Code Climate](https://codeclimate.com/github/kyuden/banken/badges/gpa.svg)](https://codeclimate.com/github/kyuden/banken)\n[![Gem Version](https://badge.fury.io/rb/banken.svg)](https://badge.fury.io/rb/banken)\n\nSimple and lightweight authorization library for Rails inspired by Pundit.\nBanken provides a set of helpers which restricts what resources\na given user is allowed to access.\n\nIn first, Look this tutorial:\n - [Tutorial](https://github.com/kyuden/banken/wiki/Tutorial)\n - [Tutorial (Japanese)](https://github.com/kyuden/banken/wiki/Tutorial-(japanese))\n\n========\n## What's the difference between Banken and Pundit?\n - [The difference between Banken and Pundit](https://github.com/kyuden/banken/wiki/The-difference-between-Banken-and-Pundit)\n - [The difference between Banken and Pundit (Japanese)](https://github.com/kyuden/banken/wiki/The-difference-between-Banken-and-Pundit-(Japanese))\n\n \n## Installation\n\n``` ruby\ngem \"banken\"\n```\n\nInclude Banken in your application controller:\n\n``` ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::Base\n  include Banken\n  protect_from_forgery\nend\n```\n\nOptionally, you can run the generator, which will set up an application loyalty\nwith some useful defaults for you:\n\n``` sh\nrails g banken:install\n```\n\nAfter generating your application loyalty, restart the Rails server so that Rails\ncan pick up any classes in the new `app/loyalties/` directory.\n\n## Loyalties\n\nBanken is focused around the notion of loyalty classes. We suggest that you put\nthese classes in `app/loyalties`. This is a simple example that allows updating\na post if the user is an admin, or if the post is unpublished:\n\n``` ruby\n# app/loyalties/posts_loyalty.rb\nclass PostsLoyalty\n  attr_reader :user, :post\n\n  def initialize(user, post)\n    @user = user\n    @post = post\n  end\n\n  def update?\n    user.admin? || post.unpublished?\n  end\nend\n```\n\nAs you can see, this is just a plain Ruby class. Banken makes the following\nassumptions about this class:\n\n- The class has the same name as some kind of controller class, only suffixed\n  with the word \"Loyalty\".\n- The first argument is a user. In your controller, Banken will call the\n  `current_user` method to retrieve what to send into this argument\n- The second argument is optional, whose authorization you want to check.\n  This does not need to be an ActiveRecord or even an ActiveModel object,\n  it can be anything really.\n- The class implements some kind of query method, in this case `update?`.\n  Usually, this will map to the name of a particular controller action.\n\nThat's it really.\n\nUsually you'll want to inherit from the application loyalty created by the\ngenerator, or set up your own base class to inherit from:\n\n``` ruby\n# app/loyalties/posts_loyalty.rb\nclass PostsLoyalty \u003c ApplicationLoyalty\n  def update?\n    user.admin? || record.unpublished?\n  end\nend\n```\n\nIn the generated `ApplicationLoyalty`, the optional object is called `record`.\n\nSupposing that you are in PostsController, Banken now lets you do\nthis in your controller:\n\n``` ruby\n# app/controllers/posts_controller.rb\nclass PostsController \u003c ApplicationController\n  def update\n    @post = Post.find(params[:id])\n    authorize! @post\n    if @post.update(post_params)\n      redirect_to @post\n    else\n      render :edit\n    end\n  end\nend\n```\n\nThe authorize method automatically infers from controller name that `Posts` will have a matching\n`PostsLoyalty` class, and instantiates this class, handing in the current user\nand the given optional object. It then infers from the action name, that it should call\n`update?` on this instance of the loyalty. In this case, you can imagine that\n`authorize!` would have done something like this:\n\n``` ruby\nraise \"not authorized\" unless PostsLoyalty.new(current_user, @post).update?\n```\n\nIf you don't have an optional object for the first argument to `authorize!`, then you can pass\nthe class. For example:\n\nLoyalty:\n```ruby\n# app/loyalties/posts_loyalty.rb\nclass PostsLoyalty \u003c ApplicationLoyalty\n  def admin_list?\n    user.admin?\n  end\nend\n```\n\nController:\n```ruby\n# app/controllers/posts_controller.rb\nclass PostsController \u003c ApplicationController\n  def admin_list\n    authorize!\n    # Rest of controller action\n  end\nend\n```\n\nYou can easily get a hold of an instance of the loyalty through the `loyalty`\nmethod in both the view and controller. This is especially useful for\nconditionally showing links or buttons in the view:\n\n``` erb\n\u003c% if loyalty(@post, :posts).update? %\u003e\n  \u003c%= link_to \"Edit post\", edit_post_path(@post) %\u003e\n\u003c% end %\u003e\n```\n\nIf you are using namespace in your controller and policy,\nyou can access the policy passing string like 'admin/posts' as a second argument.\nBelow calls Admin::PostsLoyalty.\n\n``` erb\n\u003c% if loyalty(@post, 'admin/posts').update? %\u003e\n  \u003c%= link_to \"Edit post\", edit_post_path(@post) %\u003e\n\u003c% end %\u003e\n```\n\n## Ensuring loyalties are used\n\nBanken adds a method called `verify_authorized` to your controllers. This\nmethod will raise an exception if `authorize!` has not yet been called. You\nshould run this method in an `after_action` to ensure that you haven't\nforgotten to authorize the action. For example:\n\n``` ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::Base\n  after_action :verify_authorized, except: :index\nend\n```\n\n\nIf you're using `verify_authorized` in your controllers but need to\nconditionally bypass verification, you can use `skip_authorization`.\nThese are useful in circumstances where you don't want to disable verification for the\nentire action, but have some cases where you intend to not authorize.\n\n```ruby\n# app/controllers/posts_controller.rb\nclass PostsController \u003c ApplicationController\n  def show\n    record = Record.find_by(attribute: \"value\")\n    if record.present?\n      authorize! record\n    else\n      skip_authorization\n    end\n  end\nend\n```\n\nIf you need to perform some more sophisticated logic or you want to raise a custom\nexception you can use the two lower level method `banken_authorization_performed?` which\nreturn `true` or `false` depending on whether `authorize!` have been called, respectively.\n\n## Just plain old Ruby\n\nAs you can see, Banken doesn't do anything you couldn't have easily done\nyourself.  It's a very small library, it just provides a few neat helpers.\nTogether these give you the power of building a well structured, fully working\nauthorization system without using any special DSLs or funky syntax or\nanything.\n\nRemember that all of the loyalty is just plain Ruby classes,\nwhich means you can use the same mechanisms you always use to DRY things up.\nEncapsulate a set of permissions into a module and include them in multiple\nloyalties. Use `alias_method` to make some permissions behave the same as\nothers. Inherit from a base set of permissions. Use metaprogramming if you\nreally have to.\n\n## Generator\n\nUse the supplied generator to generate loyalties:\n\n``` sh\nrails g banken:loyalty posts\n```\n\n## Closed systems\n\nIn many applications, only logged in users are really able to do anything. If\nyou're building such a system, it can be kind of cumbersome to check that the\nuser in a loyalty isn't `nil` for every single permission.\n\nWe suggest that you define a filter that redirects unauthenticated users to the\nlogin page. As a secondary defence, if you've defined an ApplicationLoyalty, it\nmight be a good idea to raise an exception if somehow an unauthenticated user\ngot through. This way you can fail more gracefully.\n\n``` ruby\n# app/loyalties/application_loyalty.rb\nclass ApplicationLoyalty\n  def initialize(user, record)\n    raise Banken::NotAuthorizedError, \"must be logged in\" unless user\n    @user = user\n    @record = record\n  end\nend\n```\n\n## Rescuing a denied Authorization in Rails\n\nBanken raises a `Banken::NotAuthorizedError` you can\n[rescue_from](http://guides.rubyonrails.org/action_controller_overview.html#rescue-from)\nin your `ApplicationController`. You can customize the `user_not_authorized`\nmethod in every controller.\n\n```ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::Base\n  protect_from_forgery\n  include Banken\n\n  rescue_from Banken::NotAuthorizedError, with: :user_not_authorized\n\n  private\n\n  def user_not_authorized\n    flash[:alert] = \"You are not authorized to perform this action.\"\n    redirect_to(request.referrer || root_path)\n  end\nend\n```\n\n## Creating custom error messages\n\n`NotAuthorizedError`s provide information on what query (e.g. `:create?`), what\ncontroller (e.g. `PostsController`), and what loyalty (e.g. an instance of\n`PostsLoyalty`) caused the error to be raised.\n\nOne way to use these `query`, `record`, and `loyalty` properties is to connect\nthem with `I18n` to generate error messages. Here's how you might go about doing\nthat.\n\n```ruby\n# app/controllers/application_controller.rb\nclass ApplicationController \u003c ActionController::Base\n  rescue_from Banken::NotAuthorizedError, with: :user_not_authorized\n\n  private\n\n  def user_not_authorized(exception)\n    loyalty_name = exception.loyalty.class.to_s.underscore\n\n    flash[:error] = t \"#{loyalty_name}.#{exception.query}\", scope: \"banken\", default: :default\n    redirect_to(request.referrer || root_path)\n  end\nend\n```\n\n```yaml\nen:\n  banken:\n    default: 'You cannot perform this action.'\n    posts_loyalty:\n      update?: 'You cannot edit this post!'\n      create?: 'You cannot create posts!'\n```\n\nOf course, this is just an example. Banken is agnostic as to how you implement\nyour error messaging.\n\n## Customize Banken user\n\nIn some cases your controller might not have access to `current_user`, or your\n`current_user` is not the method that should be invoked by Banken. Simply\ndefine a method in your controller called `banken_user`.\n\n```ruby\ndef banken_user\n  User.find_by_other_means\nend\n```\n\n## Additional context\n\nBanken strongly encourages you to model your application in such a way that the\nonly context you need for authorization is a user object and a domain model that\nyou want to check authorization for. If you find yourself needing more context than\nthat, consider whether you are authorizing the right domain model, maybe another\ndomain model (or a wrapper around multiple domain models) can provide the context\nyou need.\n\nBanken does not allow you to pass additional arguments to loyalties for precisely\nthis reason.\n\nHowever, in very rare cases, you might need to authorize based on more context than just\nthe currently authenticated user. Suppose for example that authorization is dependent\non IP address in addition to the authenticated user. In that case, one option is to\ncreate a special class which wraps up both user and IP and passes it to the loyalty.\n\n``` ruby\nclass UserContext\n  attr_reader :user, :ip\n\n  def initialize(user, ip)\n    @user = user\n    @ip = ip\n  end\nend\n\n# app/controllers/application_controller.rb\nclass ApplicationController\n  include Banken\n\n  def banken_user\n    UserContext.new(current_user, request.ip)\n  end\nend\n```\n\n## Strong parameters\n\nIn Rails 4 (or Rails 3.2 with the\n[strong_parameters](https://github.com/rails/strong_parameters) gem),\nmass-assignment protection is handled in the controller. With Banken you can\ncontrol which attributes a user has access to update via your loyalties. You can\nset up a `permitted_attributes` method in your loyalty like this:\n\n```ruby\n# app/loyalties/posts_loyalty.rb\nclass PostsLoyalty \u003c ApplicationLoyalty\n  def permitted_attributes\n    if user.admin? || user.owner_of?(post)\n      [:title, :body, :tag_list]\n    else\n      [:tag_list]\n    end\n  end\nend\n```\n\nYou can now retrieve these attributes from the loyalty:\n\n```ruby\n# app/controllers/posts_controller.rb\nclass PostsController \u003c ApplicationController\n  def update\n    @post = Post.find(params[:id])\n    if @post.update_attributes(post_params)\n      redirect_to @post\n    else\n      render :edit\n    end\n  end\n\n  private\n\n  def post_params\n    params.require(:post).permit(loyalty(@post).permitted_attributes)\n  end\nend\n```\n\nHowever, this is a bit cumbersome, so Banken provides a convenient helper method:\n\n```ruby\n# app/controllers/posts_controller.rb\nclass PostsController \u003c ApplicationController\n  def update\n    @post = Post.find(params[:id])\n    if @post.update_attributes(permitted_attributes(@post))\n      redirect_to @post\n    else\n      render :edit\n    end\n  end\nend\n```\n\n# License\n\nLicensed under the MIT license, see the separate LICENSE.txt file.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyuden%2Fbanken","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyuden%2Fbanken","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyuden%2Fbanken/lists"}