Login Required Readme
Overview
Sometimes you want to require that a user is logged in to access a route. Here's how.
Objectives
- Restrict a route to logged in users.
- Skip a filter for particular controller actions.
First pass: manual checks
Let's say we have a DocumentsController
. Its show
method looks like this:
def show
@document = Document.find(params[:id])
end
Now let's add a new requirement: documents should only be shown to users when
they're logged in. From a technical perspective, what does it actually mean for
a user to log in? When a user logs in, all we are doing is using cookies to add
their :user_id
to their session
.
The first thing you might do is to just add some code into DocumentsController#show
:
def show
return head(:forbidden) unless session.include? :user_id
@document = Document.find(params[:id])
end
The first line is a return guard. Unless the session includes :user_id
, we
return an error. head(:forbidden)
is a controller method that returns the
specified HTTP status code—in this case, if a user isn't logged in, we return
403 Forbidden
.
Refactor
This code works fine, so you use it in a few places. Now your DocumentsController looks like this:
class DocumentsController < ApplicationController
def show
return head(:forbidden) unless session.include? :user_id
@document = Document.find(params[:id])
end
def index
return head(:forbidden) unless session.include? :user_id
end
def create
return head(:forbidden) unless session.include? :user_id
@document = Document.create(author_id: user_id)
end
def update
return head(:forbidden) unless session.include? :user_id
@document = Document.find(params[:id])
# code to update a document
end
end
That doesn't look so DRY. I really wish there were a way to ask Rails to run a check before any controller action.
Fortunately, Rails gives us a solution: before_action
. We can
refactor our code like so:
class DocumentsController < ApplicationController
before_action :require_login
def show
@document = Document.find(params[:id])
end
def index
end
def create
@document = Document.create(author_id: user_id)
end
private
def require_login
return head(:forbidden) unless session.include? :user_id
end
end
Let's look at the code we've added:
before_action :require_login
This is a call to the ActionController class method before_action
.
before_action
registers a filter. A filter is a method which runs before,
after, or around, a controller's action. In this case, the filter runs before
all DocumentsController's actions, and kicks requests out with 403 Forbidden
unless they're logged in.
Skipping filters for certain actions
What if we wanted to let anyone see a list of documents, but keep the
before_action
filter for other DocumentsController
methods? We could do
this:
class DocumentsController < ApplicationController
before_action :require_login
skip_before_action :require_login, only: [:index]
# ...
end
This class method,
skip_before_action :require_login, only: [:index]
Tells Rails to skip the require_login
filter only on the index
action.
Resources
View Login Required Readme on Learn.co and start learning to code for free.