A nifty one-liner (thanks to ActiveSupport) to list all the Rails Error classes in your Rails project.
Simply run this form the Rails Console:
puts Exception.descendants.sort_by(&:name)
A nifty one-liner (thanks to ActiveSupport) to list all the Rails Error classes in your Rails project.
Simply run this form the Rails Console:
puts Exception.descendants.sort_by(&:name)
We want to create an API for our Rails application which requires a user to first authenticate with their username and password to verify their identity, but subsequently, we wish to identify the user using a JWT token.
The advantage of using JWTs (or JSON Web Tokens) is that they are an industry standard (RFC 7519) method for representing claims securely between two parties. They are trustworthy because they are digitally signed and secure.JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access routes, services, and resources that are permitted with that token.
Our solution will focus on an HMAC solution. What’s more, the secret (which is used to encrypt the token) will change each and every time a user authenticates with the server. In this way, if the JWT does get into the wrong hands (you are using SSL, aren’t you?) simply re-authenticating will make the previous secret invalid and the JWT would become useless. However, you should still use safeguards to protect the token as much as possible and do not keep them longer than required.
After the secret is generated a token is generated and returned to the user, the client can send it back whenever accessing a protected resource or route (typically) in the Authorization header using the Bearer schema: Authorization: Bearer <token>
Because they’re small, portable and reliable; JWTs are becoming extremely popular for web-based API authentication and rapidly becoming the industry standard.
The short answer is that it’s really bad. But what makes it less bad than a username and password being compromised is that it can be immediately invalidated without requiring (or impacting) on directly on the user. Also, the token itself is only useful to an attacker for a limited time. Once the token expires, it becomes useless.
However, it should be noted that there are circumstances where a stolen JWT can actually be worse. This largely depends on how the token was obtained in the first place. If an attacker has successfully executed a man-in-the-middle attack, the hacker may be able to simply obtain a new token whenever required.
Basically, as always there is no silver bullet when it comes to security concerns, and you should always follow best practices and take your own server security into account. While this has worked safely for some time in my applications; this code has been modified and generalized to make this tutorial easier to understand, it should not be considered a complete and secure implementation for a production environment.
This is actually really easy to setup in Rails with Devise. There are 2 main components. The first is a special API session controller to handle the initial authentication. Since this will not be completed through the standard Rails for and devise controller, we need to make a controller to handle it. I recommending creating a specialized session controller to do this so that the API authentication is structurally separate from the rest of your application so that it can be isolated for security and testing purposes.
module Api
class SessionsController < Devise::SessionsController
skip_before_action :verify_authenticity_token
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
self.resource.update_attributes(session_attributes)
respond_to do |format|
format.json {render json: {token: generate_token(self.resource)}}
end
end
def destroy
current_user.update_attributes(shared_secret: nil, token_expires: nil)
super
end
private
def generate_token(resource)
JWT.encode(token_payload(resource), resource.shared_secret, 'HS256')
end
def token_payload(resource)
{user: resource.email, exp: 1.week.from_now}
end
def session_attributes
{
shared_secret: create_secret,
token_expires: 1.week.from_now
}
end
def create_secret
SecureRandom.alphanumeric(127)
end
end
end
It should be pretty self explanatory. It accepts an email and password and performs the same actions as a regular Devise controller. it signs the user in, and then updates the user record with a randomly generated secret and sets an expiry for the secret generation.
You also need to configure a route so you can post the email and password to this controller. Its a little more complicated than usual but not
namespace :api do
devise_for :users, skip: :all
devise_scope :user do
post 'users', to: 'sessions#create', as: nil
end
end
This technique of authenticating the user session can be used so you can determine how long it has been since the user ACTUALLY authenticated. You could write a rake task to automatically invalidate all secrets older than a specified duration. Our example will not go into the expiry of the token, but it should be easy for any experienced Rails dev.
Next the actual API controller class. This is the meat and potatoes. All your API controllers should inherit from this controller. What this will do is bypass the usual Devise authentication process and instead look at the request header for the Authentication Token. You must supply two values. An API Key that has been uniquely assigned to each user is paired with a valid Authentication Token. This is to help strengthen the JWT Token so that it is not solely responsible for the authentication (both the JWT and the API key must be compromised).
After the API Key and the JWT Authentication Token have been verified, the system will allow the continuation of the child controller action. Notice that the current_user
user_signed_in
module Api
class ApiController < ApplicationController
skip_before_action :verify_authenticity_token
skip_before_action :authenticate_user!
before_action :authenticate_api_key!
before_action :authenticate_user_from_token!
protect_from_forgery with: :null_session
protected
def current_user
@resource
end
def user_signed_in?
!@resource.nil?
end
private
def authenticate_user_from_token!
@resource ||= user_with_key(apikey_from_request).where(email: claims[0]['user']).first
if @resource.nil?
raise Pundit::NotAuthorizedError.new('Unable to deserialize JWT token.')
end
rescue StandardError => e
Rails.logger.error e
raise Pundit::NotAuthorizedError.new(e)
end
def authenticate_api_key!
if apikey_from_request.present?
unless user_with_key(apikey_from_request).present?
raise Pundit::NotAuthorizedError.new('Unable to verify the apii key.')
end
end
end
def claims(token = token_from_request, key: shared_key)
JWT.decode(token, key, true)
rescue JWT::DecodeError => e
raise Pundit::NotAuthorizedError.new(e)
end
def jwt_token(user, key: shared_key)
expires = (DateTime.now + 1.day).to_i
JWT.encode({user: user.email, exp: expires}, key, 'HS256')
end
def token_from_request
# Accepts the token either from the header or a query var
# Header authorization must be in the following format
# Authorization: Bearer {yourtokenhere}
auth_header = request.headers['Authorization']
token = auth_header.try(:split, ' ').try(:last)
unless Rails.env.production?
if token.to_s.empty?
token = request.parameters.try(:[], 'token')
end
end
token
end
def apikey_from_request
# Accepts the ApiKey either from the header or a query var
# Header ApiKey must be in the following format
# ApiKey: {yourkeyhere}
key = request.headers.try(:[], 'ApiKey').try(:split, ' ').try(:last)
if !Rails.env.production? && key.blank?
key = request.parameters.try(:[], 'apikey')
end
key
end
def shared_key
user_secret.tap do |key|
raise Pundit::NotAuthorizedError.new('Unable to verify the secret.') if key.blank?
end
end
def user_secret
return if apikey_from_request.nil?
user_with_key(userkey_from_request).first.try(:shared_secret)
end
def user_with_key(key)
return if apikey_from_request.nil?
User.where(private_key: key).where('private_key_expires > ?', Time.zone.now)
end
end
end
There you go. You now have the basis of a pretty good API Authentication Layer for your Rails app!
A few points of note:
Pundit::NotAuthorizedError
current_user
user_signed_in?
I worked on a project which allowed users to authenticate using oauth with several well known social media platforms. After users had linked all their social media presences; we wanted to import the posts for each user from each platform. This is a (redacted) sample of how I accomplished this:
First, I have a neat little module that allows me to encapsulate a list of handler objects and a notification method that can trigger the correct handler based on how the handler has subscribed itself in the factory. This is a little confusing, but basically, each provider class will initialise itself into the factory object, having subscribed itself to a particularly type of social network which it is able to process (handle). I simply abstracted this code because I thought it might be very handy for other projects which follow a similar pattern to this:
module EventDispatcher
def setup_listeners
@event_dispatcher_listeners = {}
end
def subscribe(event, &callback)
(@event_dispatcher_listeners[event] ||= []) << callback
end
protected
def notify(event, *args)
if @event_dispatcher_listeners[event]
@event_dispatcher_listeners[event].each do |m|
m.call(*args) if m.respond_to? :call
end
end
nil
end
end
Next we need to develop the Factory object to encapsulate our handler objects. It contains all the configuration attributes of our social network platform API keys and secrets, etc. It instantiates with a hash which has a static method #load
to read a specified file (or by default a file in /config/social_network_configuration.json
) which returns an instance of itself with the contents of the configuration file passed into the constructor:
require File.expand_path('../../event_dispatcher', __FILE__)
module SocialNetworking
class SocialNetworkFactory
include EventDispatcher
attr_reader :configs
def initialize(data)
setup_listeners
@configs = {}
data.each {|n, o| @configs.store n.downcase.to_param.to_sym, o}
end
def process(network, user)
notify(network, user)
end
##
# Reads client configuration from a file and returns an instance of the factory
#
# @param [String] filename
# Path to file to load
#
# @return [SocialNetworking::SocialNetworkFactory]
# Social network factory with API configuration
def self.load(filename = nil)
if filename && File.directory?(filename)
search_path = File.expand_path(filename)
filename = nil
end
while filename == nil
search_path ||= File.expand_path("#{Rails.root}/config")
if File.exist?(File.join(search_path, 'social_network_configuration.json'))
filename = File.join(search_path, 'social_network_configuration.json')
elsif search_path == '/' || search_path =~ /[a-zA-Z]:[\/\\]/
raise ArgumentError,
'No ../config/social_network_configuration.json filename supplied ' +
'and/or could not be found in search path.'
else
search_path = File.expand_path(File.join(search_path, '..'))
end
end
data = File.open(filename, 'r') {|file| MultiJson.load(file.read)}
return self.new(data)
end
end
end
The configuration file (/config/social_network_configuration.json) looks something like:
{
"Facebook": {
"oauth_access_token": "...",
"expires": ""
},
"SoundCloud": {
"client_id": "..."
},
"Twitter": {
"access_token": "...",
"access_token_secret": "...",
"consumer_key": "...",
"consumer_secret": "..."
},
"YouKu": {
"client_id": "..."
},
"YouTube": {
"dev_key": "..."
},
"Weibo": {
"app_id": "..."
}
}
The last part is to create a different handler object for each social network (as each social network has its own specific API for interfacing with the platform. Its pretty basic:
module SocialNetworking
module Providers
class NetworkNameProvider
def initialize(factory)
# you can access the configurations through the factory
@app_id = factory.configs[:network_name]['app_id']
# instruct the factory that this provider handles the
# 'network_name' social network oauth. The factory will
# publish the users authorization object to this handler.
factory.subscribe(:network_name) do |auth|
# Do stuff ...
end
end
end
end
end
So an example of a Weibo Provider class might look something like this:
require File.expand_path('../../../../lib/net_utilities', __FILE__)
require 'base62'
require 'httpi'
module SocialNetworking
module Providers
class WeiboProvider
include NetUtilities
def initialize(factory)
@token = get_token
@app_id = factory.configs[:weibo]['app_id']
factory.subscribe(:weibo) do |auth|
Rails.logger.info " Checking Weibo user '#{auth.api_id}'"
begin
@token = auth.token unless auth.token.nil? # || auth.token_expires < DateTime.now
request = HTTPI::Request.new 'https://api.weibo.com/2/statuses/user_timeline.json'
request.query = {source: @app_id, access_token: @token, screen_name: auth.api_id}
response = HTTPI.get request
if response.code == 200
result = MultiJson.load(response.body)
weibos = result['statuses']
weibos.each {|post|
... do something with the post
}
end
auth.checked_at = DateTime.now
auth.save!
rescue Exception => e
Rails.logger.warn " Exception caught: #{e.message}"
@token = get_token
end
end
end
private
def get_token
auth = Authorization.where(provider: 'weibo').where('token_expires < ?', DateTime.now).shuffle.first
auth = Authorization.where(provider: 'weibo').order(:token_expires).reverse_order.first if auth.nil?
raise 'Cannot locate viable Weibo authorization token' if auth.nil?
auth.token
end
def uri_hash(id)
id.to_s[0..-15].to_i.base62_encode.swapcase + id.to_s[-14..-8].to_i.base62_encode.swapcase + id.to_s[-7..-1].to_i.base62_encode.swapcase
end
end
end
end
Of course there are a lot of opportunities too refactor and make the providers better. For example a serious argument could be made that the API handshake should be abstracted to a seperate class to be consumed by the provider rather than the provider doing all the API lifting itself (violates the single-responsibility principal) – but I include it inline to give better idea on how this factory works without getting too abstracted.
The last piece of this puzzle is putting it all together. There are a lot of different ways you could consume this factory; but in this example I am going to do it as a rake task that can be regularly scheduled via a cron task.
Dir["#{File.dirname(__FILE__)}/../social_networking/**/*.rb"].each {|f| load(f)}
namespace :social_media do
desc 'Perform a complete import of social media posts of all users'
task import: :environment do
factory = SocialNetworking::Atlas::SocialNetworkFactory.load
# Instantiate each of your providers here with the factory object.
SocialNetworking::Atlas::Providers::NetworkNameProvider.new factory
SocialNetworking::Atlas::Providers::WeiboProvider.new factory
# Execute the Oauth authorizations in a random order.
Authorization.where(muted: false).shuffle.each do |auth|
factory.process(auth.provider.to_sym, auth)
end
end
end
I wouldn’t do this in production though, as you may encounter problems if the task gets triggered when the previous iteration is still running. Additionally, I would recommend leveraging ActiveJob to run each handler which would give massive benefits to execution concurrency and feedback on job successes/failures.
Also, you could get really clever and loop over each file in the /providers directory and include and instantiate them all at once, but I have chosen to explicitly declare it in this example.
As you can see this is a nice little pattern which uses some pseudo-event subscription and processing to allow you to import from multiple APIs and maintaining separation of responsibilities. As we loop over each authorization record, this pattern will automatically hand the auth record to the correct handler. You can also chop and change which providers are registered; as any authorization record that doesn’t have a registered handler for its type, will simply be ignored. This means that if the Weibo API changes and we need to fix our handler; it is trivial to remove the handler from production by commenting it out, and all our other handlers will continue to function like nothing ever happened.
This code was written many years ago; and should work on Ruby versions even as old as 1.8. There are probably many opportunities too refactor and enhance this code substantially using a more recent Ruby SDK. Examples of possible enhancements would be allowing the providers to subscribe to the factory using a &block
instead of a symbol and allowing the factory to pass a block into to #process
method to give access for additional processing to be executed in the context of the provider; but abstracted from it.
Nevertheless, I hope that this pattern proves useful to anyone needing a design pattern to have a handler automatically selected to process some work without complicated selection logic.
One of the hallmark features of Rails 5.2 was the introduction of recyclable cache keys.
Once the new cache API was integrated into Basecamp, DHH had this to say about it:
We went from only being able to keep 18 hours of caching to, I believe, 3 weeks. It was the single biggest performance boost that Basecamp 3 has ever seen.
Put simply, the previous cache key generation (unless you over-wrote it) used the updated_at timestamp to differentiate the specific version fo the object. So for example [class]/[id]-[timestamp], like users/5-20110218104500 or thing/15-20110218104500, which is what Active Record in Rails used to do by default when you call #cache_key.
With Rails 5.2 this no longer works as you expect. Instead, it’s simply [class]/[id] -the timestamp is dropped.
The basic idea behind it is that the cache object can be updated directly; and thus cache keys can be reused, dramatically lowering the quantity of cache garbage and increasing the amount of useful objects stored in the cache; which should let you see a significant performance hit by increasing your successful cache hits.
The only drawback is that the cache needs to be updated using the new cache API – if you don’t do this, you will suddenly see that your cached objects no longer ‘automatically’ expire.
Whilst I do recommend upgrading your codebase to use the new cache API, you can disable it and return to the older #cache_key style (using update_at timestamp) by simply adding this to your config/environments environment file:
config.active_record.cache_versioning = false
This should get you working in the old way – I will write a post on how to maximise use of the new Rails cache API in a future post.
A while ago I wrote a small post on how to manually mark an ActiveRecord model attribute as dirty. Since Rails 5.2 has some fundamental changes to ActiveRecord::Dirty.
Specifically, three methods have been deprecated and changed in 5.2. These are:
attribute_changed?
changes
, andprevious_changes
Using these methods in a Rails 5.2 application will now result in the following message (or a variation of it) being outputted in your logs:
DEPRECATION WARNING: The behavior of `attribute_changed?` inside of after callbacks will be changing in the next version of Rails. The new return value will reflect the behavior of calling the method after `save` returned (e.g. the opposite of what it returns now). To maintain the current behavior, use `saved_change_to_attribute?` instead.
attribute_changed?
and changed?
remain the same when called within a before_save
callback. But when used in an after callback there are new methods you should use instead, which the Rails Core Team believe should reduce code ambiguity of changed?
before saving to the database: will_save_change_to_attribute?
and has_changes_to_save?
In summary, if upgrading to Rails 5.2 and after modifying an object and after saving to the database, or within after_save
:
attribute_changed?
should now be saved_change_to_attribute?
changed?
should now be saved_changes?
changes
should now be saved_changes
previous_changes
has no replacement, since the behavior for it changes.And (optional; less ambiguity, more readable, but longer) after modifying an object and before saving to the database, or within before_save
:
attribute_changed?
should now be will_save_change_to_attribute?
changed?
should now be has_changes_to_save?
changes
should now be changes_to_save
If you need to add basic wiki style functionality to your Ruby on Rails models, there is a really easy way to get similar model versioning without having to resort to cutting the code yourself.
The acts_as_versioned ‘plugin’ has been available for quite some time, but its been made far better by it now becoming a gem instead of an old-school plugin. The authors have gone to considerable effort to make it as painless as possible to use.
This post, is designed to give you a brief over-view into how to get up and running with with models which ‘acts_as_versioned’. Because its the current version (at time of posting) and because its awesome, this walk-though assumes that you are using Rails 3, not 2. The instructions for Rails 2 sites are similar, but you’ll need to tweak this for it to work.
First, you need to grab the gem:
sudo gem install acts_as_versioned
Next, add the dependency to the ‘Gemfile’, it doesn’t matter too much where it goes, I stuck it somewhere in the middle:
gem 'acts_as_versioned', '0.6.0'
Next, just under the ‘ActiveRecord::Base’ line in the model’s class file, instruct the class that its to act as a versioned model.
class Article < ActiveRecord::Base
acts_as_versioned
end
Then, in the migration file you need to execute the model's method to create the version table. This is key because the acts_as_versioned gem actually creates an additional database table to house all the previous versions of a given record. Obviously, you need to delete the table is the schema is taken down. My migration now looks like:
class CreateArticles < ActiveRecord::Migration
def self.up
create_table :article do |t|
t.string :title
t.string :body
t.integer :user_id
t.timestamps
end
Article.create_versioned_table
end
def self.down
drop_table :articles
Article.drop_versioned_table
end
end
The key method is the
Article.create_versioned_table
which creates the version table of the model. Now, get rake to create the database:
rake db:migrate
Thats it! Its done. Using acts_as_versioned is simple. I'll provide some examples, where '@article' represents an instance of a model setup 'acts_as_versioned'. To find the current version of an article you can use the version property:
@article.version
But just performing a normal ActiveRecord lookup returns the most current version anyway, so to revert to a previous version use the revert_to method on an article instance:
@article.revert_to(version_number)
You can save (just like you've done a hundred times before) a previous version as the current on by using the save method. The save on a reverted articles will just create a new version.
To get the number of versions:
@article.versions.size
Since '@article.versions' returns an array of versions, you can do neat things like this:
History
<% for version in @article.versions.reverse %>
Version <%= version.version %>
<%= link_to '(revert to this version)',
:action => 'revert_to_version',
:version => version.id,
:id => @article %>
<% end %>
Obviously for this to work, you'd need to create a 'revert_to_version' action in the appropriate controller, but you get the idea.
acts_as_versioned is an amazing piece of work, and aside from the wiki-like functionality it gives you for very little effort, I can imagine scenarios such as audit and trace logs and "undo" features which could really benefit from this little gem.