0

In my Rails 3.1.5 application, I am using the ims-lti gem to perform third-party authentication. To perform the authentication, three things must be done:

  1. Check that the request signature is correct
  2. Check if the timestamp is too old
  3. Check that the nonce has not been used

The first two are done, but I am having trouble with the nonce check. Most questions I have found deal with generating nonces in Rails, not checking them.

There are several related questions that use the oauth-plugin gem to check the nonce:

Rails oauth-plugin: multiple strategies causes duplicate nonce error

OAuth signature verification fails

return false unless OauthNonce.remember(nonce, timestamp)

Unfortunately, the oauth-plugin gem hasn't been updated since 2013, and is not compatible with the version of the oauth gem required by the ims-lti gem.

It does not appear that the oauth gem supports validating nonces.

Is there a canned way to check the nonce, whether in native Rails or through a gem, or am I relegated to:

  • Creating a nonce table
  • Checking that the nonce is not already in the table
  • Storing the nonce and timestamp in the table
  • Cycling the table to drop entries with an expired timestamp
Aaroninus
  • 1,062
  • 2
  • 17
  • 35

1 Answers1

2

Here is what I ended up doing.

Create a database table to store the nonce values:

/db/migrate/<timestamp>_create_oauth_nonces.rb

class CreateOauthNonces < ActiveRecord::Migration
  def change
    create_table :oauth_nonces do |t|
      t.string :nonce
      t.timestamp :timestamp
    end
    add_index :oauth_nonces, :nonce
    add_index :oauth_nonces, :timestamp
  end
end

Since nonces are short-lived, they should be deleted after they are no longer needed. The Sidekiq and Redis gems can be used to perform nonce deletion asynchronously:

/app/workers/oauth_nonces_worker.rb

class OauthNoncesWorker
  include Sidekiq::Worker

  def perform(nonce_id)
    OauthNonces.find(nonce_id).delete
  end
end

In the sessions controller, nonce handling is performed - check if the request has expired, then check the nonce value, and if it passes, enqueue the nonce deletion:

sessions_controller.rb

request_timestamp = DateTime.strptime(params[:oauth_timestamp], '%s')
nonce_expiry_time = 5.minutes

if request_timestamp < nonce_expiry_time.ago
    # Handle expired request here
end

nonce_exists = OauthNonces.find_by_nonce(params[:oauth_nonce])

if nonce_exists
  # Handle re-used nonce here
end

# Store the new nonce in the nonces table
nonce = OauthNonces.create!({ nonce: params[:oauth_nonce], timestamp: timestamp }, without_protection: true)

# Use Sidekiq and Redis to queue the nonce deletion
begin
  OauthNoncesWorker.perform_in(nonce_expiry_time, nonce.id)
rescue Redis::CannotConnectError
  # Okay to swallow error, since enqueuing the nonce deletion is for performance only
  # Users shouldn't be prevented from logging in just because the nonce deletion worker isn't running
end

This isn't the best solution, and has room to be improved.

  • Since the nonces don't need to be persisted for more than a few minutes, using a database table is overkill. Better to cache them, and save a DB request per login.
  • Instead of queuing every nonce deletion, instead a background worker can purge the expired nonces once a minute.
Aaroninus
  • 1,062
  • 2
  • 17
  • 35