Using devise "rememberable" without cookies - ruby-on-rails

I have a working Rails site that uses devise to manage users. For session management, I am using devise's rememberable strategy, which stores and retrieves encrypted authentication information from a user's cookie.
I'm implementing a multi-photo upload widget that uses flash. Flash does not support sending cookies along with requests. This is a problem with multiple multi-upload flash+javascript libraries, so fixing this shortcoming is probably not feasible.
So my question is: can I successfully authenticate to devise/rememberable without using cookies? And if so, how?
More details
Devise/rememberable depends on the value of remember_token within the cookie. If I could fool Rails into thinking that the value was supplied as a cookie (e.g. request.cookies['remember_token'] = '...'), my problem would be solved. Devise/rememberable would find the correct value there, unpack it, and successfully authenticate. However, the request.cookies hash is apparently read-only. Writing to the hash is silently ignored. Example (debug console from an incoming POST request):
>> request.cookies['remember_token'] = 'a string'
=> "a string"
>> request.cookies['remember_token']
=> nil
>> request.cookies
=> {}
I'm using (or trying to use) the FancyUpload v3 widget.

How about overriding Devise slightly?
Based on Devise 1.2.rc something like this should work:
module Devise
module Strategies
class Rememberable
def remember_cookie
# your code to get the hashed value from the request
end
end
end
end
Alternatively, you could add a new (subclassed) strategy:
module Devise
module Strategies
class RememberableParameter < Rememberable
def remember_cookie
# your code to get the hashed value from the request
end
end
end
end
Warden::Strategies.add(:rememberable_parameter, Devise::Strategies::Rememberable)
Or, look into Token Authenticatable:
Token Authenticatable: signs in a user based on an authentication token (also known as
"single access token"). The token can be given both through query string or
HTTP Basic Authentication
There's more about it here:
https://github.com/plataformatec/devise/blob/master/lib/devise/models/token_authenticatable.rb
Good luck!

Related

GraphQL/ Rails 422 Unprocessable Entity - Saving token to Session

I’m currently working on changing the rails backend of a project from REST to graphql and I’m running into an error with authentication following their tutorial - https://www.howtographql.com/graphql-ruby/4-authentication/
I’m using the GraphiQL engine to test all my requests and receiving error status 422 unprocessable Entity, User Must Exist
Which makes sense because the mutation I am executing is to create a new color - which has a belongs_to relationship to User.
About halfway through the page linked above ^^^ it says this:
With the token that the signinUser mutation provides, apps can
authenticate subsequent requests. There are a couple of ways this can
be done. In this tutorial, we are just going to use the built-in
session, since this doesn’t add any requirements to the client application. The GraphQL server should be able to get the token from
the session header on each request, detect what user it relates to,
and pass this information down to the resolvers.
I’m able to successfully return an auth token through the signinUser method like the docs show previously on the same page - the method that it posts to also saves the token to this supposed session in this method here (also from the same link posted above ^^^) :
def call(_obj, args, ctx)
input = args[:email]
return unless input
user = User.find_by email: input[:email]
return unless user
return unless user.authenticate(input[:password_digest])
crypt = ActiveSupport::MessageEncryptor.new(ENV["SECRET_BASE_KEY"])
token = crypt.encrypt_and_sign("user-id:#{ user.id }")
puts "please **********************************"
p ctx[:session]
ctx[:session][:token] = token
puts "please **********************************"
p ctx[:session]
OpenStruct.new({
user: user,
token: token
})
end
You’ll be able to see in my desperate struggle that I p’d out the session right before the method returns and not surprisingly saw that it contained the token for that users sign in.
However, when I proceeded to execute the mutation to create a color, my expectation was that the session would still contain that token and I’d be able to commit that color successfully. That was not the case and when I p’d out the session for this request, it return an empty hash.
I cant find any information about how the built in graphql session works - and I’m brand new to graphql in general.
My main questions would be - is the graphql session supposed to be caching token information? Why is it that the information is not carrying over to requests following signinUser ? Should I even bother with trying to use the auth in this tutorial since the docs claim that this authentication method is not a long term solution?
I know this is a lot but would really appreciate an extra brain on this.
Thanks in advance!
PS. I understand the tutorial uses links and I am using colors here - that is intentional and I have done my best to make sure that semantic differences were not causing any errors.
Rails version - 5.2.2 (using api only)
graphql - 1.7.4
graphiql rails - 1.4.4
Same as REST APIs GraphQL does not store any information between two subsequent requests, You have to pass authentication token returned in sign in mutation to all subsequent requests where you want current user-related information.
You should do something like below in graphql_controller.rb
class GraphqlController < ApplicationController
def execute
variables = ensure_hash(params[:variables])
query = params[:query]
operation_name = params[:operationName]
context = {
current_user: current_user
}
result = GraphqlTutorialSchema.execute(query, variables: variables, context: context, operation_name: operation_name)
render json: result
end
private
# set current user here
def current_user
# you can token here
token = request.headers['Authorization']
return nil unless token
# find current user from this token
end
# Handle form data, JSON body, or a blank value
def ensure_hash(ambiguous_param)
# ...code
end
end
A colleague of mine pointed out that "session" is part of rails and should create a cookie that would be accessible from the next request.
I mentioned that I was using Rails version - 5.2.2 (using api only) - well when you use the -api flag when initializing a new rails project, it adds these lines to application.rb
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
# Only loads a smaller set of middleware suitable for API only apps.
# Middleware like session, flash, cookies can be added back manually.
# Skip views, helpers and assets when generating a new resource.
config.api_only = true
Notice this line in particular - Middleware like session, flash, cookies can be added back manually.
I commented out config.api_only = true and this added cookies back to the application/ allowed me to make the next request with an existing user.
You can also add these lines I found from "Lysender" on his post - Rails 5 – API Only – Enable Cookies and Sessions if you'd prefer not to remove the api-only feature.
config.api_only = true
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore, key: '_coookie_name', expire_after: 30.days

What password hashing algorithm does devise use?

I would like to store and validate passwords in a ruby application that does not use devise, and have them be compatible with a future application that does use devise. What is the default password hashing scheme that devise uses, and is it possible to extract and use just this component from devise?
Devise's DatabaseAuthenticatable module uses BCrpyt to hash passwords, wrapped up in the Devise::Encryptor module. The relevant method, digest, is pretty simple:
def self.digest(klass, password)
if klass.pepper.present?
password = "#{password}#{klass.pepper}"
end
::BCrypt::Password.create(password, cost: klass.stretches).to_s
end
klass is only used to fetch a couple parameters: pepper, a string which is appended onto the password pre-hashing but not stored in the database (unlike salt, which is appended as well but stored with the password in the DB); and cost, a measure of how secure the hash should be (see the docs). Both of these are static and you can hard-code them into your non-Devise app (but make sure to keep pepper secret!).
So, your hash method might be written just as:
def self.digest(password)
password = "#{password}#{ENV['PASSWORD_PEPPER']}"
::BCrypt::Password.create(password, cost: 10).to_s
end

Find_for_database_authentication vs Find_by in Rails Devise app?

So, I'm trying to set up a React frontend and Rails backend with devise, and the Rails side is supposed to be an internal API. It's the first time I've ever done this, so I'm struggling with authentication. Specifically, in my SessionsController, I have this code:
def create
resource = User.find_for_database_authentication(email: params[:email])
return invalid_login_attempt unless resource
if resource.valid_password?(params[:password])
sign_in :user, resource
return render nothing: true
end
invalid_login_attempt
end
This always returns 401 Unauthorized. I check the result of calling valid_password? and it is always false.
However, if I replace find_for_database_authentication with find_by, the valid_password? works with no problems. Why is this? It's okay if for now the user can only enter his email and not his password, but this really confuses me. It also bugs me that this doesn't use any token checking (different issue).
On the side, I'm also wondering about whether or not CSRF tokens are okay for internal APIs (should I use a different token-auth?), and how I'm supposed to include a CSRF token with a login form if the user isn't logged in yet, but I guess those are questions for another post. Thanks for any help.

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.

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.

Resources