{"id":13879909,"url":"https://github.com/adamcooke/authie","last_synced_at":"2025-05-15T02:08:24.814Z","repository":{"id":21803825,"uuid":"25126408","full_name":"adamcooke/authie","owner":"adamcooke","description":"👮‍♂️ Improve user session security in Ruby on Rails applications with database session storage","archived":false,"fork":false,"pushed_at":"2025-02-20T13:27:53.000Z","size":267,"stargazers_count":246,"open_issues_count":7,"forks_count":25,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-15T00:47:56.731Z","etag":null,"topics":["authentication","persistent-sessions","rails","ruby","session-cookie"],"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/adamcooke.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"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":"2014-10-12T17:23:40.000Z","updated_at":"2025-03-05T16:28:16.000Z","dependencies_parsed_at":"2022-08-17T19:55:25.411Z","dependency_job_id":"af128f17-54b6-4a50-9568-3d089ecaa0f3","html_url":"https://github.com/adamcooke/authie","commit_stats":{"total_commits":158,"total_committers":10,"mean_commits":15.8,"dds":0.6139240506329113,"last_synced_commit":"ce0c89574208091b0165c8133e4dd274f65aae4f"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamcooke%2Fauthie","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamcooke%2Fauthie/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamcooke%2Fauthie/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adamcooke%2Fauthie/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adamcooke","download_url":"https://codeload.github.com/adamcooke/authie/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254259384,"owners_count":22040820,"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":["authentication","persistent-sessions","rails","ruby","session-cookie"],"created_at":"2024-08-06T08:02:38.635Z","updated_at":"2025-05-15T02:08:24.792Z","avatar_url":"https://github.com/adamcooke.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Authie\n\n\u003ca href=\"https://github.com/adamcooke/authie/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/adamcooke/authie/ci.yml?label=CI\u0026logo=github\"\u003e\u003c/a\u003e \u003ca href=\"https://rubygems.org/gems/authie\"\u003e\u003cimg src=\"https://img.shields.io/gem/v/authie?label=RubyGem\u0026logo=rubygems\"\u003e\u003c/a\u003e \u003ca href=\"https://codeclimate.com/github/adamcooke/authie\"\u003e\u003cimg src=\"https://img.shields.io/codeclimate/coverage/adamcooke/authie?label=Test%20Coverage\u0026logo=codeclimate\"\u003e\u003c/a\u003e\n\nThis is a Rails library which provides applications with a database-backed user\nsessions. This ensures that user sessions can be invalidated from the server and\nusers activity can be easily tracked.\n\nThe \"traditional\" way of simply setting a user ID in your session is insecure\nand unwise. If you simply do something like the example below, it means that anyone\nwith access to the session cookie can login as the user whenever and wherever they wish.\n\nTo clarify: while by default Rails session cookies are encrypted, there is\nnothing to allow them to be invalidated if someone were to \"steal\" an encrypted\ncookie from an authenticated user. This could be stolen using a MITM attack or\nsimply by stealing it directly from their browser when they're off getting a coffee.\n\n```ruby\nif user = User.authenticate(params[:username], params[:password])\n  # Don't do this...\n  session[:user_id] = user.id\n  redirect_to root_path, :notice =\u003e \"Logged in successfully!\"\nend\n```\n\nThe design goals behind Authie are:\n\n- Any session can be invalidated instantly from the server without needing to make\n  changes to remote cookies.\n- We can see who is logged in to our application at any point in time.\n- Sessions should automatically expire after a certain period of inactivity.\n- Sessions can be either permanent or temporary.\n\n## Installation\n\nAs usual, just pop this in your Gemfile:\n\n```ruby\ngem 'authie', '~\u003e 4.0'\n```\n\nYou will then need add the database tables Authie needs to your database. You\nshould copy Authie's migrations and then migrate.\n\n```\nrake authie:install:migrations\nrake db:migrate\n```\n\n## Usage\n\nAuthie is just a session manager and doesn't provide any functionality for your authentication or User models. Your `User` model should implement any methods needed to authenticate a username \u0026 password.\n\n### Creating a new session\n\nWhen a user has been authenticated, you can simply set `current_user` to the user\nyou wish to login. You may have a method like this in a controller.\n\n```ruby\nclass SessionsController \u003c ApplicationController\n\n  def create\n    if user = User.authenticate(params[:username], params[:password])\n      create_auth_session(user)\n      redirect_to root_path\n    else\n      flash.now[:alert] = \"Username/password was invalid\"\n    end\n  end\n\nend\n```\n\n### Checking whether user's are logged in\n\nOn any subsequent request, you should make sure that your user is logged in.\nYou may wish to implement a `login_required` controller method which is called\nbefore every action in your application.\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n\n  before_action :login_required\n\n  private\n\n  def login_required\n    return if logged_in?\n\n    redirect_to login_path, :alert =\u003e \"You must login to view this resource\"\n  end\n\nend\n```\n\n### Accessing the current user (and session)\n\nThere are a few controller methods which you can call which will return information about the current session:\n\n- `current_user` - returns the currently logged in user\n- `auth_session` - returns the current auth session\n- `logged_in?` - returns a true if there's a session or false if no user is logged in\n\n### Catching session errors\n\nIf there is an issue with an auth session, an error will be raised which you need\nto catch within your application. The errors which will be raised are:\n\n- `Authie::Session::InactiveSession` - is raised when a session has been de-activated.\n- `Authie::Session::ExpiredSession` - is raised when a session expires.\n- `Authie::Session::BrowserMismatch` - is raised when the browser ID provided does\n  not match the browser ID associated with the session token provided.\n- `Authie::Session::HostMismatch` - is raised when the session is used on a hostname\n  that does not match that which created the session\n\nThe easiest way to rescue these to use a `rescue_from`. For example:\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n\n  rescue_from Authie::Session::ValidityError, :with =\u003e :auth_session_error\n\n  private\n\n  def auth_session_error\n    redirect_to login_path, :alert =\u003e \"Your session is no longer valid. Please login again to continue...\"\n  end\n\nend\n```\n\n### Logging out\n\nIn order to invalidate a session you can simply invalidate it.\n\n```ruby\ndef logout\n  auth_session.invalidate\n  redirect_to login_path, :notice =\u003e \"Logged out successfully.\"\nend\n```\n\n### Default session length\n\nBy default, a session will last for however long it is being actively used in\nbrowser. If the user stops using your application, the session will last for\n12 hours before becoming invalid. You can change this:\n\n```ruby\nAuthie.config.session_inactivity_timeout = 2.hours\n```\n\nThis does not apply if the session is marked as persistent. See below.\n\n### Persisting sessions\n\nIn some cases, you may wish users to have a permanent sessions. In this case,\nyou should ask users after they have logged in if they wish to \"persist\" their\nsession across browser restarts. If they do wish to do this, just do something\nlike this:\n\n```ruby\ndef persist_session\n  auth_session.persist\n  redirect_to root_path, :notice =\u003e \"You will now be remembered!\"\nend\n```\n\nBy default, persistent sessions will last for 2 months before requring the user\nlogs in again. You can increase (or decrease) this if needed:\n\n```ruby\nAuthie.config.persistent_session_length = 12.months\n```\n\n### Accessing all user sessions\n\nIf you want to provide users with a list of their sessions, you can access all\nactive sessions for a user. The best way to do this will be to add a `has_many`\nassociation to your User model.\n\n```ruby\nclass User \u003c ActiveRecord::Base\n  has_many :sessions, :class_name =\u003e 'Authie::SessionModel', :as =\u003e :user, :dependent =\u003e :destroy\nend\n```\n\n### Storing additional data in the user session\n\nIf you need to store additional information in your database-backed database\nsession, then you can use the following methods to achieve this:\n\n```ruby\nauth_session.set :two_factor_seen_at, Time.now\nauth_session.get :two_factor_seen_at\n```\n\n### Invalidating all but current session\n\nYou may wish to allow users to easily invalidate all sessions which aren't their\ncurrent one. Some applications invalidate old sessions whenever a user changes\ntheir password. The `invalidate_others!` method can be called on any\n`Authie::Session` object and will invalidate all sessions which aren't itself.\n\n```ruby\ndef change_password\n  @user.change_password(params[:new_password])\n  auth_session.invalidate_others!\nend\n```\n\n### Sudo functions\n\nIn some applications, you may want to require that the user has recently provided\ntheir password to you before executing certain sensitive actions. Authie provides\nsome methods which can help you keep track of when a user last provided their\npassword in a session and whether you need to prompt them before continuing.\n\n```ruby\n# When the user logs into your application, run the see_password method to note\n# that we have just seen their password.\ndef login\n  if user = User.authenticate(params[:username], params[:password])\n    create_auth_session(user, see_password: true)\n    redirect_to root_path\n  end\nend\n\n# Before executing any dangerous actions, check to see whether the password has\n# recently been seen.\ndef change_password\n  if auth_session.recently_seen_password?\n    # Allow the user to change their password as normal.\n  else\n    # Redirect the user a page which allows them to re-enter their password.\n    # The method here should verify the password is correct and call the\n    # see_password method as above. Once verified, you can return them back to\n    # this page.\n    redirect_to reauth_path(:return_to =\u003e request.fullpath)\n  end\nend\n```\n\nBy default, a password will be said to have been recently seen if it has been\nseen in the last 10 minutes. You can change this configuration if needed:\n\n```ruby\nAuthie.config.sudo_session_timeout = 30.minutes\n```\n\n### Working with two factor authentication\n\nAuthie provides a couple of methods to help you determine when two factor\nauthentication is required for a request. Whenever a user logs in and has\nenabled two factor authentication, you can mark sessions as being permitted.\n\nYou can add the following to your application controller and ensure that it runs\non every request to your application.\n\n```ruby\nclass ApplicationController \u003c ActionController::Base\n\n  before_action :check_two_factor_auth\n\n  def check_two_factor_auth\n    if logged_in? \u0026\u0026 current_user.has_two_factor_auth? \u0026\u0026 !auth_session.two_factored?\n      # If the user has two factor auth enabled, and we haven't already checked it\n      # in this auth session, redirect the user to an action which prompts the user\n      # to do their two factor auth check.\n      redirect_to two_factor_auth_path\n    end\n  end\n\nend\n```\n\nThen, on your two factor auth action, you need to ensure that you mark the auth\nsession as being verified with two factor auth.\n\n```ruby\nclass LoginController \u003c ApplicationController\n\n  skip_before_action :check_two_factor_auth\n\n  def two_factor_auth\n    if user.verify_two_factor_token(params[:token])\n      auth_session.mark_as_two_factored\n      redirect_to root_path, :notice =\u003e \"Logged in successfully!\"\n    end\n  end\n\nend\n```\n\n## Storing IP address countries\n\nAuthie has support for storing the country that an IP address is located in whenever they are saved to the database. To use this, you need to specify a backend to use in the Authie configuration. The backend should respond to `#call(ip_address)`.\n\n```ruby\nAuthie.config.lookup_ip_country_backend = proc do |ip_address|\n  SomeService.lookup_country_from_ip(ip_address)\nend\n```\n\n## Instrumentation/Notification\n\nAuthie will publish events to the ActiveSupport::Notification instrumentation system. The following events are published\nwith the given attributes.\n\n- `set_browser_id.authie` - when a new browser ID is set for a user. Provides `:browser_id` and `:controller` arguments.\n- `cleanup.authie` - when session cleanup is run. Provides no arguments.\n- `touch.authie` - when a session is touched. Provides `:session` argument.\n- `see_password.authie` - when a session sees a password. Provides `:session` argument.\n- `mark_as_two_factor.authie` - when a session has two factor credentials provided. Provides `:session` argument.\n- `session_start.authie` - when a session is started. Provides `:session` argument.\n- `session_invalidate.authie` - when a session is intentionally invalidated. Provides `:session` argument with session model instance.\n- `browser_id_mismatch_error.authie` - when a session is validated when the browser ID does not match. Provides `:session` argument.\n- `invalid_session_error.authie` - when a session is validated when invalid. Provides `:session` argument.\n- `expired_session_error.authie` - when a session is validated when expired. Provides `:session` argument.\n- `inactive_session_error.authie` - when a session is validated when inactive. Provides `:session` argument.\n- `host_mismatch_error.authie` - when a session is validated and the host does not match. Provides `:session` argument.\n\n## Differences for Authie 4.0\n\nAuthie 4.0 introduces a number of changes to the library which are worth noting when upgrading from any version less than 4.\n\n- Authie 4.0 removes the impersonation features which may make a re-appearance in a futre version.\n- All previous callback/events have been replaced with standard ActiveSupport instrumentation notifications.\n- `Authie::SessionModel` has been introduced to represent the instance of the underlying database record.\n- Various methods on Authie::Session (more commonly known as `auth_session`) have been renamed as follows.\n  - `check_security!` is now `validate`\n  - `persist!` is now `persist`\n  - `invalidate!` is now `invalidate`\n  - `touch!` is now `touch`\n  - `set_cookie!` is now `set_cookie` and is now a private method and should not be called directly.\n  - `see_password!` is now `see_password`\n  - `mark_as_two_factored!` is now `mark_as_two_factored`\n- A new `Authie::Session#reset_token` has been added which will generate a new token for a session, save it and update the cookie.\n- When starting a session using `Authie::Session.start` or `create_auth_session` you can provide the following additional options:\n  - `persistent: true` to mark the session as persistent (i.e. give it an expiry time)\n  - `see_password: true` to set the password seen timestamp at the same time as creation\n- If the `extend_session_expiry_on_touch` config option is set to true (default is false), the expiry time for a persistent session will be extended whenver a session is touched.\n- When making a request, the session will be touched **after** the action rather than before. Previously, the `touch_auth_session` method was added before every action and it both validated the session and touched it. Now, there are two separate methods - `validate_auth_session` which is run before every action and `touch_auth_session` runs after every action. If you don't want to touch a session in a request you can either use `skip_around_action :touch_auth_session` or call `skip_touch_auth_session!` anywhere in the action.\n- A new config option called `session_token_length` is available which allows you to change the length of the random token used for sessions (default 64).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamcooke%2Fauthie","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadamcooke%2Fauthie","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadamcooke%2Fauthie/lists"}