Setup very basic authentication with Devise in Rails 7
Tags: rails, authentication, ruby, tutorial
Published: 2023-05-15 04:03:15

## Introduction

`devise` is the most popular gem in Rails for authentication. However, it's also the most confusing gem for beginners. It is so confusing that it even suggests not using it if you're new to Rails or web development in its readme.

I'd like to show you how to install and set up `devise` for its very basic functionalities so you can have a starting point to play around with it.

## Environment

- Ruby 3.1.2
- Rails 7.0.4.3
- devise 4.9.2

## Steps

### Initialize a new Rails app
```bash
$ rails new basic_devise_with_rails7
```

For later use, we also add an index page
```bash
$ rails generate controller pages index
```
It will create
1. a new controller, `PagesController`
2. a new action `index` in PagesController

### Set up `devise`
#### 1. install devise
```ruby
gem 'devise', '~> 4.9.2'
```
Then run `bundle install` to install it.

#### 2. Setup default settings
`devise` cannot be used right after installation. You need to initialize it.
To initialize devise, execute
```bash
$ rails generate devise:install
```

It will also pop up several suggestions for settings. I'm going to list those suggestions and how to deal with them.

```
Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

     * Not required *
```

According to the suggestions above, we can do the following things.

#### 1. Set up default URL for action_mailer
You can add it in `config/environments/development.rb` 
```ruby
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
```

#### 2. define a root path in route `config/routes.rb`
We just added `pages/index` in the routes so we can set it as the root path.
```ruby
Rails.application.routes.draw do
  root 'pages#index'
  get 'pages/index'
end
```

#### 3. add flash message templates
We can add the HTML below in `app/views/layouts/application.html.erb` to show flash messages.
```html
<body>
  <p class="notice"><%= notice %></p>
  <p class="alert"><%= alert %></p>
  <%= yield %>
</body>
```

#### 4. No need to customize Devise views
We don't need to customize Devise views because we only need the basic functionality

All settings are ok now! 

### Login & Sign up

#### Create User model

We need to choose a kind of Model to be used for authentication, that is, you want who can sign up and log in. That model is usually called `User` so we execute:
```bash
$ rails generate devise User
```
It will 
- create `app/models/user.rb`
- create `db/migrate/2023xxxxxxxxx_devise_create_users.rb`
- insert `devise_for :users` into `config/routes.rb`

Then execute
```
$ rails db:migrate
```

#### Link to Devise pages
We need a link to the **login page**, so we update the code in `app/views/pages/index.html.erb` and make it the **Home page**
```html
<h1>Home</h1>
<%= link_to 'Sign in', new_user_session_path %>
```
and start rails by 
```
$ rails start
```
Open http://localhost:3000, you should see this page:

![Home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybzaoqm4b69tbgsghd2w.png)

Yeah, it feels so plain and even ugly. Please don't mind it because it's not a CSS-styling tutorial...

Click **Sign in**, you should see the **login page**

![Sign in page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w66eilhd4tebs5j0v7tl.png)


We don't have any users at this point so we click **Sign Up** link and we will be led to the **Sign Up page**

![Sign up page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ngvklhtrviuijuotr3b.png)

Register a new user by entering the email and password and then clicking the **Sign up** button. You will be redirected to the Home index page.

![Sign up successfully](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/osrjiv0vgli4htuqc0l6.png)

### Checkpoint 1

At this point, we can review what we've done so far. Check the database to see what happened there. I'm using DBeaver here see. You can use whatever tool you like.
You can find a `users` table is created successfully. 

![users table](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9gspdcegky5vdqx2kkqz.png)

There are many columns in the `users` table, but it's easy to know that

* `email` should store the email of a user
* `encrypted_password` should store the password word of a user and it's encrypted

Check the content of the `users` table, you can find the row of the user we just created with its corresponding data.

![users table content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3zsru9rgspiyn148gtd1.png)

### Show User information
Click the **Sign in** link again, it will show **You are already signed in**

![Already signed in](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pinhv5wijgdjp3cvjji5.png)

It seems like you will be signed in automatically after signing up successfully so we are redirected to the Home. Whatever, it does not feel right. Since `Sign in` is not a valid link, maybe we don't need to see it when we're signed in. We can update the code in `app/views/pages/index.html.erb`
```html
<% if user_signed_in? %>
  <h1>Welcome Home, <%= current_user.email %></h1>
<% else %>
  <h1>Home</h1>
  <%= link_to 'Sign in', new_user_session_path %>
<% end %>
```
Refresh the page and get

![show user information](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3nm9oi4ujtl52t70o1p6.png)

### Checkpoint 2

There are three helpers you can use in controllers and views based on which model is used for `devise`. We have set `User` so we have:
- `user_signed_in?` return true if now a user is signed in
- `current_user` return the `User` instance of the current signed-in user, it'll be nil if there's no signed-in user
- `user_session` return a hash session stored in the cookie. We can ignore it for now.

### Logout

It would be nice if we can log out and let the page return to its initial state. We can update the code again in `app/views/pages/index.html.erb`
```ruby
<% if user_signed_in? %>
  <h1>Welcome Home, <%= current_user.email %></h1>
  <%= link_to 'Sign out', destroy_user_session_path, data: { turbo_method: :delete } %>
<% else %>
  <h1>Home</h1>
  <%= link_to 'Sign in', new_user_session_path %>
<% end %>
```
The page will be like:

![Home page with sign out](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xgvrrpwc59gudhw04kbb.png)

Click **Sign out** button, then you can be signed out!

![Sign out successfully](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zuskhc23agsz6r7k1z84.png)

You can go to Log in page again and login by entering the email and the password.

![Log in again](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16o3jamaa7x6pz4aim9b.png)

### Checkpoint 3

For signing out, we want to send a `DELETE` HTTP request to the server. Rails 7 has taken [turbo-rails](https://github.com/hotwired/turbo-rails) as default. Therefore, we can add `data: { turbo_method: :delete }` to achieve that.

```ruby
<%= link_to 'Sign out', destroy_user_session_path, data: { turbo_method: :delete } %>
```

Congratulations! You just finished the very basic usage of Sign Up/Login/Log out by `devise`! 

Let's review some more details about it in the following sections.

### Devise Routings

We use some mysterious paths out of nowhere in the view:

- `new_user_session_path`
- `destroy_user_session_path`

Where are these paths come from?

When we did `rails generate devise User`, it inserted `devise_for :users` into `config/routes.rb` and those paths are generated dynamically. 

You can use `rails routes` to check what routes that line generates:
![Routes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j68e3g69e5r6s7b8wasp.png)

### Flash messages

Why there was text showed up on the top of the page? For example,

- Welcome! You have signed up successfully
- You are already signed in.
- Signed out successfully.

Do you remember you added
```html
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>
```
in `app/views/layouts/application.html.erb`.  
It can show messages stored in
```ruby
flash[:notice]
# or
flash[:alert]
```
![flash div](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xgpqs8d2expttgsnhp6g.png)

### Other

#### Why do we need to set the default URL for config.action_mailer?
There are several functionalities of `devise` will send email to users. For example, it can send a confirmation letter after a user registers to prevent spam accounts.
If you want to deploy it to production and you have a domain can be used then set it in `config/environments/production.rb` 

```ruby
config.action_mailer.default_url_options = { host: 'your-domain.com' }
```

#### Why I need to login by email and why password has to be at least 6 characters long?

`rails generate devise:install` generates 2 files:
- `config/initializers/devise.rb`
- `config/locales/devise.en.yml`
You can configure all the devise settings in `devise.rb`, including iwhether you want to use `username` to log in or set that password must be at least 8 characters long.

## Conclusion

`devise` is not a simple gem to learn and use at this moment. However, it's a very powerful tool. If you only run Rails as an API server, it can also support JWT-based authentication. You can check out its wiki page to learn more about it https://github.com/heartcombo/devise/wiki/How-Tos

I hope this article can help you begin the journey with `devise`. 

## Code
All the code can be found at https://github.com/kevinluo201/basic_devise_with_rails7