DEV Community

ericeinerson
ericeinerson

Posted on

Authorization and Authentication in Ruby on Rails

Overview

Authorization and authentication are two valuable areas of coding to understand. In this summary, I will be going over what authentication and authorization are and how to make a simple login form, including a username input that will be used to determine who the user is and whether they are authorized to view a post.

By the way, when I use the word "auth" I am referring to both authorization and authentication as a whole, not one or the other and not something else entirely.

Authentication versus authorization

Authorization refers to what a user has access to; authentication refers to the verification of who a user is. Both of these are important for user security because validation of a user can prevent unwanted actions, such as admin access, from affecting the program/user in a negative way.

Cookies

Cookies are data that are stored in a browser while a website is being used by a client. Cookies are generally client-side, not server-side. Their purpose is to remember information about the user, and they can be created as plain-text name-value pairs, such as user_name=eric.

cookies[:user_name] = "eric"
Enter fullscreen mode Exit fullscreen mode

Cookies can also be set as hashes and have attributes, such as expiration dates/times.

cookies[:login] = { :value => "Eric", :expires => Time.now + 1.hour}
Enter fullscreen mode Exit fullscreen mode

This cookie, stored as the symbol :login, has a value set as "Eric" and it expires and is deleted 1 hour from the current time.

One issue with cookies is that since they are generally plain-text, their information can be accessed by anyone, causing security concerns. Below is a special type of cookie, called a session, that helps resolve this security issue.

Sessions

Sessions are data that are stored on the server side. They are a specialized cookie that adds a signature to the cookie, encrypting it and preventing clients from tampering with its data. The following is a way to set up a session:

session[:user_id] = user.id
// stores the user's ID into an encrypted session 
ID that is several characters long
Enter fullscreen mode Exit fullscreen mode

In the code above, the session method will create a signature specifically for the value of the [:user_id] key and assign the value of the user's ID to that signed value. This way, it is difficult for a malicious user to access the user_id.

Code for allowing your program to use sessions and cookies

Here is some starter code for allowing cookies and sessions to be used in your program:

class Application < Rails::Application
   config.middleware.use ActionDispatch::Cookies
   // allows one to use cookies in their program
   config.middleware.use ActionDispatch::Session:CookieStore
   // allows one to use sessions in their program
   config.action_dispatch.cookies_same_site_protection = 
   :strict
   // makes sure that cookies are only accepted 
   within applications on the same domain as the 
   program, rather than different domains, for 
   security of our program
end
Enter fullscreen mode Exit fullscreen mode
class ApplicationController < ActionController::API
   include ActionController::Cookies
end
// This allows our application controller, 
which other controllers would generally 
inherit from, to use cookies
Enter fullscreen mode Exit fullscreen mode

Creating a login form with a username

Authentication could be done in Ruby with a create action using POST:

post "/login" to sessions#create
Enter fullscreen mode Exit fullscreen mode

In the above code, post refers to the POST restful action that allows one to persist a new instance of a login to the database; sessions refers to the controller that will be performing the create action via a create method.

class SessionsController < ApplicationsController
   def create
      user = User.find_by(username:params[:username])
      session[:user_id] = user.id
      render json: user
   end
end
Enter fullscreen mode Exit fullscreen mode

Below is how React could save the username in state. After making a POST request via a submit button on a React form, the default empty string of username (from useState("") is replaced with the username retrieved from the Ruby backend. The username is converted into a JavaScript object in the body of the request and then passed into the login callback function as a parameter called user.

function Login({ handleLogin }) {
  const [username, setUsername] = useState("");

  function handleSubmit(e) {
    e.preventDefault();
    fetch("/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ username }),
    })
      .then((r) => r.json())
      .then((user) => handleLogin(user));
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Adding authorization to the login

The code above shows how one could take a user, save the user.id into the session[:user_id] and save the username in state, which shows on both the front end and back end that the user is authenticated. At the moment, all that is needed is the username, not the password.

To take this a step further, an authorize method could be added that allows certain actions by the user to be restricted unless they have logged in with a username. Below is code that could be used to do this for a program that renders posts:

class PostsController < ApplicationController
   before_action :authorize

   def show
      post = Post.find_by(id:params[:id])
      render json: post
   end

   private

   def authorize
      return render json: { error: "Unauthorized" }, status: 
      :unauthorized unless session.include? :user_id
   end
end
Enter fullscreen mode Exit fullscreen mode

This code prevents a user without a user_id stored in the session to access the post. Basically, before the show method is run, the authorize method runs (before_action ensures that :authorize will run before any method within the PostsController), returning an error unless a user_id is stored in the session as a truthy value.

Summary

Here are some important takeaways from this blog:
1) Authentication and authorization are not the same.
2) POST is a useful RESTful action that can be applied to logging a user in.
3) Cookies are usually client-side, plain text, and not secure; sessions are server-side, signed cookies that are more secure than other unsigned cookies.
4) before_action allows one to use a method before any other methods within a class; the method, authorize, was the example used in this blog that determined whether or not a user could make a post.
5) Certain middleware needs to be set up in order to use cookies in a Rails application; :strict is an example of how to prevent different domains from making requests for cookies.

References

https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html#method-i-has_secure_password

https://www.w3schools.com/js/js_cookies.asp

https://www.tutorialspoint.com/ruby-on-rails/rails-session-cookies.htm

https://www.youtube.com/watch?v=IzbQAj_tcfI

https://learning.flatironschool.com/

Oldest comments (1)

Collapse
 
mkienbus profile image
Michael Kienbusch

I just finished Flatiron School's software engineering program. I'm bookmarking this it's a nice summation of the materials from the course all in one spot. Thanks for the post!