Skip to content

janko/rodauth-rails

main
Switch branches/tags
Code

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
lib
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

rodauth-rails

Provides Rails integration for the Rodauth authentication framework.

Resources

🔗 Useful links:

🎥 Screencasts:

📚 Articles:

Why Rodauth?

There are already several popular authentication solutions for Rails (Devise, Sorcery, Clearance, Authlogic), so why would you choose Rodauth? Here are some of the advantages that stand out for me:

One common concern is the fact that, unlike most other authentication frameworks for Rails, Rodauth uses Sequel for database interaction instead of Active Record. There are good reasons for this, and to make Rodauth work smoothly alongside Active Record, rodauth-rails configures Sequel to reuse Active Record's database connection.

Installation

Add the gem to your Gemfile:

gem "rodauth-rails", "~> 1.0"

# gem "jwt",      require: false # for JWT feature
# gem "rotp",     require: false # for OTP feature
# gem "rqrcode",  require: false # for OTP feature
# gem "webauthn", require: false # for WebAuthn feature

Then run bundle install.

Next, run the install generator:

$ rails generate rodauth:install

Or if you want Rodauth endpoints to be exposed via JSON API:

$ rails generate rodauth:install --json # regular authentication using the Rails session
# or
$ rails generate rodauth:install --jwt # token authentication via the "Authorization" header
$ bundle add jwt

This generator will create a Rodauth app and configuration with common authentication features enabled, a database migration with tables required by those features, a mailer with default templates, and a few other files.

Feel free to remove any features you don't need, along with their corresponding tables. Afterwards, run the migration:

$ rails db:migrate

For your mailer to be able to generate email links, you'll need to set up default URL options in each environment. Here is a possible configuration for config/environments/development.rb:

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

Usage

Routes

Because requests to Rodauth endpoints are handled by a Rack middleware (and not a Rails controller), Rodauth routes will not show in rails routes.

Use the rodauth:routes rake task to view the list of endpoints based on currently loaded features:

$ rails rodauth:routes
Routes handled by RodauthApp:

  GET/POST  /login                   rodauth.login_path
  GET/POST  /create-account          rodauth.create_account_path
  GET/POST  /verify-account-resend   rodauth.verify_account_resend_path
  GET/POST  /verify-account          rodauth.verify_account_path
  GET/POST  /change-password         rodauth.change_password_path
  GET/POST  /change-login            rodauth.change_login_path
  GET/POST  /logout                  rodauth.logout_path
  GET/POST  /remember                rodauth.remember_path
  GET/POST  /reset-password-request  rodauth.reset_password_request_path
  GET/POST  /reset-password          rodauth.reset_password_path
  GET/POST  /verify-login-change     rodauth.verify_login_change_path
  GET/POST  /close-account           rodauth.close_account_path

Using this information, you can add some basic authentication links to your navigation header:

<% if rodauth.logged_in? %>
  <%= link_to "Sign out", rodauth.logout_path, method: :post %>
<% else %>
  <%= link_to "Sign in", rodauth.login_path %>
  <%= link_to "Sign up", rodauth.create_account_path %>
<% end %>

These routes are fully functional, feel free to visit them and interact with the pages. The templates that ship with Rodauth aim to provide a complete authentication experience, and the forms use Bootstrap markup.

Current account

The #current_account method is defined in controllers and views, which returns the model instance of the currently logged in account. If the account doesn't exist in the database, the session will be cleared.

current_account #=> #<Account id=123 email="user@example.com">
current_account.email #=> "user@example.com"

Pass the configuration name to retrieve accounts belonging to other Rodauth configurations:

current_account(:admin)

This just delegates to the #rails_account method on the Rodauth object.

Custom account model

The #current_account method will try to infer the account model class from the configured table name. If that fails, you can set the account model manually:

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    rails_account_model Authentication::Account # custom model name
  end
end

Requiring authentication

You'll likely want to require authentication for certain parts of your app, redirecting the user to the login page if they're not logged in. You can do this in your Rodauth app's routing block, which helps keep the authentication logic encapsulated:

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  # ...
  route do |r|
    # ...
    r.rodauth # route rodauth requests

    # require authentication for /dashboard/* and /account/* routes
    if r.path.start_with?("/dashboard") || r.path.start_with?("/account")
      rodauth.require_authentication # redirect to login page if not authenticated
    end
  end
end

You can also require authentication at the controller layer:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  private

  def authenticate
    rodauth.require_authentication # redirect to login page if not authenticated
  end
end
# app/controllers/dashboard_controller.rb
class DashboardController < ApplicationController
  before_action :authenticate
end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :authenticate, except: [:index, :show]
end

Routing constraints

In some cases it makes sense to require authentication at the Rails router level. You can do this via the built-in authenticated routing constraint:

# config/routes.rb
Rails.application.routes.draw do
  constraints Rodauth::Rails.authenticated do
    # ... authenticated routes ...
  end
end

If you want additional conditions, you can pass in a block, which is called with the Rodauth instance:

# config/routes.rb
Rails.application.routes.draw do
  # require multifactor authentication to be setup
  constraints Rodauth::Rails.authenticated { |rodauth| rodauth.uses_two_factor_authentication? } do
    # ...
  end
end

The current account can be retrieved via the #rails_account method:

# config/routes.rb
Rails.application.routes.draw do
  # require user to be admin
  constraints Rodauth::Rails.authenticated { |rodauth| rodauth.rails_account.admin? } do
    # ...
  end
end

You can specify the Rodauth configuration by passing the configuration name:

# config/routes.rb
Rails.application.routes.draw do
  constraints Rodauth::Rails.authenticated(:admin) do
    # ...
  end
end

If you need something more custom, you can always create the routing constraint manually:

# config/routes.rb
Rails.application.routes.draw do
  constraints -> (r) { !r.env["rodauth"].logged_in? } do # or "rodauth.admin"
    # routes when the user is not logged in
  end
end

Views

The templates built into Rodauth are useful when getting started, but soon you'll want to start editing the markup. You can run the following command to copy Rodauth templates into your Rails app:

$ rails generate rodauth:views

This will generate views for Rodauth features you have currently enabled into the app/views/rodauth directory, provided that RodauthController is set for the main configuration.

You can pass a list of Rodauth features to the generator to create views for these features (this will not remove any existing views):

$ rails generate rodauth:views login create_account lockout otp

Or you can generate views for all features:

$ rails generate rodauth:views --all

Use --name to generate views for a different Rodauth configuration:

$ rails generate rodauth:views webauthn --name admin

Page titles

The generated configuration sets title_instance_variable to make page titles available in your views via @page_title instance variable, which you can then use in your layout:

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    title_instance_variable :@page_title
    # ...
  end
end
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title><%= @page_title || "Default title" %></title>
    <!-- ... -->
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

If you're already setting page titles via content_for, you can use it in generated Rodauth views, giving it the result of the corresponding *_page_title method:

<!-- app/views/rodauth/login.html.erb -->
<%= content_for :page_title, rodauth.login_page_title %>
<!-- ... -->
<!-- app/views/rodauth/change_password.html.erb -->
<%= content_for :page_title, rodauth.change_password_page_title %>
<!-- ... -->

Layout

To use different layouts for different Rodauth views, you can compare the request path in the layout method:

# app/controllers/rodauth_controller.rb
class RodauthController < ApplicationController
  layout :rodauth_layout

  private

  def rodauth_layout
    case request.path
    when rodauth.login_path,
         rodauth.create_account_path,
         rodauth.verify_account_path,
         rodauth.verify_account_resend_path,
         rodauth.reset_password_path,
         rodauth.reset_password_request_path
      "authentication"
    else
      "dashboard"
    end
  end
end

Turbo

Turbo has been disabled by default on all built-in and generated view templates, because some Rodauth actions (multi-phase login, adding recovery codes) aren't Turbo-compatible, as they return 200 responses on POST requests.

That being said, most of Rodauth is Turbo-compatible, so feel free to enable Turbo for actions where you want to use it.

Mailer

The install generator will create RodauthMailer with default email templates, and configure Rodauth features that send emails as part of the authentication flow to use it.

# app/mailers/rodauth_mailer.rb
class RodauthMailer < ApplicationMailer
  def verify_account(account_id, key)
    # ...
  end
  def reset_password(account_id, key)
    # ...
  end
  def verify_login_change(account_id, old_login, new_login, key)
    # ...
  end
  def password_changed(account_id)
    # ...
  end
  # def email_auth(account_id, key)
  # ...
  # end
  # def unlock_account(account_id, key)
  # ...
  # end
end
# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    create_reset_password_email do
      RodauthMailer.reset_password(account_id, reset_password_key_value)
    end
    create_verify_account_email do
      RodauthMailer.verify_account(account_id, verify_account_key_value)
    end
    create_verify_login_change_email do |_login|
      RodauthMailer.verify_login_change(account_id, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_key_value)
    end
    create_password_changed_email do
      RodauthMailer.password_changed(account_id)
    end
    # create_email_auth_email do
    #   RodauthMailer.email_auth(account_id, email_auth_key_value)
    # end
    # create_unlock_account_email do
    #   RodauthMailer.unlock_account(account_id, unlock_account_key_value)
    # end
    send_email do |email|
      # queue email delivery on the mailer after the transaction commits
      db.after_commit { email.deliver_later }
    end
    # ...
  end
end

This configuration calls #deliver_later, which uses Active Job to deliver emails in a background job. It's generally recommended to send emails asynchronously for better request throughput and the ability to retry deliveries. However, if you want to send emails synchronously, you can modify the configuration to call #deliver_now instead.

If you're using a background processing library without an Active Job adapter, or a 3rd-party service for sending transactional emails, see this wiki page on how to set it up.

Migrations

The install generator will create a migration for tables used by the Rodauth features enabled by default. For any additional features, you can use the migration generator to create the corresponding tables:

$ rails generate rodauth:migration otp sms_codes recovery_codes
# db/migration/*_create_rodauth_otp_sms_codes_recovery_codes.rb
class CreateRodauthOtpSmsCodesRecoveryCodes < ActiveRecord::Migration
  def change
    create_table :account_otp_keys do |t| ... end
    create_table :account_sms_codes do |t| ... end
    create_table :account_recovery_codes do |t| ... end
  end
end

Custom migration name

You can change the default migration name:

$ rails generate rodauth:migration email_auth --name create_account_email_auth_keys
# db/migration/*_create_account_email_auth_keys
class CreateAccountEmailAuthKeys < ActiveRecord::Migration
  def change
    create_table :account_email_auth_keys do |t| ... end
  end
end

Model

The rodauth-model gem provides a Rodauth::Model mixin that can be included into the account model, which defines a password attribute and associations for tables used by enabled authentication features.

class Account < ActiveRecord::Base # Sequel::Model
  include Rodauth::Rails.model # or `Rodauth::Rails.model(:admin)`
end

The password attribute can be used to set or clear the password hash. It handles both storing the password hash in a column on the accounts table, or in a separate table.

account = Account.create!(email: "user@example.com", password: "secret")

# when password hash is stored in a column on the accounts table
account.password_hash #=> "$2a$12$k/Ub1I2iomi84RacqY89Hu4.M0vK7klRnRtzorDyvOkVI.hKhkNw."

# when password hash is stored in a separate table
account.password_hash #=> #<Account::PasswordHash...> (record from `account_password_hashes` table)
account.password_hash.password_hash #=> "$2a$12$k/Ub1..." (inaccessible when using database authentication functions)

account.password = nil # clears password hash
account.password_hash #=> nil

The associations are defined for tables used by enabled authentication features:

account.remember_key #=> #<Account::RememberKey> (record from `account_remember_keys` table)
account.active_session_keys #=> [#<Account::ActiveSessionKey>,...] (records from `account_active_session_keys` table)

See the rodauth-model documentation for more details.

Multiple configurations

If you need to handle multiple types of accounts that require different authentication logic, you can create new configurations for them. This is done by creating new Rodauth::Rails::Auth subclasses, and registering them under a name.

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  configure RodauthMain          # primary configuration
  configure RodauthAdmin, :admin # secondary configuration

  route do |r|
    r.rodauth         # route primary rodauth requests
    r.rodauth(:admin) # route secondary rodauth requests
  end
end
# app/misc/rodauth_admin.rb
class RodauthAdmin < Rodauth::Rails::Auth
  configure do
    # ... enable features ...
    prefix "/admin"
    session_key_prefix "admin_"
    remember_cookie_key "_admin_remember" # if using remember feature

    # search views in `app/views/admin/rodauth` directory
    rails_controller { Admin::RodauthController }
  end
end
# app/controllers/admin/rodauth_controller.rb
class Admin::RodauthController < ApplicationController
end

Then in your application you can reference the secondary Rodauth instance:

rodauth(:admin).login_path #=> "/admin/login"

You'll likely want to save the information of which account belongs to which configuration to the database. See this guide on how you can do that.

Sharing configuration

If there are common settings that you want to share between Rodauth configurations, you can do so via inheritance:

# app/misc/rodauth_base.rb
class RodauthBase < Rodauth::Rails::Auth
  # common settings that are shared between multiple configurations
  configure do
    enable :login, :logout
    login_return_to_requested_location? true
    logout_redirect "/"
    # ...
  end
end
# app/misc/rodauth_main.rb
class RodauthMain < RodauthBase # inherit common settings
  configure do
    # ... customize main ...
  end
end
# app/misc/rodauth_admin.rb
class RodauthAdmin < RodauthBase # inherit common settings
  configure do
    # ... customize admin ...
  end
end

Outside of a request

Calling actions

In some cases you might need to use Rodauth more programmatically. If you want to perform authentication operations outside of request context, Rodauth ships with the internal_request feature just for that.

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    enable :internal_request
  end
end
# primary configuration
RodauthApp.rodauth.create_account(login: "user@example.com", password: "secret")
RodauthApp.rodauth.verify_account(account_login: "user@example.com")

# secondary configuration
RodauthApp.rodauth(:admin).close_account(account_login: "user@example.com")

Generating URLs

For generating authentication URLs outside of a request use the path_class_methods plugin:

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    enable :path_class_methods
    create_account_route "register"
  end
end
# primary configuration
RodauthApp.rodauth.create_account_path # => "/register"
RodauthApp.rodauth.verify_account_url(key: "abc123") #=> "https://example.com/verify-account?key=abc123"

# secondary configuration
RodauthApp.rodauth(:admin).close_account_path(foo: "bar") #=> "/admin/close-account?foo=bar"

Calling instance methods

If you need to access Rodauth methods not exposed as internal requests, you can use Rodauth::Rails.rodauth to retrieve the Rodauth instance used by the internal_request feature:

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    enable :internal_request # this is required
  end
end
account = Account.find_by!(email: "user@example.com")
rodauth = Rodauth::Rails.rodauth(account: account) #=> #<RodauthMain::InternalRequest ...>

rodauth.compute_hmac("token") #=> "TpEJTKfKwqYvIDKWsuZhkhKlhaBXtR1aodskBAflD8U"
rodauth.open_account? #=> true
rodauth.two_factor_authentication_setup? #=> true
rodauth.password_meets_requirements?("foo") #=> false
rodauth.locked_out? #=> false

In addition to the :account option, the Rodauth::Rails.rodauth method accepts any options supported by the internal_request feature.

# main configuration
Rodauth::Rails.rodauth(env: { "HTTP_USER_AGENT" => "programmatic" })
Rodauth::Rails.rodauth(session: { two_factor_auth_setup: true })

# secondary configuration
Rodauth::Rails.rodauth(:admin, params: { "param" => "value" })

Testing

For system and integration tests, which run the whole middleware stack, authentication can be exercised normally via HTTP endpoints. See this wiki page for some examples.

For controller tests, you can log in accounts by modifying the session:

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action -> { rodauth.require_authentication }

  def index
    # ...
  end
end
# test/controllers/articles_controller_test.rb
class ArticlesControllerTest < ActionController::TestCase
  test "required authentication" do
    get :index

    assert_response 302
    assert_redirected_to "/login"
    assert_equal "Please login to continue", flash[:alert]

    account = Account.create!(email: "user@example.com", password: "secret", status: "verified")
    login(account)

    get :index
    assert_response 200

    logout

    get :index
    assert_response 302
    assert_equal "Please login to continue", flash[:alert]
  end

  private

  # Manually modify the session into what Rodauth expects.
  def login(account)
    session[:account_id] = account.id
    session[:authenticated_by] = ["password"] # or ["password", "totp"] for MFA
  end

  def logout
    session.clear
  end
end

If you're using multiple configurations with different session prefixes, you'll need to make sure to use those in controller tests as well:

class RodauthAdmin < Rodauth::Rails::Auth
  configure do
    session_key_prefix "admin_"
  end
end
# in a controller test:
session[:admin_account_id] = account.id
session[:admin_authenticated_by] = ["password"]

If you want to access the Rodauth instance in controller tests, you can do so through the controller instance:

# in a controller test:
@controller.rodauth         #=> #<RodauthMain ...>
@controller.rodauth(:admin) #=> #<RodauthAdmin ...>

Configuring

Configuration methods

The rails feature rodauth-rails loads provides the following configuration methods:

Name Description
rails_render(**options) Renders the template with given render options.
rails_csrf_tag Hidden field added to Rodauth templates containing the CSRF token.
rails_csrf_param Value of the name attribute for the CSRF tag.
rails_csrf_token Value of the value attribute for the CSRF tag.
rails_check_csrf! Verifies the authenticity token for the current request.
rails_controller_instance Instance of the controller with the request env context.
rails_controller Controller class to use for rendering and CSRF protection.
rails_account_model Model class connected with the accounts table.

For the list of configuration methods provided by Rodauth, see the feature documentation.

Defining custom methods

All Rodauth configuration methods are just syntax sugar for defining instance methods on the auth class. You can also define your own custom methods on the auth class:

class RodauthMain < Rodauth::Rails::Auth
  configure do
    # ...
    password_match? { |password| ldap_valid?(password) }
    # ...
  end

  # Example external identities table
  def identities
    db[:account_identities].where(account_id: account_id).all
  end

  private

  # Example LDAP authentication
  def ldap_valid?(password)
    SimpleLdapAuthenticator.valid?(account[:email], password)
  end
end
rodauth.identities #=> [{ provider: "facebook", uid: "abc123", ... }, ...]

Rails URL helpers

Inside Rodauth configuration and the route block you can access Rails route helpers through #rails_routes:

# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    login_redirect { rails_routes.activity_path }
  end
end

Calling controller methods

When using Rodauth before/after hooks or generally overriding your Rodauth configuration, in some cases you might want to call methods defined on your controllers. You can do so with rails_controller_eval, for example:

# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  private
  def setup_tracking(account_id)
    # ... some implementation ...
  end
end
# app/misc/rodauth_main.rb
class RodauthMain < Rodauth::Rails::Auth
  configure do
    after_create_account do
      rails_controller_eval { setup_tracking(account_id) }
    end
  end
end

Single-file configuration

If you would prefer to have all Rodauth logic contained inside a single file, you call Rodauth::Rails::App.configure with a block, which will create an anonymous auth class.

# app/misc/rodauth_app.rb
class RodauthApp < Rodauth::Rails::App
  # primary configuration
  configure do
    enable :login, :logout, :create_account, :verify_account
    # ...
  end

  # secondary configuration
  configure(:admin) do
    enable :email_auth, :single_session
    # ...
  end

  route do |r|
    # ...
  end
end

How it works

Rack middleware

The railtie inserts Rodauth::Rails::Middleware at the end of the middleware stack, which calls your Rodauth app around each request.

$ rails middleware
# ...
# use Rodauth::Rails::Middleware
# run MyApp::Application.routes

It can be inserted at any point in the middleware stack:

Rodauth::Rails.configure do |config|
  config.middleware = false # disable auto-insertion
end

Rails.application.config.middleware.insert_before AnotherMiddleware, Rodauth::Rails::Middleware

The middleware retrieves the Rodauth app via Rodauth::Rails.app, which is specified as a string to keep the class autoloadable and reloadable in development.

Rodauth::Rails.configure do |config|
  config.app = "RodauthApp"
end

In addition to Zeitwerk compatibility, this extra layer catches Rodauth redirects that happen on the controller level (e.g. when calling rodauth.require_authentication in a before_action filter).

Roda app

The Rodauth::Rails::App class is a Roda subclass that provides a convenience layer for Rodauth:

  • uses Action Dispatch flash messages
  • provides syntax sugar for loading the rodauth plugin
  • saves Rodauth object(s) to Rack env hash
  • propagates edited headers to Rails responses

Configure block

The configure call loads the rodauth plugin. By convention, it receives an auth class and configuration name as positional arguments (forwarded as :auth_class and :name plugin options), a block for anonymous auth classes, and also accepts any additional plugin options.

class RodauthApp < Rodauth::Rails::App
  # named auth class
  configure(RodauthMain)
  configure(RodauthAdmin, :admin)

  # anonymous auth class
  configure { ... }
  configure(:admin) { ... }

  # plugin options
  configure(RodauthMain, json: :only)
end

Route block

The route block is called for each request, before it reaches the Rails router, and it's yielded the request object.

class RodauthApp < Rodauth::Rails::App
  route do |r|
    # called before each request
  end
end

Routing prefix

If you use a routing prefix, you don't need to add a call to r.on like with vanilla Rodauth, as r.rodauth has been modified to automatically route the prefix.

class RodauthApp < Rodauth::Rails::App
  configure do
    prefix "/user"
  end

  route do |r|
    r.rodauth # no need to wrap with `r.on("user") { ... }`
  end
end

Auth class

The Rodauth::Rails::Auth class is a subclass of Rodauth::Auth, which preloads the rails rodauth feature, sets HMAC secret to Rails' secret key base, and modifies some configuration defaults.

class RodauthMain < Rodauth::Rails::Auth
  configure do
    # authentication configuration
  end
end

Rodauth feature

The rails Rodauth feature loaded by Rodauth::Rails::Auth provides the main part of the Rails integration for Rodauth:

  • uses Action View for template rendering
  • uses Action Dispatch for CSRF protection
  • runs Action Controller callbacks and rescue from blocks around Rodauth requests
  • uses Action Mailer to create and deliver emails
  • uses Action Controller instrumentation around Rodauth requests
  • uses Action Mailer's default URL options when calling Rodauth outside of a request

Controller

The Rodauth app stores the Rodauth::Rails::Auth instances in the Rack env hash, which is then available in your Rails app:

request.env["rodauth"]       #=> #<RodauthMain>
request.env["rodauth.admin"] #=> #<RodauthAdmin> (if using multiple configurations)

For convenience, this object can be accessed via the #rodauth method in views and controllers:

class MyController < ApplicationController
  def my_action
    rodauth         #=> #<RodauthMain>
    rodauth(:admin) #=> #<RodauthAdmin> (if using multiple configurations)
  end
end
<% rodauth         #=> #<RodauthMain> %>
<% rodauth(:admin) #=> #<RodauthAdmin> (if using multiple configurations) %>

Sequel

Rodauth uses the Sequel library for database interaction, which offers powerful APIs for building advanced queries (it supports SQL expressions, database-agnostic date arithmetic, SQL function calls).

If you're using Active Record in your application, the rodauth:install generator automatically configures Sequel to reuse ActiveRecord's database connection, using the sequel-activerecord_connection gem.

This means that, from the usage perspective, Sequel can be considered just as an implementation detail of Rodauth.

Rodauth defaults

rodauth-rails changes some of the default Rodauth settings for easier setup:

Database functions

By default, on PostgreSQL, MySQL, and Microsoft SQL Server Rodauth uses database functions to access password hashes, with the user running the application unable to get direct access to password hashes. This reduces the risk of an attacker being able to access password hashes and use them to attack other sites.

While this is useful additional security, it is also more complex to set up and to reason about, as it requires having two different database users and making sure the correct migration is run for the correct user.

To keep with Rails' "convention over configuration" doctrine, rodauth-rails disables the use of database functions, though you can always turn it back on.

use_database_authentication_functions? true

To create the database functions, pass the Sequel database object into the Rodauth method for creating database functions:

# db/migrate/*_create_rodauth_database_functions.rb
require "rodauth/migrations"

class CreateRodauthDatabaseFunctions < ActiveRecord::Migration
  def up
    Rodauth.create_database_authentication_functions(DB)
  end

  def down
    Rodauth.drop_database_authentication_functions(DB)
  end
end

Account statuses

The recommended Rodauth migration stores possible account status values in a separate table, and creates a foreign key on the accounts table, which ensures only a valid status value will be persisted. Unfortunately, this doesn't work when the database is restored from the schema file, in which case the account statuses table will be empty. This happens in tests by default, but it's also not unusual to do it in development.

To address this, rodauth-rails uses a status column without a separate table. If you're worried about invalid status values creeping in, you may use enums instead. Alternatively, you can always go back to the setup recommended by Rodauth.

# in the migration:
create_table :account_statuses do |t|
  t.string :name, null: false, unique: true
end
execute "INSERT INTO account_statuses (id, name) VALUES (1, 'Unverified'), (2, 'Verified'), (3, 'Closed')"

create_table :accounts do |t|
  # ...
  t.references :status, foreign_key: { to_table: :account_statuses }, null: false, default: 1
  # ...
end
  class RodauthMain < Rodauth::Rails::Auth
    configure do
      # ...
-     account_status_column :status
      # ...
    end
  end

Deadline values

To simplify changes to the database schema, rodauth-rails configures Rodauth to set deadline values for various features in Ruby, instead of relying on the database to set default column values.

You can easily change this back:

set_deadline_values? false

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the rodauth-rails project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.