I have an app which connects to an iphone app, which in turn authenticates it's users via http_digest.
I'm using authlogic, and in my schema users of the website are "users" and users of the phone app are "people". So, i have user_sessions and people_sessions. To handle the http_digest auth, i'm using the authenticate_or_request_with_http_digest method like this:
def digest_authenticate_person
authenticate_or_request_with_http_digest do |email, password|
#ldb is just a logging method i have
ldb "email = #{email.inspect}, password = #{password.inspect}"
person = Person.find_by_email(email)
if person
ldb "Authentication successful: Got person with id #{person.id}"
#current_person_session = PersonSession.create(person)
else
ldb "Authentication failed"
#current_person_session = nil
end
return #current_person_session
end
end
I can see in the logs that password is nil: only email is passed through to the inside of the authenticate_or_request_with_http_digest block.
Im testing this with a curl call like so:
curl --digest --user fakename#madeup.xyz:apass "http://localhost:3000/reports.xml"
I'd expect "fakename#madeup.xyz" and "apass" to get passed through to the inside of the block. Once i have the password then i can use a combination of email and password to find (or not) a user, in the normal way. Does anyone know how i can get access to the password as well?
grateful for any advice - max
EDIT - on further googling, i think i'm using this method wrong: i'm supposed to just return the password, or the crypted password. But then how do i compare that against the password passed as part of the http_digest username?
Found the answer: i had a fundamental misunderstanding of how authenticate_or_request_with_http_digest works: after reading the documentation (in the source code of the gem) i realised that the purpose of this method is not to do the authentication, its purpose is to provide the "email:realm:password" string to the browser, let the browser encrypt it, and check the result against it's own calculated (or cached) version of this.
Here's how i set it up:
def current_person
if #current_person
#current_person
else
load_current_person
end
end
#use in before_filter for methods that require an authenticated person (mobile app user)
def require_person
unless current_person
redirect_to root_path
end
end
def load_current_person
#check user agent to see if we're getting the request from the mobile app
if request.env['HTTP_USER_AGENT'] =~ /MobileAppName/
result = digest_authenticate_person
if result == 401
return 401
elsif result == true
#make authlogic session for person
#current_person_session = PersonSession.new(#person_from_digest_auth)
#current_person = #person_from_digest_auth
end
end
end
#this method returns either true or 401
def digest_authenticate_person
authenticate_or_request_with_http_digest(Person::DIGEST_REALM) do |email|
person = Person.find_by_email(email)
#result = nil
if person
#need to send back ha1_password for digest_auth, but also hang on to the person in case we *do* auth them successfully
#person_from_digest_auth = person
#result = person.ha1_password
else
#person_from_digest_auth = nil
#result = false
end
#result
end
end
Related
I'm struggling to find a working method to implement oauth2 login via Facebook & Google for my existing api-only rails app. Login flow & jwt management is done with Devise & Doorkeeper, following this guide.
I tried with Doorkeeper-grants-assertion examples, but none of them is working.
The problem i have is that i can't exchange the provider's token with my jwt token.
Client side (Android and iOS apps) i can login with provider and get the token, but when i try to authorize the user to create a new token, it gives me errors.
The code is the same as examples. In the case of Google i'm skipping token request because i can already get it from client:
class GoogleController
def initialize(auth_code)
#auth_code = auth_code
#user_data = user_data
end
def user_data
url = "https://www.googleapis.com/oauth2/v3/userinfo?access_token=" + #auth_code
response = Faraday.get(url, {:Accept => 'application/json'})
#resp = JSON.parse(response.body)
end
def email
#resp['email']
end
def first_name
#resp['first_name']
end
def last_name
#resp['last_name']
end
def get_user!
# below you should implement the logic to find/create a user in your app basing on #user_data
# It should return a user object
user = User.find_by(email: email)
if user
Rails.logger.info "User"
user
else
user = User.new(email: email, password: Devise.friendly_token.first(10))
user.save
Rails.logger.info "No User"
user
end
end
end
I'm using postman to make requests, below there is the response if my body is:
{
"client_id": "doorkeeper_app_uid",
"client_secret": "doorkeeper_app_secret",
"grant_type": "assertion",
"provider": "google",
"assertion": "MY USER TOKEN" }
{ "error": "invalid_client",
"error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method." }
I just found out i didn't return an User object, that's why Facebook didn't work.
Now, with the same code, only different token endpoint, Facebook login is working and i can find or create the user and return the jwt token, while Google is not.
If someone could point me in the right direction it would be great.
EDIT
after further investigation i'm at this point:
i can find or create my Google authenticated user, but when i return it to doorkeeper assert grant extension, it fails validation
def validate_resource_owner
!resource_owner.nil?
end
in class
PasswordAccessTokenRequest
and i can't generate new jwt token.
What's different from facebook that makes this validation to fail?
Incredible guys, mystical things happens but i've found a solution.
Somehow there was a conflict with 2 providers in doorkeeper.rb initializer if written like so: (Don't do this)
resource_owner_from_assertion do
if provider == "facebook"
g = Api::V1::FacebookController.new(params[:assertion])
g.get_user!
end
if provider == "google"
g = Api::V1::GoogleController.new(params[:assertion])
return g.get_user!
end
end
instead do something like:
resource_owner_from_assertion do
provider = params[:provider]
controller = (provider == "facebook") ? Api::V1::FacebookController.new(params[:assertion]) : Api::V1::GoogleController.new(params[:assertion])
controller.get_user!
end
Then there was another issue inside controllers, because i used "user" as variable name:
user = User.find_by(email: email)
and this is apparently bad, so use
facebook_user = User.find_by(email: email)
Now everything seems to work as it is supposed to. I hope someone will find this useful.
I'm having what may be a simple problem and I cant seem to find a way to fix it though I did find where it is.
What I am trying to do is set up devise password reset with devise_token_auth. I have the email set up with the token generator, and the email links to the end points '/api/auth/passwords/edit' which validates the token and then redirects to my front end password reset form (done in reactjs if it matters), the issue arrises when I actually submit the form, I'm sending the token, I'm also sending the password and confirmation, expiry, uid and all the other headers.
so heres the issue, the devise passwords controller calls the before action set_user_by_token, and i found through some debugging that this is where the issue lies, I created and override module with the default code for the set_users_by_token, and through the use of binding.pry I saw that no values were coming in through the method call, but the method was being called because it hit the pry.
heres the method code
def set_user_by_token(mapping=nil)
# determine target authentication class
rc = resource_class(mapping)
# no default user defined
return unless rc
# gets the headers names, which was set in the initialize file
uid_name = DeviseTokenAuth.headers_names[:'uid']
access_token_name = DeviseTokenAuth.headers_names[:'access-token']
client_name = DeviseTokenAuth.headers_names[:'client']
# parse header for values necessary for authentication
uid = request.headers[uid_name] || params[uid_name]
#token ||= request.headers[access_token_name] || params[access_token_name]
#client_id ||= request.headers[client_name] || params[client_name]
# client_id isn't required, set to 'default' if absent
#client_id ||= 'default'
# check for an existing user, authenticated via warden/devise, if enabled
if DeviseTokenAuth.enable_standard_devise_support
binding.pry
devise_warden_user = warden.user(rc.to_s.underscore.to_sym)
if devise_warden_user && devise_warden_user.tokens[#client_id].nil?
#used_auth_by_token = false
#resource = devise_warden_user
# REVIEW: The following line _should_ be safe to remove;
# the generated token does not get used anywhere.
# #resource.create_new_auth_token
end
end
# user has already been found and authenticated
return #resource if #resource && #resource.is_a?(rc)
# ensure we clear the client_id
if !#token
#client_id = nil
return
end
return false unless #token
# mitigate timing attacks by finding by uid instead of auth token
user = uid && rc.find_by(uid: uid)
if user && user.valid_token?(#token, #client_id)
# sign_in with bypass: true will be deprecated in the next version of Devise
if self.respond_to?(:bypass_sign_in) && DeviseTokenAuth.bypass_sign_in
bypass_sign_in(user, scope: :user)
else
sign_in(:user, user, store: false, event: :fetch, bypass: DeviseTokenAuth.bypass_sign_in)
end
return #resource = user
else
# zero all values previously set values
#client_id = nil
return #resource = nil
end
end
here at the very end its hitting the else and returning resource as nil since no other conditions were met.
Any help would be really appreciated, I'm pretty sure this is where the problems is because I've been debugging for days and this is where it lead me
if youre here because you had the same issue i did hopefully i can help! after hours of grueling debugging and testing I overcame, it might be a workaround or crappy way to do it but it works while still using the devise method.
in your passwords_controller.rb there should be a "before_action :set_user_by_token, only: => [:update]"
change ":set_user_by_token" to what ever you want to name this new method we're going to make, then copy and paste the method in the original post and make some minor changes.
change these lines:
uid = request.headers[uid_name] || params[uid_name]
#token ||= request.headers[access_token_name] || params[access_token_name]
#client_id ||= request.headers[client_name] || params[client_name]
to
uid = params[uid_name]
#token ||= params[access_token_name]
#client_id ||= params[client_name]
and done. now you dont have to mess with any initializers or concers!
hopefully I helped somebody!
We have a customer that wants to use their current Wordpress site at the "source" for their user table.
(If it makes a difference, the rails app will be the primary app interface for a web front end as well as an iOS and Android front ends.)
So, the user will login through the Website and the idea is that an API call would be made to Wordpress with the email/pwd. It would return an authentication successful. I would then issue a token or something like this to the Mobile platforms to allow them continued access.
Any thoughts on how to make the authentication piece work between rails -> wordpress?
In case anyone else wants to accomplish the same thing. Here is how I solved the problem. First, my wordpress instance and rails instances are sitting on the same box, which makes this solution viable.
1) I am using devise for authentication on the rails side. I have created an override for the "authenticate!" method, which checks wordpress.
require 'devise/strategies/authenticatable'
module Devise
module Strategies
class DeviseOverride < Authenticatable
def valid?
true
end
def authenticate!
if params[:user]
user = User.find_by_email(params[:user][:email])
# user = User.first
if user # && user.encrypted_password == params[:user][:password]
#check password with Wordpress to verify it is a good user
result = WordpressApi.verify_user(params[:user][:email], params[:user][:password])
if result
success!(user)
else
fail!("Couldn't verify your login. Please try again.")
end
else
fail!("Could not log in")
end
else
fail!("")
end
end
end
end
end
Warden::Strategies.add(:local_override, Devise::Strategies::DeviseOverride)
2) This calls a simple method where I just call over to the wordpress instance to verify the user exists. (I was trying to find a way check the DB table directly, but the WP password hashing isn't something I wanted to tackle)
3) On the wordpress side (along with some other stuff):
$user = get_user_by('email', $email);
// print $user->data->user_email;
if ($user && wp_check_password( $pwd, $user->data->user_pass, $user->ID) )
return_json_success('Valid User', 'user', $user);
else{
return_json_error('Passwords do not match', 200);
// print "Password: {$pwd}, User: {$user} UserPass: {$user->data->user_pass} UserID: {$user->ID}";
// print 'Passwords do not match';
}
Update: part of the problem might have been solved by accessing the data like
first_name = graphdata[:first_name]
instead of like
first_name = userdata.first name
but when I go to save it, I'm now getting this error
undefined method `save!' for #<Class:0x00000102dbe068>
Original problem
I'm sure there's probably multiple problems with this code.
I'm playing around with a clone of a Rails app https://github.com/banane/sample-koala-rails-app that uses Koala to authenticate with Facebook and get user data. It does this with the callback method in the home_controller.rb below.
I'm trying to save the authentication token and the user data to a User model that I created.
In the callback method below, I inserted this code
user = User.oath(session[:access_token], #user_info) ### my code/ likely wrong
session[:user_id] = user.id ### my code/ likely wrong
to try to save the data to the User model, using the 'oath' class method I made on the User model (see below). Note,I'm not even sure if I can pass that instance variable #user_info into the method...
When I test it, the error I get is
undefined method `name' for #<Hash:0x00000103457d70>
and the trace says
app/models/user.rb:8:in `oath'
app/controllers/home_controller.rb:30:in `callback'
Here's the code
User.rb
def self.oath(access_token, userdata) #my code/ likely wrong
name = userdata.name
first_name = userdata.first_name
last_name = userdata.last_name
username = userdata.username
gender = userdata.gender
email = userdata.email
access_token = access_token
save!
end
Home_controller.rb (towards the end of the callback method I try to save the user data)
def callback
if params[:code]
# acknowledge code and get access token from FB
session[:access_token] = session[:oauth].get_access_token(params[:code])
end
# auth established, now do a graph call:
#api = Koala::Facebook::API.new(session[:access_token])
begin
#user_info = #api.get_object("me")
#graph_data = #api.get_object("/me/statuses", "fields"=>"message")
rescue Exception=>ex
puts ex.message
end
user = User.oath(session[:access_token], #user_info) #my code/likely wrong
session[:user_id] = user.id
respond_to do |format|
format.html { }
end
end
The #user_info has all this data in it
{"id"=>"8331884858", "name"=>"Michael MYLASTNAME", "first_name"=>"Michael", "last_name"=>"MYLASTNAMe", "link"=>"http://www.facebook.com/myusername", "username"=>"myusername", "gender"=>"male", "email"=>"myemail", "timezone"=>5, "locale"=>"en_US", "verified"=>true, "updated_time"=>"2012-07-29T06:52:12+0000"}
It sounds like you may be just accessing the hash incorrectly, try this:
def self.oath(access_token, userdata)
create! userdata.merge({:access_token => access_token})
end
I am building a little application in Rails and what I am trying to do now is authenticate a user.
So I got this method in the controller class:
def login
if #user = User.authenticate(params[:txt_login], params[:txt_password])
session[:current_user_id] = #user.id
redirect_to root_url
end
end
Here is the definition of authenticate method (inside the User model class):
def self.authenticate(username, password)
#user = User.where(["username = ? AND password = ?", username, password])
return #user
end
The problem is that I get an error message saying:
undefined method `id' for #<ActiveRecord::Relation:0x92dff10>
I confirm that the user I was trying to log in really exists in the database (besides it tries to get the id of a user and this instruction is wrapped inside an if in case 0 users are returned from the authenticate method).
Why am I obtaining this error message? Knowing that when I change the User.where by User.find it works fine!
Thank you!
User.where("some_conditions") will return an array of User objects ( in simple terms ) , A User.find can return an array or a single object.( I am not sure because i don't see how you are using it )
As far what you see is ActiveRecord::Relation, this is what is returned when we call a find or a where or a order method on Rails 3 Models.
Also, You are storing password as a plain string which is a bad idea, you should use some available rails authentication plugins like Devise or Authlogic.