# Rails Authentication From Scratch

# Part 1: User Signup

[Rails Authentication Demo App](https://rails-authentication.avi.nyc)

[Source code](https://github.com/aviflombaum/rails-authentication)

There are certainly a lot of amazing authentication options in the Rails ecosystems.

If I was building a large application with a lot of multi-user expectations, I would use [Devise](https://github.com/heartcombo/devise). It is a great solution and it has been tried and tested over a decade.

However, if I was building a small application that only needed a few users, I would probably build my own authentication system. That's what this post is about, just to teach you how to do it should you want to and to also explain how authentication works in Rails.

If you want to follow along, I've setup this application starting from Avis Rails Starter.

## The User Model

The first step is to create your user model. This is a pretty standard model, but there are a few things to note. The first is that we are using the `has_secure_password` method. This is a Rails method that will require a `password_digest` column to store the encrypted user password.

`rails g model user email:string password_digest:string`

That will generate the appropriate [migration](https://github.com/aviflombaum/rails-authentication/blob/main/db/migrate/20230827204823_create_users.rb)

The next step is to add `bcrypt` to your Gemfile. This is the gem that will handle the encryption of the password.

And then finally, to turn on `has_secure_password` in our `User` model. While we are here, we will also add a validation to make sure that the email is unique.

[`app/models/user.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/models/user.rb)

```ruby
class User < ApplicationRecord
  validates :email, presence: true, uniqueness: true
  has_secure_password
end
```

## The Signups Controller

I've seen people name the controller responsible for registration or signup a lot of things. I've seen `UsersController`, `RegistrationsController`, `SignupsController`, and more. I personally like `SignupsController` because it is the most descriptive. It is the controller that handles the signup process.

The RESTful way to do it would be the `UsersControllers` but I like to leave that to actually managing the users, not the process of signing one up. I tend to think an application has a logical resource of a `Signup` and should have a controller the deals specifically with that.

Let's make the controller and clean up the routes related to signup.

`rails g controller signups new create`

[`config/routes.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/config/routes.rb)

```ruby
Rails.application.routes.draw do
  resource :signup, only: [:new, :create]
end
```

We're using `resource` as `Signups` are a singular resource that require no `show` or `edit` type functionality.

### New Signup Form

Let's now setup `new#signups` and the registration form.

[`app/controllers/signups_controller.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/controllers/signups_controller.rb)

```ruby
class SignupsController < ApplicationController
  def new
    @user = User.new
  end
end
```

Because we're using my rails starter which includes shadcn-ui, we can make a pretty style-ish registration form.

[`app/views/signups/new.html.erb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/views/signups.html.erb)

```erb
<%= render_form_for(@user, url: signup_path) do |f| %>
  <div class="grid gap-2">
    <div class="grid gap-1">
      <div class="grid gap-1">
        <%= f.label :email, class: "sr-only" %>
        <%= f.email_field :email, placeholder: "name@example.com",
                                  autocomplete: :email,
                                  autocorrect: "email",
                                  autocapitalize: "none" %>
      </div>
      <div class="grid gap-1">
        <%= f.label :password, class: "sr-only" %>
        <%= f.password_field :password, placeholder: "Your password...",
                                        autocomplete: "current-password" %>
      </div>
      <div class="grid gap-1">
        <%= f.label :password_confirmation, class: "sr-only" %>
        <%= f.password_field :password_confirmation, placeholder: "Confirm your password...",
                              autocomplete: "current-password" %>
      </div>
      <%= f.submit "Create Account" %>
    </div>
  </div>
<% end %>
```

`render_form_for` is a thin wrapper on-top of `form_for` that comes with the shadcn-ui library.

But because of Tailwind and shadcn-ui, or registration form looks like:

![Registration](https://img.avi.nyc/GTzfjwRB+)

### Create Signup Action

With the form complete, we can build out `create#signups`. The responsibility of the rest of the `signups` controller is to create the user by email and set the password information using the [`has_secure_password`](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) helpers.

Note that even though the database has the column of `password_digest`, the attributes we're writing too are `password` and `password_confirmation` because that's how `has_secure_password` works. It uses those attributes to encrypt the password and make sure it matches the confirmation.

`app/controllers/signups_controller.rb`
```ruby
class SignupsController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to root_path, notice: "You have successfully signed up!"
    else
      flash.now[:alert] = "There was a problem signing up."
      render :new, status: 422
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end
```

The happy path of registration should work now but lets handle the validation. The first thing is because our form works with Turbo, we have to send an error code status of 422, which is an [`unprocessable_entity`](http://www.railsstatuscodes.com/unprocessable_entity.html) saying that if the `User` instance fails validation, Turbo should re-render the response from the form submission within the current document.

### Create Signup Form Validation

A lot of the frontend for validation is handled by [`render_form_for`](https://shadcn.rails-components.com/docs/components/forms) decorating the fields with errors with an error class that the stylesheet will border red. But we can display the flash alert as a [toast](https://shadcn.rails-components.com/docs/components/toast) and spell out the validation errors.

The entire form ends up looking like:

[`app/views/signup/new.html.erb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/views/signups/new.html.erb)
```erb
<div class="container flex flex-row items-center justify-center">
  <div class="my-20">
    <div class="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
      <div class="flex flex-col space-y-2 text-center">
        <h1 class="text-2xl font-semibold tracking-tight">Create an account</h1>
        <p class="text-muted-foreground text-sm">Enter your email and your password below.</p>
        <% if @user.errors.any? %>
          <div class="text-left">
            <%= render_alert variant: :error, title: "Failed to Create Account" do %>
              <% @user.errors.full_messages.each do |message| %>
                <p><%= message %></p>
              <% end %>
            <% end %>
          </div>
        <% end %>
      </div>
      <div class="grid gap-6">
        <%= render_form_for(@user, url: signup_path) do |f| %>
          <div class="grid gap-2">
            <div class="grid gap-1">
              <div class="grid gap-1">
                <%= f.label :email, class: "sr-only" %>
                <%= f.email_field :email, placeholder: "name@example.com",
                                        autocomplete: :email,
                                        autocorrect: "email",
                                        autocapitalize: "none" %>
              </div>
              <div class="grid gap-1">
                <%= f.label :password, class: "sr-only" %>
                <%= f.password_field :password, placeholder: "Your password...",
                                              autocomplete: "current-password" %>
              </div>
              <div class="grid gap-1">
                <%= f.label :password, class: "sr-only" %>
                <%= f.password_field :password_confirmation, placeholder: "Confirm your password...",
                                              autocomplete: "current-password" %>
              </div>
              <%= f.submit "Create Account" %>
            </div>
          </div>
        <% end %>
        <p class="text-muted-foreground px-8 text-center text-sm">
          By clicking continue, you agree to our
          <a
          class="hover:text-primary underline underline-offset-4"
          href="/terms">Terms of Service</a>
          and
          <a
          class="hover:text-primary underline underline-offset-4"
          href="/privacy">Privacy Policy</a>.
        </p>
      </div>
    </div>
  </div>
</div>
<%= render_toast header: flash[:alert], variant: :destructive if flash[:alert] %>
```

# Part 2: User Sessions

Now that users can signup for the application, let's let them login. I like to create a `SessionsController` and draw the following routes to handle login and logout.

`rails g controller sessions new create destroy`

[`config/routes.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/config/routes.rb)
```rb
get "/login", to: "sessions#new", as: "login"
post "/sessions", to: "sessions#create"
get "/logout", to: "sessions#destroy", as: "logout"
```

Let's build the Login form.

## Login Form

`sessions#new` doesn't need anything special, a blank action is enough.

[`app/controllers/sessions_controller.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/controllers/sessions_controller.rb)

```rb
class SessionsController < ApplicationController
  def new
  end
end
```

The login from:

[`app/views/sessions/new.html.erb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/views/sessions/new.html.erb)
```erb
<%= render_form_for(User.new, as: :user, url: sessions_path) do |f| %>
  <div class="grid gap-2">
    <div class="grid gap-1">
      <%= f.label :email, class: "sr-only" %>
      <%= f.email_field :email, placeholder: "name@example.com",
                                autocomplete: :email,
                                autocorrect: "email",
                                autocapitalize: "none" %>
    </div>
    <div class="grid gap-1">
      <%= f.label :password, class: "sr-only" %>
      <%= f.password_field :password, placeholder: "Your password...",
                                      autocomplete: "current-password" %>
    </div>
    <%= f.submit "Login" %>
  </div>
<% end %>
```

The from is going to submit to `sessions#create`. I'm only instantiating a `User` to give my form something to wrap around and name fields correctly, but we're not creating a new `User`. You could easily use `form_tag` for this form.

## Login Logic

The login logic is going to live in `ApplicationController` and looks like this:

[`app/controllers/application_controller.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/controllers/application_controller.rb)
```rb
class ApplicationController < ActionController::Base
  private

  def sign_in(user)
    session[:user_id] = user.id
  end

  def current_user
    @current_user = User.find_by(id: session[:user_id]) if session[:user_id]
  end
  helper_method :current_user

  def signed_in?
    !!current_user
  end
  helper_method :signed_in?
end
```

These methods constitute the entirety of the login logic. `sign_in` will store the user's id in the session. `current_user` will load the user from the database from the session if it exists. You can check if a user is `signed_in?` by the presence of `current_user`.

**This is why I like implementing my own authentication system.** It's really not that much, that is the bulk of the key logic.

## Logout Logic

We've already got a route ready for logging a user out. Let's implement the logic.

[`app/controllers/sessions_controller.rb`](https://github.com/aviflombaum/rails-authentication/blob/main/app/controllers/sessions_controller.rb)

```rb
class SessionsController < ApplicationController
  # Rest of Controller

  def destroy
    session[:user_id] = nil # Or reset_session
    redirect_to root_path, notice: "You have successfully logged out!"
  end
end
```

Depending on whether you want to keep any of the other user session information or completely reset it, logging a user out is as simple as setting the `session[:user_id]` to `nil` or calling `reset_session`.

## Conclusion

That's a basic authentication system in rails, without any bells and whistles. In a follow up post I'll show you [how you can build a password reset](https://code.avi.nyc/reset-password-in-rails-from-scratch) as well as implement omniauth for 3rd party OAuth login and magic login links.

