Repository navigation
2. Users & 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.
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 installLastly, to finish installing devise we need to run:
foo@bar:~$ rails generate devise:installDevise 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 installCreate 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
endWe 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 :usersIn this workshop we leave specs (tests) as future work. We strongly advise to replace the auto-generated specs and use rspec-rails gem instead.
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) =======================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
endAfter 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"
endWe 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.