Frank
Frank

Reputation: 143

Rack middleware fails to redirect on authentication in Sinatra

Why does the Rack middleware fail to redirect when coupled with default GET and POST login routes and 401 handling in the Sinatra app?

Relevant Shield middleware extract :

module Shield
  class Middleware
    attr :url

    def initialize(app, url = "/login")
      @app = app
      @url = url
    end

    def call(env)
      tuple = @app.call(env)

      if tuple[0] == 401
        [302, headers(env["SCRIPT_NAME"] + env["PATH_INFO"]), []]
      else
        tuple
      end
    end

  private
    def headers(path)
      { "Location" => "%s?return=%s" % [url, encode(path)],
        "Content-Type" => "text/html",
        "Content-Length" => "0"
      }
    end

    def encode(str)
      URI.encode_www_form_component(str)
    end
  end
end

View full source code (104 lines/2.8kb).

Here a relevant extract of the Sinatra app:

# application_controller.rb
class ApplicationController < Sinatra::Base
  helpers Shield::Helpers
  use Shield::Middleware, "/login"
  ...

  get '/noway' do
    error(401) unless authenticated(User)
    erb :app_noway
  end  

  get '/login' do
    erb :login
  end

  post "/login" do
    if login(User, params[:login], params[:password])
      remember(authenticated(User)) if params[:remember_me]
      redirect(params[:return] || "/")          
    else
      redirect "/login"
    end
  end
end

Full source code (basic app displaying the problem behavior), for easy and immediate perusal: https://github.com/shieldtest/shieldtest

The repository is ready for a "clone and rackup" with database, env and all. Login credentials; email: [email protected], password: shield.

Problem
When accessing a protected route (/noway), the middleware injects a authentication process, as intended. But after the successful autentication, the subsequent redirect always defaults to root, instead of the return URL for the protected page (/noway).

Solution needed
The protected page (/noway) should be redirected to automatically after authenticating successfully via Shield.


Visual walk-through

Step 1 (below): At the Sinatra main page. Click link to protected page (/noway) enter image description here

Step 2 (below): Redirected to /login correctly, as no user is authenticated. Enter correct login credentials correctly.
enter image description here

PROBLEM BEHAVIOR - redirected to main instead of the protected page
Step 3A (below): After entering correct login credentials: sent back to main page (again)
enter image description here

TESTING LOGIN - protected page is accessible now (manually, by clicking page again)
Step 4 (below): At the main page. Click the protected page (/noway) again => Access granted enter image description here

Upvotes: 1

Views: 547

Answers (1)

Frank
Frank

Reputation: 143

The params[:return] was never forwarded to the POST request, it seems.

So, a 'dirty fix' would be to grab the return params and pass it via the login form to the POST request. This yields the desired behavior:

#login.rb
...
<% if params[:return] %>
  <input type='hidden' name='redirect' value="<%= params[:return] %>">
<% end %>
...

And then redirecting to the redirect params from the login form:

#application_controller.rb
post "/login" do
  if login(User, params[:login], params[:password])
    ...
    redirect to params[:redirect] || "/"
    ...
  end
end

Still, I would have preferred to understand why the middleware didn't perform as expected and how to fix/store this return params via the Rack middleware.

Upvotes: 0

Related Questions