Logging In With Cookies! Browser Storage Pt. 2
Tags: rails, beginners, ruby
Published: 2020-03-23 20:12:14

Last week, I broke down what is browser storage, and today I'll be covering using cookies to log in. I'll be using specific examples from rails with a walk through on implementation. You can read the [browser storage post here](https://dev.to/dawncronin/intro-to-browser-storage-5fph)!


### Cookies Refresher

Cookies are a way for websites to store data on your browser. More specifically, when you make a http request to a server, you share the site's specific cookies in the request. The server can send a response to the user telling them what to store as a cookie. This data is used to allow you to stay logged in between sessions, or maintain a cart on shopping site.

### Rails Setup

To implement authentication and login using Rails, we can use some built in features. Rails has a built in method called [has_secure_password](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) that will allow you to store a password digest in your database, and handle the encryption. By default, has_secure_password uses the gem bcrypt for the encryption protocol. You will need to add this line to your gem file ( or un-comment it)
```Ruby
gem 'bcrypt', '~> 3.1.7'

```
Additionally, for your users table, rather than storing a password in the database, you will need a column on your table called password_digest. You can add a migration to change this on an existing rails app, or you can include it in your initial migrations if you are starting a new one. You do not need a 'password' column on your table, just the password_digest. 

Once you have your migrations and gem file up to date, you can now update your user model to associate the password_digest to the has_secure_password method. Your user model should look something like this:

```Ruby
class User < ApplicationRecord
    validates :username, presence: true
    has_secure_password

end
```

Adding the has_secure_password line will add a bunch of methods to your User class, allowing you to store encrypted passwords, and check login credentials. 

###Application and Session Controllers

A session in rails is a built in way to access and change cookies pertaining to a user's logged in session. It abstracts the cookies away into acting like a ruby hash. We will be implementing a new controller to manage the cookies we save for maintaining a user's session. This is called the session controller. 

When we think about the actions we need to take to save a user's data, we will need a way to check the cookies when someone accesses a restricted page, add a new cookie when someone logs in, and delete a cookie when someone logs out.  

Fist in our ApplicationController, we can have a check to see if in the http request, the cookie or session includes a valid logged in user. We can use this current_user method across all of our controllers now to check who is logged in. The sessions are available to us using hash notation in ruby. 

```Ruby
class ApplicationController < ActionController::Base
    helper_method :current_user

    def current_user
        if session[:user_id]
          User.find_by(id: session[:user_id])
        end
      end
end
```

In our new SessionsController, we can include a create which will correlate with logging in, and a destroy that will correlate with logging out. 

``` Ruby
class SessionsController < ApplicationController
  
    def create
      @user = User.find_by(username: params[:username])
      if @user && @user.authenticate(params[:password])
        session[:user_id] = @user.id
        redirect_to @user
      else  
        flash[:error] = "Invalid username or password."
        redirect_to "/login"
      end
    end
  
    def destroy
      session[:user_id] = nil
      redirect_to "/"
    end
  end

```

At this point, you need to make sure that your other controllers have the correct access on them, only allowing users that are logged in when necessary. 

```Ruby
    before_action :require_login
    skip_before_action :require_login, only: [:create, :new]
   
    def show
        @user = User.find(params[:id])
    end

    def new
        if current_user
            redirect_to "/users/#{current_user.id}"
        else
        @user = User.new
        end
    end

    def create
        @user = User.new(user_params)
        if @user.valid?
            @user.save
            session[:user_id] = @user.id
            redirect_to @user
        else
            flash[:errors] = @user.errors.full_messages
            redirect_to "/signup"
        end
    end

    private

    def user_params
        params.require(:user).permit(:username, :first_name, :last_name, :password, :password_confirmation)
    end

    def require_login
        return head(:forbidden) unless session.include? :user_id
    end
```

You'll need to make sure that your routes match what you have setup, with your login and logout post requests mapping to the sessions controller. Additionally, this method works fine for small scale applications and projects, but for larger scaling, or bigger cookies, there can be issues with using cookies.

Next week I'll be looking at a specific user of local storage and JTW. Stay tuned!