A secure session management system for Ruby Sinatra applications featuring BCrypt password hashing, CSRF protection, account lockout, and thread-safe JSON user storage.
Clone the repository
git clone https://github.com/HydroshieldMKII/Sinatra-SSM.git
cd Sinatra-SSMInstall dependencies
bundle installRequired gems:
- sinatra
- bcrypt
- rack-session
Users are stored in a JSON file with the following structure:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"username": "admin",
"password_hash": "$2a$12$...",
"created_at": 1703001600,
"failed_attempts": 0,
"locked_until": null,
"last_login": 1703088000
}
]Configure the session manager in your Sinatra app:
configure do
session_config.session_secret = ENV['SESSION_SECRET'] || SecureRandom.hex(64)
session_config.users_store = ENV['USERS_PATH'] || 'users.json'
session_config.cookie_name = 'my_app.session'
session_config.session_expire = 86400 # 24 hours
session_config.login_path = '/login'
session_config.bcrypt_cost = 12
session_config.min_password_length = 12
session_config.max_failed_attempts = 5
session_config.lockout_duration = 900 # 15 minutes
session_config.session_rotation = true
session_config.csrf_protection = true
session_config.validate!
endrequire 'sinatra/base'
require_relative 'SimpleSessionManager'
class App < Sinatra::Base
register Sinatra::SessionManager
configure do
session_config.session_secret = SecureRandom.hex(64)
session_config.users_store = 'users.json'
session_config.validate!
use Rack::Session::Cookie,
key: session_config.cookie_name,
secret: session_config.session_secret,
expire_after: session_config.session_expire,
httponly: true,
secure: production?,
same_site: :lax
end
get '/login' do
erb :login, locals: { csrf_token: csrf_token }
end
post '/login' do
if authenticate!(params[:username], params[:password])
redirect '/dashboard'
else
status 401
erb :login, locals: {
error_message: 'Invalid credentials or account locked',
csrf_token: csrf_token
}
end
end
get '/dashboard' do
require_authentication!
erb :dashboard
end
post '/logout' do
logout!
redirect '/login'
end
post '/users' do
require_authentication!
begin
user = create_user(params[:username], params[:password])
json id: user['id'], username: user['username']
rescue => e
status 400
json error: e.message
end
end
endrequire 'sinatra'
require_relative 'SimpleSessionManager'
register Sinatra::SessionManager
configure do
settings.session_config.session_secret = SecureRandom.hex(64)
settings.session_config.users_store = 'users.json'
settings.session_config.validate!
use Rack::Session::Cookie,
key: settings.session_config.cookie_name,
secret: settings.session_config.session_secret,
expire_after: settings.session_config.session_expire,
httponly: true,
secure: production?,
same_site: :lax
end
# Routes here...Check if the current user is authenticated.
get '/' do
if authenticated?
redirect '/dashboard'
else
redirect '/login'
end
endAuthenticate a user with username and password. Returns true on success, false on failure.
post '/login' do
if authenticate!(params[:username], params[:password])
redirect '/dashboard'
else
status 401
'Invalid credentials'
end
endRequire authentication for a route. Redirects to login page if not authenticated.
get '/admin' do
require_authentication!
'Admin panel'
endGet the current authenticated user (without password hash).
get '/profile' do
require_authentication!
user = current_user
erb :profile, locals: { user: user }
endClear the session and log out the user.
post '/logout' do
logout!
redirect '/login'
endCreate a new user with validated password.
post '/register' do
begin
user = create_user(params[:username], params[:password])
'User created successfully'
rescue => e
status 400
e.message
end
endGet the current CSRF token for forms.
<form method="post" action="/login">
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
<!-- form fields -->
</form>Automatically called before each request to verify CSRF tokens on non-GET requests.
- Minimum 12 characters (configurable)
- Must contain uppercase letter
- Must contain lowercase letter
- Must contain digit
- Must contain special character
After 5 failed login attempts (configurable), the account is locked for 15 minutes.
- Secure, httponly cookies
- Session rotation on login
- CSRF protection
- Configurable session expiration
The UserStore uses Monitor mixin for thread-safe file operations.
Ensure all forms include the CSRF token:
<% if csrf_token %>
<input type="hidden" name="csrf_token" value="<%= csrf_token %>">
<% end %>Check that:
SESSION_SECRETis at least 64 characters- Cookies are enabled in the browser
- Not using incognito/private browsing
secure: trueis not set in development (HTTP)
Common password validation errors:
- "Password too short" - Needs 12+ characters
- "Password requires digit" - Add a number
- "Password requires uppercase" - Add an uppercase letter
- "Password requires lowercase" - Add a lowercase letter
- "Password requires special character" - Add !@#$%^&* etc.
Classic style requires settings.session_config:
settings.session_config.session_secret = '...'Modular style uses session_config directly:
session_config.session_secret = '...'