In what kind of use are Servicestack's sessions in a multi-server environment scalable? - scalability

When a user is authenticated, the session is stored using the defined ICacheclient, which can be memory, memcached or redis.
If I use two web server, I suppose the session is only populated on the server where the authentication was done, am I right ?
Do I have to use sticky session in this scenario (memory cache client) ?

We have a pair of servers delivering our web app and can scale as we need. We use REDIS to maintain a clients authentication status through the ICacheclient interface. To enable easy testing on the build machine we switch between redis and memcached based on whether a debugger is attached. Our Apphost.cs has this:
if (Debugger.IsAttached)
{
container.Register<ICacheClient>(new MemoryCacheClient());
}
else
{
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager(AppConfig.REDISUrl + ":" + AppConfig.REDISPort));
container.Register<ICacheClient>(c => (ICacheClient)c.Resolve<IRedisClientsManager>().GetCacheClient());
}
The built in authenication methods just work with whatever the ICacheClient is set to as described in the documentation

Related

How do I cache external api response per user in rails?

I have a rails 4.1 application wherein I have a bookings section. In this section I have multiple sub sections such as Hotels, Vacation rentals etc.
In each of these sections I am fetching data from relevant apis which user can sort and filter.
I would like to cache response per user so that once I have got the data, filtering & sorting is quick with cached data and time is not wasted on making another trip to the other site.
However one issue is users can do this even without logging in.
I have setup memcached store which caches the data fine for the first user but, when second user comes data for first user gets overwritten.
How can I cache data per user(logggd/unlogged) ? (I am willing to change the cache store provided I don't have to spend anything extra for it to work)
Rails actually supports per user "caches" out of the box. Its called the session. By default Rails uses cookies as storage but you can easily swap the storage to use Memcached or Redis to bypass the ~4kB size limit imposed on cookies by the browser.
To use Rails with memcached as the storage backend use the Dalli gem:
# Gemfile
gem 'dalli'
# config/environments/production.rb
config.cache_store = :dalli_store
This will let you store pretty much anything by:
session[:foo] = bar
And the session values can be fetched as long as the user retains his cookie containing the session id.
An alternate approach if you want to keep the performance benefits of using CookieStore for sessions is to use the session id as part of the key used to cache the request in Memcached which would give each user an individual cache.
You can get the session id by calling session.id in the controller.
require 'dalli'
options = { :namespace => "app_v1", :compress => true }
dc = Dalli::Client.new('localhost:11211', options)
cache_key = 'foo-' + session.id
#data = dc.fetch(cache_key) do
do_some_api_call
end
You can do this with view fragments and the regular rails low level cache as well. Just be note that models are not session aware.
See:
http://www.justinweiss.com/articles/how-rails-sessions-work/
http://guides.rubyonrails.org/caching_with_rails.html

Rails authentication across apps/servers

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.

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/

Can't understand sessions in Rails

Please don't bit my for my misunderstanding.
The sessions are very new for me, and i have some problems.
Okay i read many information about sessions and especially rails session. But this don't give me right imagine about sessions.
Did i understand right, when users send request to server (get) -> Server create a new session (and store this some file in hard drive with session id), session id -> is a random generated num? so, server create a new session (and store session on drive) after this server send back answer to client and set session_id in cookies?
Ok, i debug some params and see some results:
debug(session):
{:_csrf_token=>"jeONIfNxFmnpDn/xt6I0icNK1m3EB3CzT9KMntNk7KU=", :session_id=>"06c5628155efaa6446582c491499af6d", "flash"=>{}}
debug(cookies):
{"remember_user_token"=>"1::3GFRFyXb83lffzwPDPQd", "_blog_session"=>"BAh7CDoQX2NzcmZfdG9rZW4iMWplT05JZk54Rm1ucERuL3h0NkkwaWNOSzFtM0VCM0N6VDlLTW50Tms3S1U9Og9zZXNzaW9uX2lkIiUwNmM1NjI4MTU1ZWZhYTY0NDY1ODJjNDkxNDk5YWY2ZCIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNoSGFzaHsABjoKQHVzZWR7AA==--348c88b594e98f4bf6389d94383134fbe9b03095"}
Okay, i know, what _csrf_token helps to prevent csrf.
session_id -> is id of the session which stored on hard drive (by default)
but what is _blog_session in cookies?
also, remeber_user_token containes my id (1::*) and what about second part, what is it?
Sorry for this stupid questions, i know what i can easy use any nice auth-plugins (authlogic/clearance/devise), but i want to fully understand sessions.
Thank you.
(also sorry for my english, this is not my native language)
remember_user_token is probably set by your authentication plugin, it is encrypted string, which is stored in users table and is used to authenticate him. Details can vary between plugins.
Second part: you are probably using cookie based session store (it is default),
So, _blog_session stores your encrypted session data.
More about cookie based sessions here and here.
The name "_blog_session" is set in config/initializers/session_store.rb
It looks like:
# Your secret key for verifying cookie session data integrity.
# If you change this key, all old sessions will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
ActionController::Base.session = {
:key => '_blogs_session',
:secret => '07fb6f0d41af4ae06aebb1696fcbb5a5398d4a08570744a4cd53ff237020c43a2022b4041d617d95bcf3f5c4601c7e6c1646eecfc157cc200e7dfedd7d7c6813'
}

Resources