DEV Community

Cover image for user auth in Ruby - Cuba, Shield and Mongoid
Andy Huynh
Andy Huynh

Posted on • Edited on

user auth in Ruby - Cuba, Shield and Mongoid

User authentication should be secure and easy to understand. The crew at Openredis provide an authentication ecosystem that's expressive and Ruby idiomatic. The following provides the building blocks for a basic authentication system for your Ruby apps:

Tools

  • Cuba: Popular microframeworks like Sinatra and Ramaze will forever have a place in Rubyland. However, Cuba and its small codebase (~400 LOC) is succinct enough and friendly to new developers. It's also quite readable and terse if you're into that.
  • Mongoid: Data migrations are a pain. Why not work with a JSON object.. oh - Mongo does that. Mongo has an ORM called Mongoid we can leverage.
  • Shield: A dead simple solution for user authentication (~110 LOC)

shell

$ mkdir auth_example
$ cd auth_example
$ touch app.rb
$ vim app.rb
Enter fullscreen mode Exit fullscreen mode

app.rb

require "cuba"

Cuba.define do
  on get do
    on root do
      res.write "Welcome home!"
    end

    on "login" do
      res.write "Let me in!"
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Here's what you'll need to know: Cuba#define takes a block. It responds to route matchers similar to a Rails controller. The name of your route is like a Rails controller route action. So "login" would be an action on whatever controller you define.

Notice #root and #login are nested inside get blocks. In a second terminal, you can run $ ruby app.rb. Nothing will happen.

Cuba users Rack under the hood. Rack responds to a rackup file. In the same directory, create config.ru:

config.ru

require "./app"

run Cuba
Enter fullscreen mode Exit fullscreen mode

To clarify, config.ru tells Rack where to look in order to run the app. Thus, we simply include app.rb and instantiate Cuba.

Run rackup config.ru and point your browser to localhost:9292. Try localhost:9292/login. See messages? Good -let's move on.

Next, we should connect Mongo as a backend server. We're ready to add Mongoid to the project.

Before we continue, let's set our dependencies in a Gemfile:

Gemfile

source "https://rubygems.org"

ruby "2.1.1"

gem "cuba",    "~> 3.2.0"
gem "mongoid", "~> 4.0.0"
Enter fullscreen mode Exit fullscreen mode

Run bundle install to ensure your gem libraries get installed locally.

In regards to Mongo, I won't go into detail on how to install it. They do a better at that... I'll refer you to Mongo's docs.

Once you get it installed, start server with $ mongod and create a config file for our ORM

config/mongoid.yml

development:
  sessions:
    default:
      database: auth_example
      hosts: 
        - localhost:27017
Enter fullscreen mode Exit fullscreen mode

Pretty simple. We're connecting Mongo to the app and setting the name as auth_example. From here, we can leverage Mongoid to talk to our server.

app.rb

require "cuba"
require "mongoid"

ENV["RACK_ENV"] ||= "development"
Mongoid.load!("#{Dir.pwd}/config/mongoid.yml")

Cuba.define do
  on get do
    on root do
.
..
...
Enter fullscreen mode Exit fullscreen mode

Mongoid.load is our adapter that connects the server to the app. Let's initialize a Mongo database to interact with. Start a Mongo session in a terminal $ mongo.

> mongo
MongoDB shell version: 2.3.8
...
> show dbs
admin (empty)
local 0.078GB
> use auth_example
> show dbs
admin (empty)
local 0.078GB
auth_example (empty)
Enter fullscreen mode Exit fullscreen mode

use <name_of_database> instantiates a new database if there isn't one. That's pretty much it! Now that the setup is done, all we need is the authentication logic. Shield does this well plays nicely with our setup. It assumes we have a user model so create a user.rb file.

models/user.rb

class User
  include Shield::Model
  include Mongoid::Document

  field :email, type: String
  field :crypted_password, type: String

  def self.fetch(identifier)
    where(email: identifier).first
  end

  def self.[](id)
    find(id)
  end
end
Enter fullscreen mode Exit fullscreen mode

Let's break this down:

  • Including Shield::Model means Sheild is expecting your user to have User#fetch and a crypted_password attribute to function properly.
  • User#fetch uses Mongoid's where query to find the first instance of an email.
  • User.[](id) just finds a user instance by id. If you're unfamiliar with this syntax, the way you would pass an id as a param is User[42]

Updating app.rb will wire User to the rest of the app.

app.rb

require "cuba"
require "mongoid"
require "shield"

require "./models/user"

ENV['RACK_ENV'] ||= "development"
Mongoid.load!("#{Dir.pwd}/config/mongoid.yml")

Cuba.define do
  on get do
    on root do
...
Enter fullscreen mode Exit fullscreen mode

Our code is sufficient to start interacting with a fake user now. Open a console with "$ irb -r ./app"

Console

$ user = User.new(email: "foo@bar.com")
=> #<User _id: <LONG_SHA>, email: "foo@bar.com", crypted_password: nil>
$ user.password = "foobar123"
=> "foobar123"
$ Shield::Password.check("foobar123", user.crypted_password)
=> true 
$ nil == User.authenticate("foo@bar.com", "foobar123")
=> true
$ user.save
=> true
$ nil == User.authenticate("foo@bar.com", "foobar123")
=> false
Enter fullscreen mode Exit fullscreen mode

Fun fact: -r autoloads whatever file you pass to the irb console you're opening.

Let's double check we wrote to our database. Open mongo again with > mongo auth_example

Mongo

MongoDB shell version: 2.6.3
connecting to: auth_example

> db.users.find({email: "foo@bar.com"})
{ "_id" : ObjectId("54b43b4d416e64b333000000"), "email" : "foo@bar.com", "crypted_password" : "900923b82a97febe274" }
Enter fullscreen mode Exit fullscreen mode

From here, you can create a login flow for your users. I recommend using mote, also by Openredis, for template rendering. I'll follow up with another post on the markup we can include to really see Cuba in action.

Top comments (0)