Skip to content

Instantly share code, notes, and snippets.

@tomdalling
Created May 23, 2016 12:26
Show Gist options
  • Save tomdalling/b873e731e5c6c56431807d40a904f6cf to your computer and use it in GitHub Desktop.
Save tomdalling/b873e731e5c6c56431807d40a904f6cf to your computer and use it in GitHub Desktop.
A simple Sinatra app that demonstrates basic authentication
#!/user/bin/env ruby
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'sinatra', '~> 1.4'
gem 'bcrypt', '~> 3.1'
end
require 'sinatra/base'
require 'bcrypt'
def hash_password(password)
BCrypt::Password.create(password).to_s
end
def test_password(password, hash)
BCrypt::Password.new(hash) == password
end
User = Struct.new(:id, :username, :password_hash)
USERS = [
User.new(1, 'bob', hash_password('the builder')),
User.new(2, 'sally', hash_password('go round the sun')),
]
class AuthExample < Sinatra::Base
enable :inline_templates
enable :sessions
get '/' do
if current_user
erb :home
else
redirect '/sign_in'
end
end
get '/sign_in' do
erb :sign_in
end
post '/sign_in' do
user = USERS.find { |u| u.username == params[:username] }
if user && test_password(params[:password], user.password_hash)
session.clear
session[:user_id] = user.id
redirect '/'
else
@error = 'Username or password was incorrect'
erb :sign_in
end
end
post '/create_user' do
USERS << User.new(
USERS.size + 1, #id
params[:username], #username
hash_password(params[:password]) #password_hash
)
redirect '/'
end
post '/sign_out' do
session.clear
redirect '/sign_in'
end
helpers do
def current_user
if session[:user_id]
USERS.find { |u| u.id == session[:user_id] }
else
nil
end
end
end
run!
end
__END__
@@ sign_in
<h1>Sign in</h1>
<% if @error %>
<p class="error"><%= @error %></p>
<% end %>
<form action="/sign_in" method="POST">
<input name="username" placeholder="Username" />
<input name="password" type="password" placeholder="Password" />
<input type="submit" value="Sign In" />
</form>
@@ home
<h1>Home</h1>
<p>Hello, <%= current_user.username %>.</p>
<form action="/sign_out" method="POST">
<input type="submit" value="Sign Out" />
</form>
<p>There are <%= USERS.size %> users registered:</p>
<ul>
<% USERS.each do |user| %>
<li><%= user.username %></li>
<% end %>
</ul>
<h2>Create New User</h2>
<form action="/create_user" method="POST">
<input name="username" placeholder="Username" />
<input name="password" placeholder="Password" />
<input type="submit" value="Create User" />
</form>
@@ layout
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Simple Authentication Example</title>
<style>
input { display: block; }
.error { color: red; }
</style>
</head>
<body><%= yield %></body>
</html>
@CodeMonkeySteve
Copy link

The string comparison on line 18 leaves you vulnerable to timing attacks, you need a constant-time comparison function (e.g. https://github.com/plataformatec/devise/blob/9a11586a724487dc98dddea0ab9c8afcc0e9439d/lib/devise.rb#L484)

@tomdalling
Copy link
Author

tomdalling commented May 24, 2016

@CodeMonkeySteve but it's not a plain text string comparison, is it? bcrypt-ruby/bcrypt-ruby#42

@christiangenco
Copy link

Shouldn't you be salting the passwords?

@kathgironpe
Copy link

I think this is only good for apps that only require 1 user or much smaller.
I only use http basic authentication for such cases.

@tomdalling
Copy link
Author

@christiangenco Salting is done automatically inside bcrypt. Also sorry for the late reply – GitHub didn't notify me.

@flips
Copy link

flips commented Mar 11, 2017

Thanks @tomdalling for the example. Helped me realize what I was doing wrong. 🙂

@katgironpe Care to elaborate why the basic principle showed here in your opinion wouldn't scale/be safe in bigger setups? (What would you recommend for bigger setups?)

@philiprhoades
Copy link

There is a typo after the HashBang: "/user . . "

@w3b-net-au
Copy link

Nice demo, thanks. Still works!

@marcuxyz
Copy link

marcuxyz commented Feb 6, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment