Biting Both Ends of the Login Sandwich

I’ll jump right in to a description of my first tango with setting up dual UIs in a single rails app.

The app is a barebones food ordering app inspired by grubzhlub or stretchmarks

This can be accomplished in 4 steps. Let’s take a quick look at them.

  1. Add a login, includes login form, path, and controller method.
  2. Configure our app to allow a restaurant session.
  3. Address shared paths
  4. Restricting paths only available to one login type.

Add a Login

Our new user type, restaurant, can share login pages with the original user type which is, ahem, called user. We just have to add a form.

Add the following code to app/views/sessions/login.html.erb

<%= form_for :session, url: "/sessions/restaurant" do |f| %>  <%= f.label :username %>
<%= f.text_field :username %>
<%= f.label :password %>
<%= f.text_field :password %>
<%= f.submit "login" %><% end %>

notice the new path on line one. let’s add that path to routes.rb

post '/sessions/restaurant', to: 'sessions#create_restaurant', as: 'create_restaurant'

now add the #create_restaurant method to the sessions controller:

class SessionsController < ApplicationController
...
def create_user
...
end
# new login method for restaurant (identical to create_user)
def create_restaurant
restaurant = Restaurant.find_by(username: params[:session[:username])
if !restaurant
flash[:error] = "Restaurant not found"
redirect_to new_login_path
elsif restaurant.authenticate(params[:session][:password])
session[:restaurant_id] = restaurant.id
redirect_to restaurant_path(restaurant)
else
flash[:error] = "Password incorrect"
redirect_to new_login_path
end
end

supposing correct credentials we’re faced with two issues when redirecting to restaurant_path(restaurant).

  1. our app is set up to check for a user (sessions[:user_id) ]in the cookies, not a restaurant(sessions[:restaurant_id]). thankfully, this problem only has to be fixed once.
  2. restaurant_path(restaurant) takes us to the restaurant show page that the user would see and is not suited for the restaurant user type.

Configure our app to allow restaurant sessions

To fix our first problem, let’s make a small change to the set_user method in the application controller.

class ApplicationController < ActionController::Base
...
def set_user
if session[:user_id] # => new!
@current_user = User.find_by(id: session[:user_id])
elsif session[:restaurant_id]
@current_restaurant = Restaurant.find_by(id: session[:restaurant_id]) # => new!
end # => new!

end
def auth_user
...
end
end

Addressing Shared Paths

we can fix our second problem in restaurants_controller#show by changing the variables and the view template .

class RestaurantsController < ApplicationController
...
def show
if session[:user_id]
# variables for the user
@restaurant = Restaurant.find(params[:id])
@items = @restaurant.items
@new_order = Order.new(restaurant: @restaurant)
# renamed template for the user
render 'show_for_user'
elsif session[:restaurant_id]
# new variables for the restaurant
@restaurant = Restaurant.find(session[:restaurant_id])
@items = @restaurant.items
@open_orders = @restaurant.open_orders
# new template for the restaurant
render 'show_for_restaurant'
end
end
end

our new show_for_restaurant template will contain all the functionality we want to provide our restaurant and there is no way for the user to access that template. This solution can be applied to any path that is shared by the restaurant and user.

Restricting Paths that are Only Available to one Login Type

Our final step is to restrict our restaurant from viewing user specific pages and vice versa, for example, the user edit page.

Let’s address this in two places.

  1. Set the login_path to check for for user and restaurant in the session hash.
class SessionsController < ApplicationController
...

def login
if session[:user_id]
# existing redirect if user logged in
redirect_to user_path(session[:user_id])
elsif session[:restaurant_id]
# new redirect if restaurant is logged in
redirect_to restaurant_path(session[:restaurant_id])
end
end

Our login path is now a convenient redirecting tool that we can use to redirect either user or restaurant in case one of them tries to access any forbidden page.

2. Placing the restriction.

Suppose our restaurant tries to access a users edit page by pasting the link directly into the browser, we can just add the following code to user_controller#edit:

class UsersController < ApplicationController
...
def edit
@user = User.find_by(id: params[:id])
#prevent all but the current user from accessing the edit page
return redirect_to new_login_path unless @user == @current_user
end
end

since our login path will detect our user type for us, it will also work if a user from directly pasting another user’s url into the browser to nefariously access their edit page.

Many thanks to Andrew Santos, one of my coaches at Flatiron School for guiding me in this endeavor.

I'm a fullstack developer with a long history of SQL messaroundery.