Rails authentication across apps/servers - ruby-on-rails

I've been developing my rails apps whilst keeping them as modular as possible. I'm trying to implement different parts underneath as services.
Say an example of Facebook:
a) A MainApp that allows the user to have a wall, posts, etc.
b) A PhotoApp that stores photos, allows the user to see his photos, etc. This is a standalone app that will have a REST API that can be used by MainApp as well.
I was thinking of using OAuth as a Single Sign On solution (as in this tutorial http://blog.joshsoftware.com/2010/12/16/multiple-applications-with-devise-omniauth-and-single-sign-on/) where each app will be authorized via OAuth and will get access to the current user session based on the cookie.
First question: Is this a viable solution?
Second question: I want to be able to call the PhotoApp API from the MainApp server (not from the user's browser). How would authentication work in this situation?
Third question: How would this work if say I had a service that used node.js?

Yes, SSO using OAuth is a viable solution, but it's not the simplest one. When building anything new, OAuth 2.0 is the way to go. The OAuth standards cover a lot of ground.
The primary advantage of OAuth is that it allows users to give 3rd party apps access to their account without disclosing their password to the 3rd party. If you are not seriously providing such interoperability, then OAuth is probably overkill.
Given the complexity, I offer a different pair of solutions:
For Single Sign On
The trick is to share the session ID cookie between hosts within your domain & to use a shared session store (like ActiveRecordStore or a cache-based store.)
Every Rails app has a "secret" that is used to sign cookies. In newer Rails apps this is located in /config/initializers/secret_token.rb. Set the same secret token in each application.
Then, configure the session to allow access from all subdomains:
AppName::Application.config.session_store :active_record_store, :key => '_app_name_session', :domain => :all
For Internal API calls
Use a good shared secret to authenticate over HTTPS connections. Pass the secret in the "Authorization" header value.
You can use the shared secret easily with other architectures (like node.js). Just make sure you always use HTTPS, otherwise the shared secret could be sniffed on the network.

You could look at a Service Oriented Architecture solution as proposed by Jeremy Green at Octolabs during the 2014 RailsConf.
The blog post with all the resources (repos, demos, etc.) is located here: http://www.octolabs.com/so-auth
And the video that explains everything is here: http://www.youtube.com/watch?v=L1B_HpCW8bs
This centralized SSO is no simple task but Jeremy has done an excellent job talking about Service Oriented Architecture and sharing exactly how you might put this system together.

I recently had a similar problem of wanting to share session data between Rails and an Erlang app. My solution was to write a Rack::Session::Abstract::ID class that stored sessions in Redis as hash vaules. It doesn't call Marshal.dump on String types. This allows non-ruby applications to use some of the session values if they have the session_id.
require 'rack/session/abstract/id'
class MaybeMarshalRedisSession < Rack::Session::Abstract::ID
def initialize(app, options = {})
#redis = options.delete(:redis) || Redis.current
#expiry = options[:expire_after] ||= (60 * 60 * 24)
#prefix = options[:key] || 'rack.session'
#session_key = "#{#prefix}:%s"
super
end
def get_session(env, sid)
sid ||= generate_sid
session = #redis.hgetall(#session_key % sid)
session.each_pair do |key, value|
session[key] = begin
Marshal.load(value)
rescue TypeError
value
end
end
[sid, session]
end
def set_session(env, sid, session, options={})
#redis.multi do
session.each_pair do |key, value|
# keep string values bare so other languages can read them
value = value.is_a?(String) ? value : Marshal.dump(value)
#redis.hset(#session_key % sid, key, value)
end
#redis.expire(#session_key % sid, #expiry)
end
sid
end
def destroy_session(env, sid, option={})
#redis.del(#session_key % sid)
generate_sid unless options[:drop]
end
end
You can use this from rails with:
MyApp::Application.config.session_store MaybeMarshalRedisSession
From Rack with:
use MaybeMarshalRedisSession
And from elsewhere with:
redis.hgetall("rack.session:#{session_id}")
If you want to call PhotoApp from your MainApp or Node.js you can make a HTTP request that includes your user's session cookie.

Related

Two "access codes" instead of "username" and "password"?

The information system I sometimes use has 2 access codes that are password-masked.
Is this just a security through obscurity measure (being able to punch in username and password in front of audience) or does it have any other advantage over the conventional user/pass or token/secred?
I'm considering this when building my own IS for my business partners and myself. Is this any good or just annoying and useless unmemorable stuff for the user?
If it is a good idea, how to implement that with user.authenticate()?
I would not implement such a system, because..
The username/ID (first "access code") need not be a secret; while it should not expose confidential information (defined by policy), the purpose of this key is not to "add security" and making it hard to remember would annoy people - at least, it would annoy me.
If a user has to write down a "secret" because it is too hard to remember .. then anyone with access to the recording (e.g. text file, Post-It note) has access to the might-not-be-a-secret secret.
The way to increase security with passwords (second "access code") is to encourage passphrases, which are can be easier than "P#ssw0rds!" to remember (and are much easier than random passwords to remember!), but much harder to brute-force. It is the password/pass-phrase which is the secret token.
Assuming the use of proper connection encryption and using sound bcrypt/scrypt password hashing (and not suffering from an attack vector such as Heartbleed or a local keysniffer), then the next consideration is to mitigate brute-force attacks.
I would focus on using a solid (exiting and proven) authentication implementation, and secure server management and key policy.
That being said, here are additional thoughts ..
It might be useful/relevant to make the username/ID (first "access code") field masked, like a password field. This can prevent cases where the password/pass-phrase is accidentally exposed when entered into a username/ID field, as when such authentication is done in front of a live audience. (I've seen this mistake done several times.)
However the goal is not to add security, excepting that it can mitigate accidents, as the username/ID is not a password: it is not "encrypted", hashed, or otherwise considered a secret.
Using an additional credential provider (e.g. RSA fob, or smart card/fingerprint/pub-private keys) can be used in such cases where increased security is required. Appropriate use of such is much more secure than "two passwords".
In terms of security, access codes are probably slightly more secure than user & pass, considering they're encrypted correctly. This is my opinion
For Rails, you will have to remember 3 important factors:
Who's using your system
How will they engage with the authentication area
Are you using with any other system (such as Devise)?
--
Public Keys
If you're looking to create a preview mode of sorts, I would create a series of API keys, which you'll be able to use to gain limited functionality to the app.
We do that like this:
#users table
id | public_key | other | information | created_at | updated_at
#app/models/concerns/token.rb
module Token
extend ActiveSupport::Concern
included do
before_create :generate_token
end
protected
def generate_token
self.public_key = loop do
random_token = SecureRandom.urlsafe_base64(10, false)
break random_token unless self.class.exists?(public_key: random_token)
end
end
end
#app/models/user.rb
include Token
I found this code somewhere (I forgot where unfortunately), but it basically uses the before_create callback to populate the public_key attribute of your User model.
The public key is created using the SecureRandom method

How to generate an API Key and Secret in Ruby on Rails?

E.g. API_key: 4faa86aa5848207502000002 and API_secret 7375d7d1e89d3d602b184432fbcf3c09c7cb30676f19af9ac57d228be401.
Should I use SecureRandom?
Thanks!
ActiveSupport::SecureRandom would work for the actual generation, but you should also consider a way to invalidate and reset the token on different events.
Since you're using Devise, take a look at the Token Auth Strategy. You could write a similar strategy with two tokens (API Key and API Secret, respectively). You need to write both the strategy and the Model, but in both cases the Token Auth example gets you pretty far.
As a starting point (from the Token Auth example), your model should declare both required parameters.
module Devise
module Models
module APIKeyAuthenticatable
...
def self.required_fields(klass)
[:api_key, :api_secret]
end
def reset_keys
self.api_key = self.class.api_key
self.api_secret = self.class.api_secret
end
You might also want to read Custom authentication strategy for devise. If you're looking to provide a more full-featured API auth solution atop devise devise_oauth2_providable looks pretty good.
I have tried authlogic_api. It was fairly easy to implement.

Playing with the Yahoo Fantasy Sports API in IRB

I want to play around with the Yahoo Fantasy Sports API. I have no idea where to start. What do I need to do in order to start playing with the API in IRB and start calling and retrieving different players or stats? This is my first attempt at tackling an API that does not have a readily available Ruby gem.
Yahoo Fantasy Sports API: http://developer.yahoo.com/fantasysports/guide/
I've followed the steps detailed in the dev guide and set up my developer Consumer key and Secret key. Not sure what to do with this information though.
I'm using Rails 3.2.2 and Ruby 1.9.2
I've been spending many hours the past couple weeks trying to get a site to tie in with the Yahoo fantasysports API and only recently got over the hurdle of being able to get authenticated through OAuth, make valid requests, and refresh access tokens indefinitely. Here are the steps you need to take to be able to mess around in IRB:
Gem stuff
Include oauth-plugin in your Gemfile. This will also install the OAuth/OAuth2 ruby gems as dependencies. This will handle the request/access tokens needed, but won't be fully compatible right out of the box.
The oauth-plugin requires an authentication system to be in place for your app. I would highly recommend devise both for its ease of use and the fact that the oauth-plugin pretty much works along with it with very little setup. I found it easier to connect the two if I first generate 'User' through devise before I generated a consumer with the oauth-plugin. There are tons of guides for devise if you run into issues.
Migration stuff
A quirk of the Yahoo version of OAuth is that you need to store a 'session_handle' for a user in order to refresh the access token when it expires after 60 minutes. I just edited the existing OauthConsumerToken migration to include t.string :session_handle. I'm not sure what the issue was with MYSQL when I did this, but I had to also specify a limit of 190 for the consumer_tokens index that oauth created. So my full add index is add_index :consumer_tokens, :token, :unique => true, :length => 190. I would recommend also adding a :guid string column to the users table, since that is what Yahoo uses as a unique identifier.
To accommodate the two extra columns we are tracking that oauth doesn't know about (session handle and guid), you need to override some of the oauth-plugin default behaviour. I've already forked the project and made the necessary changes if you want to just use my version (https://github.com/JamesSwift/oauth-plugin). The three dependencies for my version are a session_handle column in the ConsumerTokens table, a yahoo_guid column in the Users table, and set the variable CB_URL in your development.rb to be the url that you registered your app under with Yahoo. Also remember that if you use my version of the plugin you need to specify the path/github url depending on how you want to include it.
Configuration stuff
You need to set the :key and :secret in config/intializers/oauth_consumers.rb. I call my consumer a YahooToken, so mine looks like this:
OAUTH_CREDENTIALS={
:yahoo => {
:key => "the key given to me by yahoo"
:secret => "the secret given to me by yahoo"
}
}
load 'oauth/models/consumers/service_loader.rb'
You also need to specify the global yahoo settings in the custom token class you created. Again, mine is a YahooToken, so in app/models/yahoo_token.rb my first few lines are:
class YahooToken < ConsumerToken
YAHOO_SETTINGS={
:site=>"http://fantasysports.yahooapis.com/fantasy/v2",
:authorize_url =>"https://api.login.yahoo.com/oauth/v2/request_auth",
:request_token_url => "https://api.login.yahoo.com/oauth/v2/get_request_token",
:access_token_url => "https://api.login.yahoo.com/oauth/v2/get_token",
:oauth_version=>"1.0"
}
Tell your user model it has a token:
has_one :yahoo, :class_name=>"YahooToken", :dependent=>:destroy
Actually doing stuff, stuff
Now you can load up your server, create a user, and go to http://localhost:3000/oauth_consumers/yahoo to get your token. Yahoo refuses to redirect you back to localhost, so you will end up getting redirected to CB_URL/parameters-that-yahoo-returns. Copy the parameter string and go to http://localhost:3000/oauth_consumers/yahoo/callback/paste-the-string-here. This should successfully complete the initial retrieval of the access token. Don't worry, you only need to do this once per local user while developing locally. It automatically redirects in production environments.
Now that you have a token you can use it in IRB for the next hour as much as you want. Here is an example of using the token from the console:
t = User.first.yahoo
resp = t.client.get("http://fantasysports.yahooapis.com/fantasy/v2/users;use_login=1")
puts resp.body
You can also put &format=json at the end to get it in json instead of xml
After 60 minutes you need to get a new access token. I have a refresh function in my YahooToken:
def refresh!
old_one = self
request_token = OAuth::RequestToken.new(YahooToken.consumer, self.token, self.secret)
options={}
options[:oauth_session_handle]=self.session_handle
access_token = request_token.get_access_token options
new_one = YahooToken.find_or_create_from_access_token self.user, access_token
if new_one
old_one.delete
return new_one
end
return nil
end
Whenever my token expires I just t = t.refresh!. I would also recommend an age method on your tokens which will facilitate creating a rake task that will refresh tokens every hour for you automatically.
All the available fantasysports related resources are listed here:(http://developer.yahoo.com/fantasysports/guide/)
To get started, I would suggest you familiarize yourself with OAuth, which the Yahoo Fantasy Sports API uses for authentication. You will also need to know how to make HTTP requests in Ruby. Most of the rest of work will be in reading the API documentation and experimenting.
If you're looking for Ruby libraries for using OAuth or making HTTP requests, or anything else you run into, you may find The Ruby Toolbox helpful.
I prefer pelle/oauth-plugin save session_handle in refresh! rather then fork it.
consumer wrapper
model/YahooToken.rb inspired by #JamesSwift.
Migration
I authorizate with omniauth-yahoo, so the guid save in Authorization model and you need to add session_handle(:string) and change token(:text) column in consumer_tokens.
enjoy.

Prevent Authlogic from establishing a session/cookie for non-HTML requests

I'm using Authlogic and Rails 3. On top of the regular browser-based user experience (logging in via form and whatnot), I'd like to implement an API.
Authlogic seems to support single access tokens that don't persist by default. I supply them by adding a GET argument as in:
/users.xml?user_credentails=my_single_access_token
Question: Is there any way I can have Authlogic accept the API key via HTTP Basic Auth? Highrise does something just like this, allowing for:
curl -u 605b32dd:X http://sample.highrisehq.com/people/1.xml
The same with Freshbooks:
curl -u insert_token_here:X https://sample.freshbooks.com/api/2.1/xml-in -d '[xml body here]'
How I would go about imitating this functionality? I can't even figure out where the input data (postdata from forms, HTTP basic, API token) are taken in. I've boiled it down to a call to UserSessions.find with no arguments, but I lose track of it after there.
Any help would be much appreciated!
Related question: I'd also like to disable session persistence (make it so that no cookie is stored) if HTTP basic is used. Any help on this too would be appreciated!
If you're implementing an API, you could consider building a separate Rack application that is then mounted at '/api/1.0/...' and shares your models.
That way you are not tying yourself into having your API directly related to your public routes, which could be difficult to construct for the API user.
A good approach would be to create a simple Sinatra application that exposes just the methods that you want, and to then create a separate authentication strategy:
require 'sinatra'
require 'active_support' # all the Rails stuff
require 'lib/user' # your User class
require 'sinatra/respond_to' # gem install sinatra-respond_to
Sinatra::Application.register Sinatra::RespondTo
use Rack::Auth::Basic, "API", do |username, password|
User.find_by_login(username).valid_password?(password)
end
get '/api/1.0/posts' do
#posts = Post.recent # assuming you have a Post model...
respond_to do |wants|
wants.xml { #posts.to_xml }
wants.to_json { #posts.to_json }
end
end
get '/api/1.0/users/:id' do
#user = User.find_by_login(params[:id])
# Careful here - don't release personal details!
respond_to do |wants|
wants.xml { #user.to_xml }
wants.to_json { #user.to_json }
end
end
Versioning your API with a '1.0' (or similar) in the path means that if you change your models you can create a new version of your API without breaking your users' existing code.
Using this you should be able to allow users to authenticate with HTTP Basic in the form:
curl -u steven:password http://example.com/api/1.0/users/steven.xml
curl -u steven:password http://example.com/api/1.0/users/steven.json
curl -u steven:password http://example.com/api/1.0/posts.xml
To get this running, save it as 'api.rb', and either run it as a Rack Middleware, or create a 'config.ru' file like so:
require 'api'
run Sinatra::Application
And then from that directory:
rackup
Disclaimer: I'm not a 100% this is possible in the way your describing without hacking up Authlogic's core functionality.
The first issue your going to have is that authlogic prevents the use of SSO tokens for authentication unless the request is ATOM or RSS to override this you need to pass a config paramater see here: http://rdoc.info/github/binarylogic/authlogic/master/Authlogic/Session/Params/Config
To the core issue: I don't see any 'easy' way to handle this functionality, however what you could do for something like curl is pass the user token as a paramater (using the -G option) just like you would when visiting the url.
cURL Documentation: http://curl.haxx.se/docs/manpage.html
Forgive me if I misunderstand your question, but I think the answer is a simple "no." You're mixing two metaphors here. If you want a secure API key, use the single access token; if you want to use http basic access authentication, you need a different base64 glyph -- and http basic auth isn't particularly secure (unless used over https, which isn't generally practical).
In more detail:
Per the wikipedia, http basic authentication is intended to provide a username and password in a simple, standard, but fairly insecure base64 encoded glyph.
To use basic auth, then I believe you want to generate the glyph via a simple
Base64.encode64("#{user.name}:#{password}")
...and I'd probably do this by having the user type their password, since you can't derive the password from the crypted_password that authlogic stores in your database.
But the upshot is that this is a very different beast from the single_access_token, and the two can't be mixed.

How to deal with authentication for a Ruby API wrapper?

I'm working on an API wrapper for Viddler, which will eventually be made public, and I'm trying to figure out the best way to deal with authentication/API keys, specifically with usage within Rails applications in mind.
The easiest way to write the wrapper would be to just have the code create a new client each time, and the developer could store the API key in a constant for future use:
#client = Viddler::Client.new(VIDDLER_API_KEY)
The problem with this is, it's kind of clunky to have to keep creating client objects and passing in the API key. This gets even more complicated when you throw user authentication into the mix.
I'm thinking some sort of solution where I all the the API key to be set in the environment file and then the authentication would be done in a before_filter.
Viddler::Client.api_key = 'abc123'
Viddler::Client.authenticate! 'username', 'password'
Viddler::Client would then store this in a class variable, and you could call Viddler::Client.new without any parameters and make authenticated calls. One thing I'd be concerned about is that this means the developer would have to be sure to clear out the authentication before or after each request, since the class variables would persist between requests.
Any thoughts?
Storing the API key globally would for sure be pretty useful and certainly is the way to go for that kind of information. User authentication on the other hand I think shouldn't be stored globally, never ever, especially for a high level API, because telling your users to "ensure to add an after_filter :reset_viddler_auth" might lead to some unexpected security risks.
# in a config/initializer/*.rb file or something
Viddler::Client.api_key = "abc123"
# in the controller/action/model/wherever
#client = Viddler::Client.new # anonymous
#client.authenticate!("username", "password") # authenticate anon client
#client_auth = Viddler::Client.new("username", "password") # authenticated client
Guess like that you've best of both worlds :) Maybe even provide a way to create a new client with another API key like,
#client_other = Viddler::Client.new("username", "password", :api_key => "xyz890")
So... just my 2 cents.
PS: not sure how up-to-date it is, but there's already a ruby viddler wrapper, just FYI, http://viddler.rubyforge.org/rdoc/

Resources