40

I am busy porting a very small web app from ASP.NET MVC 2 to Ruby/Sinatra.

In the MVC app, FormsAuthentication.SetAuthCookie was being used to set a persistent cookie when the users login was validated correctly against the database.

I was wondering what the equivalent of Forms Authentication would be in Sinatra? All the authentication frameworks seem very bulky and not really what I'm looking for.

AndrewVos
  • 1,297
  • 2
  • 14
  • 25
  • 4
    So while I'm not going to even pretend this addresses an answer to your question, I'm going to point out that it is inherently dangerous to design and develop your own authentication scheme. There are plenty of 'hackers' that start drooling when they find these sorts of things. It's better to find established, proven tools to do this. It's not a new problem and there are already many acceptable solutions out there. – jaydel Aug 18 '12 at 16:07
  • Use bcrypt gem. It can generate cryptic hashes for passwords and if a hackie accessed your database all they get are the hashes and they should not work for passwords. I other words, keep it one way. You'll see bcrypt-ruby a lot but now it's just bcrypt and is a C extension type so you will need to compile that on windows with some dev tools added. You're right they are bulky. https://sideprojectsoftware.com/blog/2015/02/22/sinatra-authentication should help. – Douglas G. Allen Aug 18 '15 at 12:54

5 Answers5

77

Here is a very simple authentication scheme for Sinatra.

I’ll explain how it works below.

class App < Sinatra::Base
  set :sessions => true

  register do
    def auth (type)
      condition do
        redirect "/login" unless send("is_#{type}?")
      end
    end
  end

  helpers do
    def is_user?
      @user != nil
    end
  end

  before do
    @user = User.get(session[:user_id])
  end

  get "/" do
    "Hello, anonymous."
  end

  get "/protected", :auth => :user do
    "Hello, #{@user.name}."
  end

  post "/login" do
    session[:user_id] = User.authenticate(params).id
  end

  get "/logout" do
    session[:user_id] = nil
  end
end

For any route you want to protect, add the :auth => :user condition to it, as in the /protected example above. That will call the auth method, which adds a condition to the route via condition.

The condition calls the is_user? method, which has been defined as a helper. The method should return true or false depending on whether the session contains a valid account id. (Calling helpers dynamically like this makes it simple to add other types of users with different privileges.)

Finally, the before handler sets up a @user instance variable for every request for things like displaying the user’s name at the top of each page. You can also use the is_user? helper in your views to determine if the user is logged in.

Todd Yandell
  • 14,656
  • 2
  • 50
  • 37
  • Firstly, thank you for such a well thought out response! Secondly, I forget to mention but it would be nice if I could have persistent sessions. I'm assuming that if I did something like below, then this would allow the session to be persistent? Rack::Session::Cookie, :secret => "some really unique value" Are there any security issues with this approach if that is the case? – AndrewVos Aug 25 '10 at 18:50
  • beautiful, an opportunity to use more of Sinatra's lovely DSL :) – Paul Y Feb 15 '17 at 02:47
  • 1
    but where are stored a user credentials (username and password) ? how can we create a user account in the first place so that he/she could autneticates!!! – Hanynowsky Jun 12 '17 at 15:47
31

Todd's answer does not work for me, and I found an even simpler solution for one-off dead simple authentication in Sinatra's FAQ:

require 'rubygems'
require 'sinatra'

use Rack::Auth::Basic, "Restricted Area" do |username, password|
    [username, password] == ['admin', 'admin']  
end

get '/' do
    "You're welcome"
end

I thought I would share it just in case anyone wandered this question and needed a non-persistent solution.

Ralphleon
  • 3,968
  • 5
  • 32
  • 34
  • 4
    Todd's answer does not work as-is, because it is not complete, not because it is wrong. On the contrary, it is a rather sophisticated and well-thought solution, it simply lacks an `App.run!` at the end and of course the actual implementation of the `User` class (responsible for credentials management) – p4010 Sep 13 '16 at 09:41
8

I' have found this tutorial and repository with a full example, its working fine for me

https://sklise.com/2013/03/08/sinatra-warden-auth/

https://github.com/sklise/sinatra-warden-example

user9869932
  • 6,571
  • 3
  • 55
  • 49
  • 2
    First link is dead. It seems this: https://sklise.com/2013/03/08/sinatra-warden-auth/ is the one that works. – microspino Dec 18 '17 at 22:12
1

I used the accepted answer for an app that just had 2 passwords, one for users and one for admins. I just made a login form that takes a password(or pin) and compared that to one that I had set in sinatra's settings (one for admin, one for user). Then I set the session[:current_user] to either admin or user according to which password the user entered and authorized accordingly. I didn't even need a user model. I did have to do something like this:

use Rack::Session::Cookie, :key => 'rack.session',
                       :domain => 'foo.com',
                       :path => '/',
                       :expire_after => 2592000, # In seconds
                       :secret => 'change_me'

As mentioned in the sinatra documentation to get the session to persist in chrome. With that added to my main file, they persist as expected.

Josh Hunter
  • 1,507
  • 1
  • 12
  • 15
0

I found JWT to be the simple, modern/secure solution I was searching for. OP mentioned bulky frameworks, so for reference I downloaded the tag of the latest jwt gem at the time of writing (2.2.3) and it's 73 KB zipped and 191 KB unzipped. Seems to be well-maintained and open sourced on GitHub.

Here's a good blog post about it with code and a walkthrough for near-beginners: https://auth0.com/blog/ruby-authentication-secure-rack-apps-with-jwt/

Blake Gearin
  • 175
  • 1
  • 10