# rodauth-rails Provides Rails integration for the [Rodauth] authentication framework. ## Resources * [Rodauth documentation](http://rodauth.jeremyevans.net/documentation.html) * [rodauth-rails wiki](https://github.com/janko/rodauth-rails/wiki) * [Rails demo](https://github.com/janko/rodauth-demo-rails) ## Installation Add the gem to your Gemfile: ```rb gem "rodauth-rails", "~> 0.3" ``` Then run `bundle install`. Next, run the install generator: ``` $ rails generate rodauth:install ``` The generator will create the following files: * Rodauth migration at `db/migrate/*_create_rodauth.rb` * Rodauth initializer at `config/initializers/rodauth.rb` * Sequel initializer at `config/initializers/sequel.rb` for ActiveRecord integration * Rodauth app at `lib/rodauth_app.rb` * Rodauth controller at `app/controllers/rodauth_controller.rb` * Account model at `app/models/account.rb` ### Migration The migration file creates tables required by Rodauth. You're encouraged to review the migration, and modify it to only create tables for features you intend to use. ```rb # db/migrate/*_create_rodauth.rb class CreateRodauth < ActiveRecord::Migration def change create_table :accounts do |t| ... end create_table :account_password_hashes do |t| ... end create_table :account_password_reset_keys do |t| ... end create_table :account_verification_keys do |t| ... end create_table :account_login_change_keys do |t| ... end create_table :account_remember_keys do |t| ... end # ... end end ``` Once you're done, you can run the migration: ``` $ rails db:migrate ``` ### Rodauth initializer The Rodauth initializer assigns the constant for your Rodauth app, which will be called by the Rack middleware that's added in front of your Rails router. ```rb # config/initializers/rodauth.rb Rodauth::Rails.configure do |config| config.app = "RodauthApp" end ``` ### Sequel initializer Rodauth uses [Sequel] for database interaction. If you're using ActiveRecord, an additional initializer will be created which configures Sequel to use the ActiveRecord connection. ```rb # config/initializers/sequel.rb require "sequel/core" # initialize Sequel and have it reuse Active Record's database connection DB = Sequel.postgres(extensions: :activerecord_connection) ``` ### Rodauth app Your Rodauth app is created in the `lib/` directory, which comes with a default set of authentication features enabled, as well as extensive examples on ways you can configure authentication behaviour. ```rb # lib/rodauth_app.rb class RodauthApp < Rodauth::Rails::App configure do # authentication configuration end route do |r| # request handling end end ``` Note that Rails doesn't autoload files in the `lib/` directory by default, so make sure to add `lib/` to your `config.autoload_paths`: ```rb # config/application.rb module YourApp class Application < Rails::Application # ... config.autoload_paths += %W[#{config.root}/lib] end end ``` ### Controller Your Rodauth app will by default use `RodauthController` for view rendering and CSRF protection. ```rb # app/controllers/rodauth_controller.rb class RodauthController < ApplicationController end ``` ### Account Model Rodauth stores user accounts in the `accounts` table, so the generator will also create an `Account` model for custom use. ```rb # app/models/account.rb class Account < ApplicationRecord end ``` ## Getting started Let's start by adding some basic authentication navigation links to our home page: ```erb
Authenticated as: <%= current_account.email %>
``` ### Requiring authentication Next, we'll likely want to require authentication for certain sections/pages of our app. We can do this in our Rodauth app's routing block, which helps keep the authentication logic encapsulated: ```rb # lib/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 ``` We can also require authentication at the controller layer: ```rb # app/controllers/application_controller.rb class ApplicationController < ActionController::Base private def authenticate rodauth.require_authentication # redirect to login page if not authenticated end end ``` ```rb # app/controllers/dashboard_controller.rb class DashboardController < ApplicationController before_action :authenticate end ``` ```rb # app/controllers/posts_controller.rb class PostsController < ApplicationController before_action :authenticate, except: [:index, :show] end ``` Or at the Rails router level: ```rb # config/routes.rb Rails.application.routes.draw do constraints -> (r) { r.env["rodauth"].require_authentication } do namespace :admin do # ... end end end ``` ### Views The templates built into Rodauth are useful when getting started, but at some point we'll probably want more control over the markup. For that we can run the following command: ```sh $ rails generate rodauth:views ``` This will generate views for the default set of Rodauth features into the `app/views/rodauth` directory, which will be automatically picked up by the `RodauthController`. You can pass a list of Rodauth features to the generator to create views for these features (this will not remove any existing views): ```sh $ rails generate rodauth:views login create_account lockout otp ``` Or you can generate views for all features: ```sh $ rails generate rodauth:views --all ``` You can also tell the generator to create views into another directory (in this case don't forget to rename the Rodauth controller accordingly). ```sh # generates views into app/views/authentication $ rails generate rodauth:views --name authentication ``` #### Layout To use different layouts for different Rodauth views, you can compare the request path in the layout method: ```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.reset_password_path, rodauth.reset_password_request_path "authentication" else "dashboard" end end end ``` ### Mailer Rodauth may send emails as part of the authentication flow. Most email settings can be customized: ```rb # lib/rodauth_app.rb class RodauthApp < Rodauth::Rails::App # ... configure do # ... # general settings email_from "no-reply@myapp.com" email_subject_prefix "[MyApp] " send_email(&:deliver_later) # ... # feature settings verify_account_email_subject "Verify your account" verify_account_email_body { "Verify your account by visting this link: #{verify_account_email_link}" } # ... end end ``` This is convenient when starting out, but eventually you might want to use your own mailer. You can start by running the following command: ```sh $ rails generate rodauth:mailer ``` This will create a `RodauthMailer` with the associated mailer views in `app/views/rodauth_mailer` directory. ```rb # app/mailers/rodauth_mailer.rb class RodauthMailer < ApplicationMailer def verify_account(recipient, email_link) ... end def reset_password(recipient, email_link) ... end def verify_login_change(recipient, old_login, new_login, email_link) ... end def password_changed(recipient) ... end # def email_auth(recipient, email_link) ... end # def unlock_account(recipient, email_link) ... end end ``` You can then uncomment the lines in your Rodauth configuration to have it call your mailer. If you've enabled additional authentication features, make sure to override their `send_*_email` methods as well. ```rb # lib/rodauth_app.rb class RodauthApp < Rodauth::Rails::App # ... configure do # ... send_reset_password_email do mailer_send(:reset_password, email_to, reset_password_email_link) end send_verify_account_email do mailer_send(:verify_account, email_to, verify_account_email_link) end send_verify_login_change_email do |login| mailer_send(:verify_login_change, login, verify_login_change_old_login, verify_login_change_new_login, verify_login_change_email_link) end send_password_changed_email do mailer_send(:password_changed, email_to) end # send_email_auth_email do # mailer_send(:email_auth, email_to, email_auth_email_link) # end # send_unlock_account_email do # mailer_send(:unlock_account, email_to, unlock_account_email_link) # end auth_class_eval do # queue email delivery on the mailer after the transaction commits def mailer_send(type, *args) db.after_commit do RodauthMailer.public_send(type, *args).deliver_later end end end # ... end end ``` ## How it works ### Middleware rodauth-rails inserts a `Rodauth::Rails::Middleware` into your middleware stack, which calls your Rodauth app for each request, before the request reaches the Rails router. ```sh $ rails middleware ... use Rodauth::Rails::Middleware run MyApp::Application.routes ``` The Rodauth app stores the `Rodauth::Auth` instance in the Rack env hash, which is then available in your Rails app: ```rb request.env["rodauth"] #=> #