Published 2022-12-05.
Last modified 2023-01-27.
Time to read: 3 minutes.
ruby
collection.
Authentication can be defined as any process that presents a challenge to the user,
followed by a successful presentation of credentials.
Authorization can be defined as granting access to a resource to
a process that runs on behalf of an authenticated user.
A&A is a short form for Authentication and Authorization.
I want to add bespoke A&A to an nginx web server. For an online training website, students should have access only to the collection of pages corresponding to the courses that they signed up for, plus any public pages.
Nginx can act as a proxy server, whereby it interacts with another web service. The output of the proxied service can be incorporated into the nginx server's output, or it can be used to control the content displayed to the user.
Software Layers
This article builds upon the previous article, Sinatra Request Explorer, and demonstrates bespoke A&A, implemented via a small web application. The webapp is written in Ruby, and complies with the Rack specification. I will discuss how this A&A implementation can be used in an nginx webapp in a future article.
Both Ruby Sinatra and Ruby on Rails are Rack-compliant.
Software is best built in layers, after all.
Warden
, used by Sinatra and Rails,
is a Rack-compliant generalized authentication framework.
Revitalizing the Sinatra Warden Example
Web applications built with Ruby Sinatra can use
Warden
to provide A&A on a route-by-route basis.
I found a dusty old GitHub project, sinatra-warden-example
originally written by Steve Klise
,
that did a good job of demonstrating how to use Warden for multi-user authentication.
This demo webapp featured a sqlite database for storing user credentials.
Sinatra-warden-example
had not been kept up to date, so I
forked and updated it.
Steve has archived the repository, so it is now read-only;
thus, I was unable to submit a pull request with my changes.
Steve had written a good README for the webapp, with useful links that explain the details. I updated the README as well. Go ahead and read it to learn about the webapp; there is no point rewriting it here.
Run the Webapp
$ git clone git@github.com:mslinn/sinatra-warden-example.git Cloning into 'sinatra-warden-example'... remote: Enumerating objects: 147, done. remote: Counting objects: 100% (18/18), done. remote: Compressing objects: 100% (13/13), done. remote: Total 147 (delta 5), reused 12 (delta 4), pack-reused 129 Receiving objects: 100% (147/147), 36.24 KiB | 12.08 MiB/s, done. Resolving deltas: 100% (71/71), done.
$ cd sinatra-warden-example/
sinatra-warden-example
defines the following routes in
app/app.rb
:
- get '/'
- get '/auth/login'
- post '/auth/login'
- get '/auth/logout'
- post '/auth/unauthenticated'
- get '/protected'
$ bundle install Fetching gem metadata from https://rubygems.org/........ Using bundler 2.3.18 Using multi_json 1.15.0 Using public_suffix 5.0.0 Fetching json 1.8.6 Using ruby2_keywords 0.0.5 Using rack 2.2.4 Fetching uuidtools 2.2.0 Using tilt 2.0.11 Fetching stringex 1.5.1 Fetching bcrypt 3.1.18 Fetching json_pure 1.8.6 Using webrick 1.7.0 Fetching fastercsv 1.5.5 Using addressable 2.8.1 Using mustermann 3.0.0 Using rack-protection 3.0.4 Fetching shotgun 0.9.2 Fetching warden 1.2.9 Installing shotgun 0.9.2 Installing warden 1.2.9 Installing uuidtools 2.2.0 Installing bcrypt 3.1.18 with native extensions Installing stringex 1.5.1 Installing fastercsv 1.5.5 Installing json_pure 1.8.6 Installing json 1.8.6 with native extensions Fetching data_objects 0.10.17 Fetching dm-core 1.2.1 Using sinatra 3.0.4 Fetching sinatra-flash 0.3.0 Installing data_objects 0.10.17 Installing sinatra-flash 0.3.0 Fetching do_sqlite3 0.10.17 Installing dm-core 1.2.1 Fetching dm-migrations 1.2.0 Fetching dm-transactions 1.2.0 Fetching dm-do-adapter 1.2.0 Fetching dm-validations 1.2.0 Fetching dm-timestamps 1.2.0 Installing do_sqlite3 0.10.17 with native extensions Installing dm-transactions 1.2.0 Installing dm-timestamps 1.2.0 Installing dm-migrations 1.2.0 Installing dm-do-adapter 1.2.0 Installing dm-validations 1.2.0 Fetching bcrypt-ruby 3.1.5 Installing bcrypt-ruby 3.1.5 Fetching dm-serializer 1.2.2 Fetching dm-types 1.2.2 Installing dm-types 1.2.2 Installing dm-serializer 1.2.2 Fetching dm-sqlite-adapter 1.2.0 Installing dm-sqlite-adapter 1.2.0 Bundle complete! 14 Gemfile dependencies, 32 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. Post-install message from bcrypt-ruby:
#######################################################
The bcrypt-ruby gem has changed its name to just bcrypt. Instead of installing `bcrypt-ruby`, you should install `bcrypt`. Please update your dependencies accordingly.
#######################################################
Run the sinatra-warden-example
webapp like this:
$ bundle exec rackup [2023-01-27 12:27:25] INFO WEBrick 1.7.0 [2023-01-27 12:27:25] INFO ruby 3.1.0 (2021-12-25) [x86_64-linux] [2023-01-27 12:27:25] INFO WEBrick::HTTPServer#start: pid=1232025 port=9292
Webapps launched by rackup
run at
localhost:9292
.
The webapp looks like this:
Each request made to the webapp echos to the console. CTRL-C terminates the webapp.
127.0.0.1 - - [06/Dec/2022:22:51:52 -0500] "GET / HTTP/1.1" 200 376 0.0411 127.0.0.1 - - [06/Dec/2022:22:51:52 -0500] "GET /favicon.ico HTTP/1.1" 404 518 0.0029 127.0.0.1 - - [06/Dec/2022:22:52:02 -0500] "GET /auth/login HTTP/1.1" 200 596 0.0022 127.0.0.1 - - [06/Dec/2022:22:52:04 -0500] "POST /protected HTTP/1.1" 303 - 0.0056 127.0.0.1 - - [06/Dec/2022:22:52:04 -0500] "GET /auth/login HTTP/1.1" 200 663 0.0028 ^C[2022-12-06 22:52:09] INFO going to shutdown ... [2022-12-06 22:52:10] INFO WEBrick::HTTPServer#start done.
Log in with userid admin
and password admin
.
The webapp redirects to /protected
.
When you log out, the webapp redirects back to /
.
Invalid URLs, such as http://localhost:9292/asdf
, display this:
Alternative Invocation
The webapp can also be run this way:
$ ruby app/app.rb [2023-01-27 16:30:22] INFO WEBrick 1.7.0 [2023-01-27 16:30:22] INFO ruby 3.1.0 (2021-12-25) [x86_64-linux] == Sinatra (v3.0.3) has taken the stage on 4567 for development with backup from WEBrick [2023-01-27 16:30:22] INFO WEBrick::HTTPServer#start: pid=1270346 port=4567 ^C== Sinatra has ended his set (crowd applauds) [2023-01-27 16:30:24] INFO going to shutdown ... [2023-01-27 16:30:24] INFO WEBrick::HTTPServer#start done.
For debugging, the above can be used in a Visual Studio Code launch configuration:
{ "version": "0.2.0", "configurations": [ { "args": [], "cwd": "${workspaceRoot}", "name": "Run SinatraWardenExample", "program": "${workspaceRoot}/app/app.rb", "request": "launch", "type": "Ruby", } ] }
Rack::Request::Env
It is helpful to see the contents of the
Rack::Request::Env
module
in a debugger, which is available as request.env
in app code and ERBs:
Sinatra Session Secrets
Martin Fowler wrote about generating session secrets securely. He suggested using a bash script like this:
$ export SESSION_SECRET="#$( head -c64 /dev/urandom | base64 )"
A Sinatra committer wrote up
session secret recommendations).
He recommended using the
sysrandom
gem.
Here is a way to generate the session secret in an environment variable using the SysRandom
gem:
$ export SESSION_SECRET="$( ruby -e "require 'sysrandom/securerandom'; p SecureRandom.hex(64)" )" $ echo $SESSION_SECRET "5f55076c016fc465d2dfee5d9a8273e12fc1de51891fc1aefe190d3cb7468e1f4eff30215ea81aac9dd962a52be23a297a3adb32c0cd534cfab94d1815d0b910"
Next Steps
I will transform this webapp into something generally useful, and I will explain how that works in a future article.
About the Author
I, Mike Slinn, have been working with Ruby for a long time now. Back in 2005, I was the product marketing manager at CodeGear (the company was formerly known as Borland) for their 3rd Rail IDE. 3rd Rail supported Ruby and Ruby on Rails at launch.
In 2006, I co-chaired the Silicon Valley Ruby Conference on behalf of the SD Forum in Silicon Valley. As you can see, I have the t-shirt. I was the sole chairman of the 2007 Silicon Valley Ruby Conference.
Several court cases have come my way over the years in my capacity as a software expert witness. The court cases featured questions about IP misappropriation for Ruby on Rails programs. You can read about my experience as a software expert if that interests you.
I currently enjoy writing Jekyll plugins in Ruby for this website and others, as well as Ruby utilities.