21. Easy authentication with Rails and Devise

I am at a stage of the website where I require some authentication – in my case, to create content on the website, i.e. to make posts, I will require the user to be authenticated.

If you have not deployed your application you may want to check earlier posts like this one.

Authentication should always be done using libraries, don’t re-invent the wheel.

For rails, there is a well established, production ready library that can be used, Devise.

If you’re looking for authorization, i.e. you’re looking to fine tune which users can have certain abilities to use different actions, you will want to explore other libraries such as cancancan. This will not be covered in this post as it’s a different topic, however they can be used together.

Getting started with Devise

We can follow the getting started section of Github, add gem 'devise' to your Gemfile and execute bundle install in your terminal.

Once this is complete, execute

rails generate devise:install

We continue to follow the instructions, so I will add:

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


# production.rb
  ...
  config.action_mailer.default_url_options = { host: 'api.yazii.co.uk', port: 3000 

Update the production host to your actual host.

Next, we generate the model to describe our user, in this case we will call the model ‘User’ as we just want one type of user currently. You can change the User to Admin or anything you like.

rails generate devise User
...
Running via Spring preloader in process 70548
      invoke  active_record
      create    db/migrate/20210131140502_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

As you can see, this creates the model and migration files as expected. Before we can use it, we have to migrate the database using: rails db:migrate

There is a lot of different configurations that you can apply to Devise, and there is a lot of documentation available on GitHub for this so I will skip this.

Instead, I will demonstrate how you can quickly start testing your implementations using Postman.

First of all, let’s get the API paths that you’re working on, personally I find the documentation slightly confusing about this, but you can simply type this in your terminal:

rails routes | grep users

It should return something similar to this:

This way, even if you update the routes file, you will get the exact routes that you need to test and use.

The GET new_user_registration is the HTTP page for user registration. I am looking to implement an API only registration so it’s not quite what I am looking for, instead i look at:

user_registration 
PATCH     /users(.:format) 
PUT       /users(.:format)                      
DELETE    /users(.:format)                   
POST      /users(.:format)                    

Let’s look at what happens when we try to call this endpoint, but before you do, you may need to place the following into your application_controller

  respond_to :html, :json
  protect_from_forgery with: :null_session, only: Proc.new { |c| c.request.format.json? }  

This is fine if you’re using rails and you’re planning to edit the html view (or potentially use the default).

However me and many others would like to use this as API request. The way to enable Devise to work with API mode is to modify the route like so:

  devise_for :users, defaults: { format: :json }

with default format of json, Devise will no longer send the html response by default. Let’s see what happens when we post a registration request with empty body:

This JSON response is perfect for API only backend implementation and single page applications, such as with react front end.

Let’s put in some ‘valid’ data to test the API;

{
    "user": {
        "email": "test123@email.com",
        "password": "password123"
    }
}

You can then use the sign_in endpoint to log your user in:

And that’s it! you have a basic authentication model incorporated to your site.

Don’t forget to actually make your actions use the authentication by adding this to relevant functions and controllers:

before_action :authenticate_user!

Bonus section – Add another parameter, username

By completing the above, you will have the basic auth implementation working in the backend, i.e. you can register using email and password.

If you want to add an additional field, such as email you’ll require few more small steps.

  • Application controller must permit the additional parameters (or override them in Devise controller if you have it)
  • Create migration for the new columns

That’s pretty much it, let’s try it!

In your application_controller.rb file add the following content:

  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:username])
    devise_parameter_sanitizer.permit(:account_update, keys: [:username])
  end

This allows the username parameter to be passed (along with defaults) to sign_up and account_update.

Check more about sanitiser here if you’re curious.

Now create your migration for user model, mine looks like this:

  def change
    add_column :users, :username, :string, limit: 255, null: false, unique: true
  end

I can do it like this because I have no data, but if you have some, the null validation will fail. If that’s the case, you may want to remove this constraint.

After running rails db:migrate you can start the server and try it with Postman.

And here’s one for signing in:

And that’s it, we can see the username field is now present in our sign in responses.

small tip

if you wish to sign in using a different field to email, find this file: config/initializers/devise.rb and check the section on:

  # config.authentication_keys = [:email]

By default, the authentication keys are email, but you can change it to username for example.

Good luck!