Encrypt the Devise token - devise

I'm using Devise with token Authentication and, now, I want to encrypt the token in the Database.
Can anyone give me a hint where devise does the storing/retrieving of the token from the DB?
I'm also using the attr_encrypted gem with which the whole encryption should be rather easy once the right location is found.
Edit:
I have implemented token authentication like it is described here: http://zyphdesignco.com/blog/simple-auth-token-example-with-devise
I added the following line in the user model, which should encrypt the authentication_token
attr_encrypted :authentication_token, :key => 'a secret key', :attribute => 'authentication_token'
When I run it and try to login, I get following error message:
Completed 500 Internal Server Error in 364ms
SystemStackError - stack level too deep:
(gem) actionpack-3.2.13/lib/action_dispatch/middleware/reloader.rb:70:in `'
It seems there is a conflict with devise and attr_encrypted and that both are fighting over redefinition of the authentication_token method (thx for the hint #sbfaulkner)
Maybe someone had a similar problem and knows a solution?

The important bits about the Token Authenticable strategy are in the Devise::Models:: TokenAuthenticatable module - it works with a set of simple methods:
The find_for_token_authentication is used to authenticate the resource
ensure_authentication_token/ensure_authentication_token! should be used to generate a token for a fresh resource - Devise won't call it by itself.
If the attr_encrypted gem is compatible with AR models then I believe that you won't have any problems with Devise, but the best way to be sure of that is to trying it out.

Here is how I did it, on my User model:
before_save :ensure_authentication_token
attr_encrypted :authentication_token, :key => 'my key'
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = User.encrypt_authentication_token(Devise.friendly_token)
break token unless User.where(encrypted_authentication_token: token).first
end
end
The secret is in this method: encrypt_authentication_token that attr_encrypted creates.

Related

How to enable CSRF in Rails 5 API mode

I have a Rails API that's authenticated with an http-only cookie, and as such I require CSRF protection. From what I can tell, the Rails community seems to prefer storing jwt auth tokens in local storage rather than in a cookie. This avoids the need for CSRF but exposes you to XSS, which is why we chose to use cookies + csrf.
It seems that CSRF protection is disabled by default due to the community preference for local storage. I am trying to enable it with limited success. Here is how I'm attempting to handle it:
module V1
class ApplicationController < ::ApplicationController
include Concerns::Authentication
include ActionController::RequestForgeryProtection
protect_from_forgery
protected
def handle_unverified_request
raise 'Invalid CSRF token'
end
after_action :set_csrf_cookie
def set_csrf_cookie
if current_user
cookies['X-CSRF-Token'] = form_authenticity_token
end
end
end
end
On the client side, I can see that the token comes back in the cookie. When I make a request, I also see that the token is present in the X-CSRF-Token header. All looks well so far.
However, the verified_request? method returns false, so handle_unverified_request gets invoked. Stepping through the Rails code, I see that my token is present in request.x_csrf_token, but the token appears to fail verification when it's checked against the session. One thing I'm wondering here is if I need to enable something to get the session to work correctly, as I understand that session management isn't turned on be default in API mode. However, if that were the case I would sort of expect attempts to access the session object to blow up, and they don't, so I'm not sure.
Have I made an error, or is there some other middleware I need to turn on? Or do I need a different approach altogether to enable CSRF with this scheme?
I realize that this was a case of overthinking the problem. I really don't need Rails's forgery protection to do anything for me, or to check the value against the session, because the value of my token is already a cookie. Here's how I solved it:
First, the base controller sets the csrf cookie. This would be skipped for logout or any public endpoints, if there were any.
module V1
class ApplicationController < ::ApplicationController
include Concerns::Authentication
include ActionController::RequestForgeryProtection
after_action :set_csrf_cookie
protected
def set_csrf_cookie
if current_user
cookies['X-CSRF-Token'] = form_authenticity_token
end
end
end
end
Then my authenticated endpoints inherit from an AuthenticatedController that checks the auth token and the csrf token:
module V1
class AuthenticatedController < ApplicationController
before_action :authenticate!
def authenticate!
raise AuthenticationRequired unless current_user && csrf_token_valid?
end
rescue_from AuthenticationRequired do |e|
render json: { message: 'Authentication Required', code: :authentication_required }, status: 403
end
rescue_from AuthTokenExpired do |e|
render json: { message: 'Session Expired', code: :session_expired }, status: 403
end
private
def csrf_token_valid?
Rails.env != 'production' || request.headers['X-CSRF-Token'] === cookies['X-CSRF-Token']
end
end
end
Hope this helps someone else trying to use CSRF + cookies in a Rails 5 API!

Assigning a random password while using Devise

Here's a breakdown of the situation -
Desired behavior: user can sign_up through Devise gem by providing their email address only. Web app generates and db stores a temporary password, unknown to user.
Logic: This is meant to be a 'gradual sign-up' process to a web application still under construction (we want to begin capturing potential users without providing access to the web app as it's still in partial development). The email is to be used for communication purposes until final release.
Problem: Devise gem requires user to input email && password during sign_up process. We've found no obvious way to circumvent the dual requirement. User failing to provide password generates error.
Potential solution: After searches and many tries, this seems to be the closest alternative (found here).
generated_password = Devise.friendly_token.first(8)
user = User.create!(:email => email, :password => generated_password)
Question: While this potential solution makes sense, we're REALLY new to this and don't understand in which file to place this code within the Devise configuration, and how to call it.
All help appreciated.
LM
OK, I kept digging until I found what I was looking for (here) - maybe it can help you too.
In your model:
before_validation :generate_password, :on => :create
def generate_password
o = [('a'..'z'), ('A'..'Z'), (0..9)].map{|i| i.to_a}.flatten
self.password = self.password_confirmation = (0..16).map{ o[rand(o.length)] }.join if self.password.blank?
end

Authorization error with pengwynn linkedin gem

I would like to point out I am a newbie, and not a dev so I might miss some basic step here.
I am trying to figure out how to authorize through omniauth-linkedin gem and query Linkedin API through pengwynn 'linkedin' gem.
I can connect the user through oauth, create the devise-user entry and so on, all good there.
The problems arise when I try to query the API, specifically I would be interested in getting the list of skill for the user. I have this code under my users_controller.rb
def show
#user = User.find(params[:id])
token = #user.access_token
secret = #user.access_secret
client = LinkedIn::Client.new(ENV["LINKEDIN_KEY"], ENV["LINKEDIN_SECRET"])
client.authorize_from_access(token, secret)
raise client
end
I am raising the client just to have a play with the newly created client, unfortunately when querying client.profileI get 401 error:
LinkedIn::Errors::UnauthorizedError: (401): [unauthorized]. The token used in the OAuth request is not valid. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx>
What am I getting wrong here?
Before of raise client you should provide the permissions. You should make a redirect to:
redirect_to request_token.authorize_url
For example:
def show
client = LinkedIn::Client.new(ENV["LINKEDIN_KEY"], ENV["LINKEDIN_SECRET"])
request_token = client.request_token({:oauth_callback => "callback url here"}, :scope => "rw_nus r_fullprofile r_emailaddress")
client.authorize_from_access(request_token.token, request_token.secret)
redirect_to request_token.authorize_url
end
In your callback url you should save the tokens for future requests.
Good Luck!

How to use 'Has_secure_password', when trying to refactor?

I am trying to refactor the railstutorial authorization mechanism.
My version of rails is 3.2.0 and I am using ruby 1.9.3-p0 and postrgresql 9.1.
So far, my tests are passing when it comes to a failed attempt to sigin but the successfull sign in fails.(The reason is that I have to refactor the old signin mechanism)
Here is my session helpers sign_in function:
def sign_in(employee)
cookies.permanent.signed[:remember_token] = [employee.id, employee.salt]
self.current_employee = employee
end.
One problem I see immediately with the sign_in function is that has_secure_password already takes care of the encryption and salt etc ... my thinking was that maybe I should use password_digest instead of employee.salt, but that failed as well.
I would like to have my cookies expire after two hours. I found this option at api.rubyonrails.org under cookies.
cookies[:key] = {
value => "employee.id, employee.salt",
expires => 2.hours.from.now
}
Another question I have has to do with the fact that has_secure_password already has an authenticate method so that means that I do not have to use the authenticate definition defined in the employee model,(user model) in the rails tutorial, but when I comment it out I get a flag reading:
NoMethodError: undefined method 'authenticate'
Here is my session controllers create action:
def create
employee = Employee.authenticate(params[:session][:email],
params[:session][:password])
if employee.nil?
flash.now[:error] = "Invalid email/password combination."
#title = "Sign in"
render 'new'
else
sign_in employee
redirect_back_or employee
end
end
It seems the Employee.authenticate is a problem.
So I essentially have three question and they are as follows:
In the rails tutorial we go through a fairly lengthly process of encrypting and applying salt etc to the employees password. Since has_secure_password has this already taken care of, what variable would I pass to my functions or arguments that would capture the encrypted password?
The next question has to do with the expiration of the cookie, and how I would use that in the sign_in function?
Lastly, how do I use the authenticate method so that rails recognizes it as a genuine method?
Just for the record, I have searched through railsguide, api.rubyonrails.org and other questions asked on SO that are similar to this one. Of course this merely points up my lack of understanding of the principles, but I am learning and do take direction well.
Thanks for any thoughts, suggestions and or resources you might share with me.
Update
I re-read the api on has_secure_password and authenticate takes only one argument, namely an unencrypted password ... so I have something to work with.
I still need any help or thoughts or suggestions that you might offer ... thanks.
update
I found this article that deals with session timeouts:
http://madkingsmusings.blogspot.com/2011/05/session-timeouts-on-rails.html
I am still working to see if I can get it to work for me, but it is tailored for the railstutorial.
As for the other questions, Michael Hartl is busy pushing out the second edition of Ruby on Rails tutorial and in that edition he will be dealing with has_secure_password.
The new version of the railstutorial is available.
For the first and last question question... You'll find the authentication extremely simple.
In the User model:
has_secure_password
In the SessionController:
def create
user = User.find_by_email(params[:session][:email])
if user && user.authenticate(params[:session][:password])
sign_in user
redirect_back_or user
else
flash.now[:error] = 'Invalid email/password combination'
render 'new'
end
end
In the SessionsHelper:
def sign_in(user)
cookies[:remember_token] = user.remember_token
current_user = user
end
It should have been obvious but I didn't even think about looking for the code on github. Maybe someone else will appreciate the link.
Here is Hartl's 2nd edition Sample_App source on github

No POST from facebook using real-time updates in Rails, Heroku and Koala

This question is an expanded version of Facebook Real-time updated does not call our servers, that seems to be dead. Also, Realtime updates internal server error on Heroku using Koala is not helpful because I'm subscribing from the heroku console as pjaspers suggested.
I have an app (ruby 1.9.2p290 and Rails 3.1.3) that connects to facebook to get data from the current user. Everything is working ok with the koala gem (v1.2.1), but I'm polling the fb servers every time the users logs in. I would like to use facebook real-time updates, and I have read the following:
Koala manual on fb realtime updates: https://github.com/arsduo/koala/wiki/Realtime-Updates
Facebook page on realtime: https://developers.facebook.com/docs/reference/api/realtime/
I have set up the system in test mode and deployed to heroku successfully. I can subscribe to the user object and I get the GET request to my server, but no POST with updated information is ever received from facebook. If I issue a POST to my server manually everything works.
More information:
routes.rb
get '/realtime' => 'realtime#verify'
post '/realtime' => 'realtime#change'
generating
realtime GET /realtime(.:format) {:controller=>"realtime", :action=>"verify"}
POST /realtime(.:format) {:controller=>"realtime", :action=>"change"}
The controller (mock version, only to test if it's working):
class RealtimeController < ApplicationController
def verify
render :text => params["hub.challenge"]
end
def change
puts params.inspect
render :nothing => true
end
end
The subscription from the heroku console:
irb(main):004:0> #updates = Koala::Facebook::RealtimeUpdates.new(:app_id => ENV['FACEBOOK_APP_ID'], :secret => ENV['FACEBOOK_APP_SECRET'])
=> #<Koala::Facebook::RealtimeUpdates:0x00000004f5bca8 #app_id="XXXXXXX", #app_access_token="XXXXXXX", #secret="XXXXXXX", #graph_api=#<Koala::Facebook::API:0x00000004a8d7a8 #access_token="XXXXXXX">>
irb(main):005:0> #updates.list_subscriptions
=> [{"object"=>"user", "callback_url"=>"http://blah-blah-0000.herokuapp.com/realtime", "fields"=>["education", "email", "friends", "name", "website", "work"], "active"=>true}]
I don't know what to do next...
Maybe I am not triggering the correct changing events?
How do I see the list of users of my app? (right now it's a test app and the only user would be me)
Anyone with this kind of issue?
Is something wrong in the code?
Is facebook down? Is it the end of Internet?
Thank you for the help :)
You need to respond to the GET request with a challenge response. I have the same route for both POST and GET requests and use the following code:
route:
match "facebook/subscription", :controller => :facebook, :action => :subscription, :as => 'facebook_subscription', :via => [:get,:post]
controller:
def realtime_request?(request)
((request.method == "GET" && params['hub.mode'].present?) ||
(request.method == "POST" && request.headers['X-Hub-Signature'].present?))
end
def subscription
if(realtime_request?(request))
case request.method
when "GET"
challenge = Koala::Facebook::RealtimeUpdates.meet_challenge(params,'SOME_TOKEN_HERE')
if(challenge)
render :text => challenge
else
render :text => 'Failed to authorize facebook challenge request'
end
when "POST"
case params['object']
# Do logic here...
render :text => 'Thanks for the update.'
end
end
end
That should get you going with things... Note that to make a subscription I am using this:
#access_token ||= Koala::Facebook::OAuth.new(FACEBOOK_API_KEY,FACEBOOK_API_SECRET).get_app_access_token
#realtime = Koala::Facebook::RealtimeUpdates.new(:app_id => FACEBOOK_API_KEY, :app_access_token => #access_token)
#realtime.subscribe('user', 'first_name,uid,etc...', facebook_subscription_url,'SOME_TOKEN_HERE')
I think the key is that you properly respond to the GET request from Facebook. They use this to verify that they are contacting the correct server prior to sending confidential info about their users.
Also -- its been a while since I've looked at this, but if I remember correctly, I seem to recall having issues with anything besides default protocol port specifications within the callback URL. Ex: http://www.something.com:8080/subscription did not work -- it had to be http://www.something.com/subscription
Not sure if this might be the case with you, but make sure that you have permissions to access the object -user, permissions, page- properties (location, email, etc.)
In my case, I was trying to get notifications for changes on the location property without my app requiring the user_location permission. For the user object, look at the Fields section in this page https://developers.facebook.com/docs/reference/api/user/
Does Facebook know where your application is? Does it have a URL that it can resolve?