-
-
Save tomdalling/b873e731e5c6c56431807d40a904f6cf to your computer and use it in GitHub Desktop.
#!/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 but it's not a plain text string comparison, is it? bcrypt-ruby/bcrypt-ruby#42
Shouldn't you be salting the passwords?
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.
@christiangenco Salting is done automatically inside bcrypt. Also sorry for the late reply – GitHub didn't notify me.
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?)
There is a typo after the HashBang: "/user . . "
Nice demo, thanks. Still works!
About timing attack...
its unnecessary, because, bcrypt implement this
https://github.com/codahale/bcrypt-ruby/blob/v3.0.1/lib/bcrypt.rb#L175-178
14 years ago...
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)