You Shall Not Pass: The Ins and Outs of Authentication
Tags: rails, programming, ruby, security
Published: 2022-09-18 21:01:02

## As I was finishing up my fourth phase at Flatiron School
we learned about how to make a login/signup feature on web applications using an authentication process. This phase in my software development program is centered around the fullstack framework Ruby on Rails. It's quite a power language that can do both frontend and backend programming. 

The process to make a website secured through an login or signup authentication process can be very complicated. I definitely had some trouble getting this process down, so I decided the best way to climb this mountain was to write a blog about it to fully conquer authentication.


![Ace Venture When Nature Calls](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pjvrsio8skk0o1djooqn.jpeg)

If you are familiar with Ruby on Rails, you probably are know about Gemfiles. They are project dependencies, similar to node modules, that have pre-made code that does a lot of heavy lifting for you, so you can focus on other aspects of your project. We will be using BCrypt.

[BCrypt](https://github.com/bcrypt-ruby/bcrypt-ruby) is a hefty gemfile that uses hashes and salts to encrypt passwords in your database.  Hashes are a fixed-length value that will always produce the same output for the same given input. The output can be a 32-bit or 64-bit number. What BCrypt does, is add a salt to this hash, which is an additional 29-bit number that generates a new salt every time you login with your password. This method makes it incredibly difficult to hack into the database to reveal the actual password to a user.

Additionally, this gem adds three methods that the user model can use to read, write, and encrypt passwords:

```
password=
password_confirmation=
authenticate
```

---

## As there are many ways to authenticate,
 this is the method I have found to work (after a lot of troubleshooting). There is a lot to set up, but there is flexibility in how you implement your ruby methods and display your data in the front end. For this example, I am using Ruby v.2.7.4, Rails v.6.1.3 along with React v.17.0.2 and React Dom v.5 for my frontend framework.

## Setup Work Flow

- Install gemfiles

If you are starting a new rails project, these are some important gems to include in your Gemfile:

```
gem 'bcrypt', '~> 3.1.7'
gem 'rack-cors'
gem 'pg', '~> 1.1'
```
Something of note is that I use PostgreSQL for my database, and rack-cors as middleware. Rack-cors is usually needed when you are deal with separate frontend and backend projects that work through different domains. Theoretically if you are working on a mono-template, where both ends are in the same parent directory, you won't need rack-cors. When working through this demo, if you encounter any issues related to AJAX or CORS, I would recommend installing rack-cors in your gemfile.

`bundle install` to install all of your gem files. Additionally, don't forget to `npm install` all your node modules for your frontend (Use this command in your **_React_** folder if your local environment combines both frontend and backend in a parent directory).

## Next,
 we will need to configure Rails to create cookies and sessions (I highly recommend learning about sessions and how Rails interacts with, more info through the [Rails Docs](https://guides.rubyonrails.org/action_controller_overview.html#session)). Inside the `configure/application.rb` file include the following:

```
module MyApp
  class Application < Rails::Application
    config.load_defaults 6.1
    config.api_only = true

    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore

    config.action_dispatch.cookies_same_site_protection = :strict
  end
end
```
 The last line that starts with `configu.action_dispatch` is important to include so that cookies are only shared in the same domain.

## Moving on,
 we need to add a line of code to our `app/controllers/application_controller.rb` file:

```
class ApplicationController < ActionController::API
  include ActionController::Cookies
end
```

## Create
 a user model. I prefer using a resource generator to fast-track some of the coding. In your terminal in your project directory, use this command:

```
rails g resource User name email password_digest 
```
This will create a user controller, model, serializer, and all CRUD routes in your `config/routes.rb` file. The column in your user table can be whatever you would like, but the password_digest column is vital to interact with bcrypt.

## Add
 this to the User Model file:

```
has_secure_password
```
This allows up to have proper validations for the password.

---
**_Whew,_** that was a lot to set up. Let's start writing some methods.

## Sign Up Work Flow

- Frontend display a signup form.
- Client submits form, initiating a `POST` request to a create method.
- The backend will make sure the data is properly validated. If it is, it will serialize a newly created object (a user row in the User table). If invalid, the server is provide error messages.

Before we talk about the frontend, let's deal with what the server side will do.

Although not necessary, it is advised to create a custom route for your `users#create` method, like this:

```
post '/signup', to: 'users#create'
```
Using a custom route will allow to continue to use the `create` method for the user controller if you ever need to.

## In your User Controller
 create your strong params, to control what the client can send to the database. In a private method, write:

```
  def user_params
      params.permit(:username, :email, :password, :password_confirmation)
  end
```
Then write your create method:

```
  def create
    user = User.create(user_params)
      if user.valid?
        session[:user_id] = user.id # this is the piece that logs a user in and keeps track of users info in subsequent requests.
        render json: user, status: :ok
      else
        render json: user.errors.full_messages, status: :unprocessable_entity
      end
  end
```
Let's breakdown this method:

- We first declare a variable, that creates and saves a new User object with the _user_params_.
- _If_ the user takes in the valid parameters, it will create a new session, using the id of the newly create user variable. This session will allow the client to stay logged in throughout their entire experience using the web application.
- If the user is NOT valid, it will render a json object of error messages that can subsequently be rendered by the frontend to display.

## On the Frontend
this is how I used my frontend to control a form, use `State` to create a new object with the form data, and to make a fetch request on submit:

```
import React, { useState } from "react";

function Signup() {
  const [currentUser, setCurrentUser] = useState({})
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
  });

  const { name, email, password } = formData;

  function handleChange(e) {
    const { name, value } = e.target;
    setFormData({ ...formData, [name]: value });
  }

  function handleSubmit(e) {
    e.preventDefault();
    fetch(`/signup`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(formData),
    }).then((res) => {
      if (res.ok) {
        res.json().then((formData) => {
          setCurrentUser(formData);
        });
      } else {
        res.json().then((errors) => {
          console.error(errors);
        });
      }
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="name">Username:</label>
      <input
        id="username-signup-input"
        type="text"
        name="name"
        value={formData.name}
        onChange={handleChange}
      />
      <label htmlFor="email">Email:</label>
      <input
        id="email-signup-input"
        type="text"
        name="email"
        value={formData.email}
        onChange={handleChange}
      />
      <label htmlFor="password">Password:</label>
      <input
        id="password-signup-input"
        type="password"
        name="password"
        value={formData.password}
        onChange={handleChange}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

export default Signup;

```
_**Important Note:**_
When I used the fetch in React, I had issues actually creating a session. What fixed this issue was using back-ticks for the URL in your fetch. I would recommend using these back-ticks as precaution.


 One additional note about the front end is in the onSubmit function: after the `POST` request, this function uses a callback function called `setCurrentUser` that will set state to the newly created user object. That way you can use a `useEffect` hook to call this state upon initial render of the application. That way your front end will know if the current user is logged in when navigating through the webpage.


---

## To Display User Information
We need to verify that the current user id matches the session id.

In your Application Controller, write a method to check if these two ids match:

```
    def current_user
        User.find_by(id: session[:user_id])
    end
```
Great, now let's focus back on the User. Create a new custom route:

```
get '/me', to: "users#show"
```
Inside your User model, add the appropriate `show` method:

```
def show
  if current_user
    render json: current_user, status: :ok
  else
    render json: "No current session stored", status: :unauthorized
  end
end
```
Because the `current_user` method in the Application Controller can be inherited in all children controllers, this method is using the current_user method to create a boolean return of either true or false. If true, then render a json object of the current_user's data.

---

## Log In/Log Out Workflow

This is a similar workflow to the Sign Up process:

- Write custom routes for the `create` and `destroy` for your Sessions Controller 
- Write the appropriate methods in your Sessions Controller.
- Write the frontend code that practically mimics the Signup component. 

If you don't have sessions controller already, use this command in your terminal:

```
rails g controller Sessions
```

## Logging In...
In your config/routes.rb file:

```
post "/login", to: "sessions#create"
```
In your Sessions Controller, write a `create` method that would create a user variable that uses `:params` from the form in the frontend to find the appropriate user in the database. If the credentials match up, render a json object of the user variable. If they don't match up, render error messages:

```
  def create
    user = User.find_by(username: params[:username])
    if user&.authenticate(params[:password])
      session[:user_id] = user.id
      render json: user, status: :ok
    else
      render json: "Invalid Credentials", status: :unauthorized
    end
  end
```

Then write your front end to take in data from a controlled form and use a `POST` request to the /login endpoint. This code should look almost identical to the signup component.

## Logging Out...
In your config/routes.rb file:

```
post "/logout", to: "sessions#destroy"
```
In your Sessions Controller, write a `destroy` method that will delete the current session:

```
  def destroy
    session.delete :user_id
  end
```
Your front end will fetch a `DELETE` request to the `/logout` endpoint.

---


![Star Trek Victory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2sxv9bvwrnhzkcuczhx9.gif)

**AND THAT'S A WRAP.** Take a couple deep breaths, it's as complicated as you think.

## Final Notes

- Go through this process slowly. Make you sure you understand every part of the code before moving on.
- Think about work flow process for each feature. Go step by step, from the client side to the server side, to write your code.
- If you get errors, use Google. Errors are your friend, you **_WILL_** debug your way through it.

Hopefully this blog has provided a little bit of clarity to authenticating. Thanks for reading. 'Til next time!