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"
Cookies can also be set as hashes and have attributes, such as expiration dates/times.
cookies[:login] = { :value => "Eric", :expires => Time.now + 1.hour}
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
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
class ApplicationController < ActionController::API
include ActionController::Cookies
end
// This allows our application controller,
which other controllers would generally
inherit from, to use cookies
Creating a login form with a username
Authentication could be done in Ruby with a create action using POST:
post "/login" to sessions#create
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
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>
);
}
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
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://www.w3schools.com/js/js_cookies.asp
https://www.tutorialspoint.com/ruby-on-rails/rails-session-cookies.htm
Top comments (1)
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!