Skip to content

2. Users & Authentication

Nicolas Erlichman edited this page Aug 18, 2022 · 11 revisions

Authentication

The first thing that we'll need to add to the application are users that can sign up and log in.

In this step we'll see how to do just that with the support of the gem (external library) devise.

Adding devise

The Gemfile file allows you to specify what gem dependencies are needed for your Rails application.

We can add devise (or any other gem) to our application by adding the following to the Gemfile:

...
gem "pg", "~> 1.1"
# ===========
gem 'devise', "~> 4.8.1"
# ===========
...

And then running the following in your console:

foo@bar:~$ bundle install

Lastly, to finish installing devise we need to run:

foo@bar:~$ rails generate devise:install

Adding devise-token-auth

Devise by itself is not enough if we are doing an api only rails app, like we do in this workshop. In this cases, we can add the gem devise-token-auth to bridge the gap.

Let's add to our Gemfile:

...
gem 'devise', "~> 4.8.1"
# ===========
gem 'devise_token_auth', '~> 1.2', github: 'lynndylanhurley/devise_token_auth',
                                   ref: '5b1a5e19450f3755ce5ebe2f631b40c876ffc22d'
# ===========
...

and run:

foo@bar:~$ bundle install

Create the file config/initializers/devise_token_auth.rb with the following content:

# frozen_string_literal: true

DeviseTokenAuth.setup do |config|
  # By default the authorization headers will change after each request. The
  # client is responsible for keeping track of the changing tokens. Change
  # this to false to prevent the Authorization header from changing after
  # each request.
  config.change_headers_on_each_request = false
  
  # Limiting the token_cost to just 4 in testing will increase the performance of
  # your test suite dramatically. The possible cost value is within range from 4
  # to 31. It is recommended to not use a value more than 10 in other environments.
  config.token_cost = Rails.env.test? ? 4 : 10
end

Users

Generating users

We can use devise's generator to automatically create our users model and migration.

foo@bar:~$ rails generate devise user
      invoke  active_record
      create    db/migrate/20220818142458_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

In this workshop we leave specs (tests) as future work. We strongly advise to replace the auto-generated specs and use rspec-rails gem instead.

Migration

We will need to modify the migration generated by devise to add the tokens that will be used by devise-toke-auth. To do that, let's add the following to our migration db/migrate/20220818142458_devise_create_users.rb:

## Tokens
...
# ===========
t.json :tokens
# ===========

t.timestamps null: false
...

That's it, now you can run the migration to update your database

foo@bar:~$ rails db:migrate
== 20220818142458 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0069s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0019s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0012s
== 20220818142458 DeviseCreateUsers: migrated (0.0100s) =======================

Controller and Endpoints

API Controller

First we create an API controller which all other controllers will inherit from:

app/controllers/api/api_controller.rb

# frozen_string_literal: true

module Api
  class ApiController < ActionController::API
    extend DeviseTokenAuth::Concerns::SetUserByToken

    before_action :authenticate_user!

    def route_not_found
      render_generic_error('Route not found', status: :not_found)
    end
  end
end

User authentication endpoints

After that we need to mount devise-token-auth endpoints for our users. We do that by replacing the contents of routes.rb file with the following:

Rails.application.routes.draw do
  mount_devise_token_auth_for 'User', at: 'api/users', controllers: {
    sessions: 'api/users/sessions',
    registrations: 'api/users/registrations',
    passwords: 'api/users/passwords'
  }

  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  # root "articles#index"
end

Conclusion

We are done! We have just created our first model User and we are ready to sign up and login to our application.

By using devise and devise-token-auth in our ApiController every controller that inherits from it will have access to the logged in user by using current_user.

Clone this wiki locally