Devise, The Swiss Army Knife of Rails User Authentication.
Tags: rails
Published: 2021-02-04 16:50:14

There are simple rules I have learned to live by when writing code. The first one is Don't Repeat Yourself (DRY), the second is to always make sure I comment my regex, ( _You should comment all your code, but mostly your regex_ ) and finally, always find out if there is a tool that can help you build what you're building faster and better.  

## Why Devise??

Now if you're anything like me, you learnt rails by reading Michael Hartl's book on Rails. The book is a gem (pun intended) and I would recommend it to anyone who wants to learn rails and the TDD methodology. In the book, when building the Users model, Michael uses the `has_secure_password` for user authentication. Now this works pretty well for that example, and it is what I used because it is what I knew. That was until I went on [r/rails](https://www.reddit.com/r/rails) and found out about Devise. 

![WHAT IS DEVISE??](https://dev-to-uploads.s3.amazonaws.com/i/1hwxkkompa1rf55bud2i.png)

Well Devise is this wonderful gem that is used for UserAuthentication. It handles a lot more than I can explain on here, but basically, instead of having to build the specific controller methods for authentication, managing failed log-in attempts, locking accounts and account recovery, you just install devise on your app and you will have all this available to you. 

![How to Devise](https://dev-to-uploads.s3.amazonaws.com/i/7d22im41ofxnt75azfuh.png)

## How to Devise
First off, the docs are always an invaluable resource so I will ask you to refer to that before copy pasting my code. [Devise Repo](https://github.com/heartcombo/devise). 

So let's do basic User authentication. 

### Step 1 - The Model
- Add the Devise gem to your gemfile ` gem 'Devise' ` then run bundle install.

- Run the generator `rails generate devise:install` . At this point, a number of instructions will appear in the console. Among these instructions, you'll need to set up the default URL options for the Devise mailer in each environment. For example in your development environment, (`config/environments/development.rb`)
```
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
```

- Generate the devise model just how you would any other model `rails generate devise user` and then run the migration `rails db:migrate`

-At this point, the official rails documentation recommends restarting the rails app. This should also include stopping spring (`spring stop`), in order to avoid running into weird errors.

Worth noting is that your model will have some interesting code added to it. Specifically, the devise methods in your models will have some options used to configure its modules. It would look something like 
```
 devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable :validatable
```

### Step 2 - Routes
- To configure your routes for devise, add the following line in you `config/routes.rb` file
```
devise_for :users
```

- However, if you need more specific routes, you can just create your routes noramlly and then wrap them in a `devise_scope` block in the router. For instance:
```
devise_scope :user do
    post 'sign_in', to 'devise/sessions#create'
end
```

### Step 3 - Controllers. 
- For the purposes of this example, we will just build the sessions controller for sign in and sign out. 
- The controller using devise would not look that much different from your regular controllers. In this case it would look something like this. 
```
class SessionsController < Devise::SessionsController
  
  # sign in
  def create
    if @user.valid_password?(sign_in_params[:password])
      sign_in "user", @user
      render json: {
        messages: "Signed In Successfully",
        is_success: true,
        data: {user: @user}
      }, status: :ok
    else
      render json: {
        messages: "Signed In Failed - Unauthorized",
        is_success: false,
        data: {}
      }, status: :unauthorized
    end
  end

  private
  def sign_in_params
    params.require(:sign_in).permit :email, :password
  end

  def load_user
    @user = User.find_for_database_authentication(email: sign_in_params[:email])
    if @user
      return @user
    else
      render json: {
        messages: "Cannot get User",
        is_success: false,
        data: {}
      }, status: :failure
    end
  end
end
```

Now that we have our model, routes and controller set up, we can boot up our server with ```rails s``` and test these endpoints with Postman. (or cURL for the CLI diehards) 
The JSON object for the request in Postman would look like this 
```
{
    "user":{
        "email": "email@example.com",
        "password" : "foobar123"
    }
}
```

( *Please note that this assumes you have a user in your database with the email and password shown above* )

when you send the POST request to *localhost:3000/sign_in* you should get that sweet 200-OK response. 

Well, that's basically it. You have devise running on your app providing functionality for User Authentication. 

All that is left now is to restrict  access to the other models only to those users who are signed in. Remember when I called Devise a swiss army knife? Well, it comes bundled with a lot of helper methods, one of which is `:authenticate_user!`

So for any model that you need a user to be authorized before access, just add:
`before_action :authenticate_user!` on it's controller.

This can also be augmented to specific variations like

```before_action :authenticate_user!, only: [:create]```
This restricts the create action to only authorized users who are signed in.

Hopefully, this covers what you need to know to get started with the devise gem. It is not enough though, so again, read the [docs](https://github.com/heartcombo/devise). 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/o4xr1lpcqrpd98w3h234.png)

As a side note, also check out `devise_token_auth` [here](https://github.com/lynndylanhurley/devise_token_auth)