Use API to authenticate in Rails - ruby-on-rails

I currently have a Rails application that is connected to an existing SQL database. I am using Devise for my user management, however the pre-existing User table in the database uses a very customized password encryption method.
There is a web service I can connect to that passes a JSON object with the login information to authenticate whether it is valid or not, and I have to manage my own session and everything after that.
I attempted to follow "Railscast #250", and combine it with Devise and some Stack Overflow searches, but things are not going very well.
This is what I have now, but it isn't doing anything, and I just don't feel like I am on the right track with this.
class SessionsController < Devise::SessionsController
def new
super
end
def create
post_params = {
"RuntimeEnvironment" => 1,
"Email" => params[:session][:email],
"Password" => params[:session][:password]
}.to_json
user_params = RestClient.post 'http://some.ip/WebServices', post_params, :content_type => "json"
user = User.authenticate(user_params)
if user
session[:user_id] = user.user_id
redirect_to root_path
else
flash.now.alert = "Invalid Username or Password"
render "new"
end
end
end
This is the JSON Object returned if there is a successful login:
{"Success":true,"ErrorMessage":"","ResponseString":"","LoginResultData":{"FailMessage":"","ResultCode":0,"User":{"AccountCompleteFlag":1,"CreationDtime":"\/Date(1430848539000-0400)\/","DeleteFlag":0,"Email":"john#doe.com","FailedPasswordCount":1,"HistoricalFlag":0,"IsDirty":false,"IsAdminFlag":0,"IsSiteAdminFlag":0,"LastLoginDtime":"\/Date(1447789258000-0500)\/","NameFirst":"Ttest","NameLast":"test","Password":"TRQt3d2Z7caDsSKL0ARVRd8nInks+pIyTSqp3BLxUgg=","PasswordLockDtime":"\/Date(-62135578800000-0500)\/","PasswordLockFlag":0,"PasswordResetCode":"","PasswordResetStatus":0,"Phone":"1-X-5555555555-","RegistrationSource":"Registration","UserId":100029,"UserType":1,"PhoneInfo":{"AreaCode":"555","CountryCode":"X","Extension":"","FirstThree":"555","InternationalPhoneNumber":"","IsDirty":false,"IsInternational":false,"LastFour":"5555"}}}}
And what is returned for a failed one:
{"Success":true,"ErrorMessage":"","ResponseString":"","LoginResultData":{"FailMessage":"Invalid email address","ResultCode":1,"User":null}}
Is there a way where I can use Devise's session management while connecting to the API?

You can still authenticate through Devise using the email and password that the user provided. The RestClient would just be like a double check: just make sure that there are no routes that the user can authenticate through besides going through the RestClient. You can check this by doing rake routes.
For checking whether the result code was valid, you can do some JSON parsing as follows:
authentication_response = RestClient.post 'http://some.ip/WebServices', post_params, :content_type => "json"
json_authentication_response = JSON.parse(authentication_response)
result_code = json_authentication_response["LoginResultData"]["ResultCode"]
if result_code == 0
# Authenticate
else
# Don't authenticate
end

Related

Rails devise signup with JWE token payload

I need to create functionality where other microservice creates a link to my app with JWE token as a params in which is encrypted json user params e.g.:
json_payload = {
email: 'test#test.com',
external_id: '1234'
}.to_json
The flow should be:
user gets the url generated by different app with JWE token as params (e.g. http://localhost:3000/users/sign_up/?jwe_token=some_gigantic_string_123)
enter that url
under the hood Rails app creates new user based on encrypted params
after successful user creation redirect that user to the edit page
So as you see, the user shouldn't notice that there was an account creation but the first page it will see is the password edit.
Am I not doing some sort of antipaternity here with below code? Please take a look:
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
return redirect_to(new_user_session_path) unless params[:jwe_token]
json_payload = JWE.encrypt(payload, rsa_key)
payload = JSON.parse json_payload
user = User.new(user_params)
if user.save
redirect_to generate_password_url(request.base_url, user)
else
redirect_to new_user_session_path, alert: 'Something went wrong'
end
end
private
def generate_password_url(base_url, user)
path = edit_password_path(user, reset_password_token: fetch_token(user))
"#{base_url}#{path}"
end
def fetch_token(user)
user.send(:set_reset_password_token)
end
end
I assume that if user creation is to be handled by a link I have to use new method. Am I not creating an antipattern here? Is there any other way to do so?

Unauthorized request using devise authentication methods as API for resetting password

I am working on a project that is divided in two apps :
- one rails JSON API that is dealing with the database and is rendering data as JSON
- one "front-end" rails app that is sending requests to the API whenever it needs and displaying the json data in a nice way.
Authentification for the API is token based using gem'simple_token_authentication' meaning that for most of the requests that are sent to the API you have to send the user token & his email in the header for the request to be authorized.
The one who worked on the project before me had also installed Devise authentification system on the API side to allow direct access to the API methods from the navigator after successfull login with email & password.
I just started coding on the "front-end app" that is supposed to request the API and I am having trouble especially with the authentification system.
As Devise was already installed on the API, I thought it would be a good idea to make the user login on the front-end app which would then request devise's methods present on the API for creating user, auth, reseting password...
The problem is that devise's methods are rendering html and not JSON so I actually had to override most of devise's controller. To give you a quick idea of how it works :
You fill the sign up form on the front-end app then the params are sent to the front-end app controller that is then requesting devise's register user method on the API :
1) front-end app controller :
def create
# Post on API to create USER
#response = HTTParty.post(ENV['API_ADDRESS']+'users',
:body => { :password => params[:user][:password],
:password_confirmation => params[:user][:password_confirmation],
:email => params[:user][:email]
}.to_json,
:headers => { 'Content-Type' => 'application/json' })
# si le User est bien crée je récupère son email et son token, je les store en session et je redirige vers Account#new
if user_id = #response["id"]
session[:user_email] = #response["email"]
session[:user_token] = #response["authentication_token"]
redirect_to new_account_path
else
puts #response
#errors = #response["errors"]
puts #errors
render :new
end
end
2) API overrided devise controller :
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
#user = User.new(user_params)
if #user.save
render :json => #user
else
render_error
end
end
def update
super
end
private
def user_params
params.require(:registration).permit(:password, :email)
end
def render_error
render json: { errors: #user.errors.full_messages }, status: :unprocessable_entity
end
end
This works ok. Here I send back the user that was just created on the API as JSON and I store is auth token and his email in the session hash.
My problem is with the reset_password method for which I am trying to reuse some of devise code.
First, I ask for a reset of the password which generates a reset password token for the user who requested the change. This also generates an email to the user with a link (with the token inside) pointing to the reset password form for the specific user. This is working well. I am getting the link in the email then going to the edit_password form on my front-end app :
Change your password
<form action="/users/password" method='post'>
<input name="authenticity_token" value="<%= form_authenticity_token %>" type="hidden">
<%= hidden_field_tag "[user][reset_password_token]", params[:reset_password_token] %>
<%=label_tag "Password" %>
<input type="text" name="[user][password">
<%=label_tag "Password Confirmation" %>
<input type="text" name="[user][password_confirmation]">
<input type="Submit" value="change my password">
</form>
When the form is submitted it goes through my front-end app controller :
def update_password
#response = HTTParty.patch(ENV['API_ADDRESS']+'users/password',
:body => {
:user => {
:password => params[:user][:password],
:password_confirmation => params[:user][:password_confirmation],
:reset_password_token => params[:user][:reset_password_token]
}
}.to_json,
:headers => { 'Content-Type' => 'application/json' })
end
which then calls my overrided Devise::PasswordController (update method) :
# app/controllers/registrations_controller.rb
class PasswordsController < Devise::RegistrationsController
# POST /resource/password
def create
if resource_params[:email].blank?
render_error_empty_field and return
end
self.resource = resource_class.send_reset_password_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
render_success
else
render_error
end
end
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)
render_success
else
render_error
end
end
private
# TODO change just one big method render_error with different cases
def render_success
render json: { success: "You will receive an email with instructions on how to reset your password in a few minutes." }
end
def render_error
render json: { error: "Ce compte n'existe pas." }
end
def render_error_empty_field
render json: { error: "Merci d'entrer un email" }
end
end
However the request is always Unauthorized :
Started PATCH "/users/password" for ::1 at 2016-02-05 11:28:30 +0100
Processing by PasswordsController#update as HTML
Parameters: {"user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "reset_password_token"=>"[FILTERED]"}, "password"=>{"user"=>{"password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]", "reset_password_token"=>"[FILTERED]"}}}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)
I dont understand why is this last request unauthorized ?
Your predecessor likely made a mess of things on the API side just for his convenience.
We know that using cookies for API's is a really bad idea since it leaves the doors wide open for CSRF/XSRF attacks.
We can't use the Rails CSRF protection for an API because it only works as sort of guarantee that the request originated from our own server. And an API that can only be used from your own server is not very useful.
Devise by default uses a cookie based auth strategy because thats what works for web based applications and Devise is all about making auth in web based applications easy.
So what you should do is either remove Devise completely from the API app or convert Devise to use a token based strategy. You also should consider removing the sessions middleware from the API app. Also the Devise controllers are so heavily slanted towards client interaction so that trying to beat them into API controllers is going to be very messy.
Updating a password in an API is just:
class API::V1::Users::PasswordsController
before_action :authenticate_user!
def create
#user = User.find(params[:user_id])
raise AccessDenied unless #user == current_user
#user.update(password: params[:password])
respond_with(#user)
end
end
This is a very simplified example - but the point is if you strip off all the junk from the controller related to forms / flashes and redirects there is not that much you are really going to re-use.
If your front-end app is a "classical" client/server Rails app then you can use a regular cookie based auth (Devise) and let it share the database with the API app. Token based auth does not work well with classical client/server apps due to its stateless nature.
If the front end app is a SPA like Angular or Ember.js you might want to look into setting up your own OAuth provider with Doorkeeper instead.

NoMethodError in SessionsController#create - Undefined method '[]' for #<Oauth::Access_Token...>

I'm having some trouble storing a user's Twitter access token in the User model. I have installed the Omniauth gem as per Railscast #241 and was successful in setting up the Twitter authentication, storing the "uid" and "name". In order to make authenticated Twitter API calls, I wanted to store the user's access token and access token secret and thus created a migration to create those fields. I did that successfully and can assign those fields successfully in the Rails Console to records that do not have the stored. When trying to authenticate a new user, however, and pull this information in from the start, I get the error listed in the title. HEre is additional error information:
app/models/user.rb:13:in `block in create_from_omniauth'
app/models/user.rb:10:in `create_from_omniauth'
app/models/user.rb:6:in `from_omniauth'
app/controllers/sessions_controller.rb:5:in `create'
I follow down this path from the sessions controller to the User model but can't figure out what is causing the error. I have included those documents below.
Sessions Controller
class SessionsController < ApplicationController
def create
user = User.from_omniauth(env["omniauth.auth"])
session[:user_id] = user.id
redirect_to root_url, notice: "Signed in"
end
def destroy
session[:user_id] = nil
redirect_to root_url, notice: "Signed out"
end
end
User Model
class User < ActiveRecord::Base
attr_accessible :name, :uid, :access_token, :access_token_secret
has_many :events
def self.from_omniauth(auth)
where(auth.slice('uid')).first || create_from_omniauth(auth)
end
def self.create_from_omniauth(auth)
create! do |user|
user.uid = auth["uid"]
user.name = auth["info"]["nickname"]
user.access_token = auth["extra"]["access_token"]["token"]
user.access_token_secret = auth["extra"]["access_token"]["secret"]
end
end
end
Can anyone help me troubleshoot this error? I know it has to do with setting the access_token fields in the create_from_omniauth method as it works fine without them. I've been banging my head trying to figure out why these won't work. Thanks in advance for any help.
I doubt why you need to save these tokens as they looks of no use. In this case, the most important thing is the Twitter returned uid for identifying or create user.
Anyway, the reason of your error is there are not such keys.
auth['extra']['access_token'] is a string, there is no further sub keys.
:extra => {
:access_token => "", # An OAuth::AccessToken object
To access token and secret, you can use auth['credentials']
:credentials => {
:token => "a1b2c3d4...", # The OAuth 2.0 access token
:secret => "abcdef1234"
},
Reference:
https://github.com/arunagw/omniauth-twitter#authentication-hash
https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema#schema-10-and-later

Ruby on rails .authentication failing

I'm building an API for a web app I'm developing, and the following code I'm trying to use for API authentication/login is returning false on the authorization.
In my API user controller I have:
def login
if params[:user]
# Find the user by email first
#user = User.where(email: params[:user][:email]).first
if !#user
respond_with nil
else
#auth = #user.authenticate(params[:user][:password])
if #auth
respond_with #user
else
respond_with #auth
end
end
end
end
It is always responding with #auth, which is false, even when valid email and passwords are being provided. It has no problem pulling the user info from my Mongo db.
I guess I'm just not clear on what .authenticate does. According to a railscast.com video I watched, it should compare that users password digest with the password entered. When a valid password is provided for the user, #auth is always false.
This method was actually working fine, the test data in the database wasn't what i thought it was..

REST Client for Ruby Gives ActionController::InvalidAuthenticityToken

I have a RESTful Rails application with a resource called "Foo". I'm trying to use REST Client to do a put:
resource = RestClient::Resource.new 'http://localhost:3000/foos/1', :user => 'me', :password => 'secret'
resource.put :name => 'somethingwitty', :content_type => 'application/xml'
But my app raises:
ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken):
/usr/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/request_forgery_protection.rb:86:in `verify_authenticity_token'
It seems like my app isn't getting the message that this is an XML request and that the AuthenticityToken should be ignored. Maybe I'm not using REST Client correctly. Any ideas on why I'm getting the exception?
Try putting an :only => [:update, :delete, :create] on the protect_from_forgery line in your application controller.
More info: http://ryandaigle.com/articles/2007/9/24/what-s-new-in-edge-rails-better-cross-site-request-forging-prevention
Use something like:
resource.put '<foo><name>somethingwitty</name></foo>', :content_type => 'application/xml'
I think you need to make two changes;
(a) Use the rails routing to tag this as an XML request
(b) Use HTTP Basic Authentication to authenticate the request.
This means changing your URL above to include the username, password like this
me:secret#localhost:3000/foos/1.xml
also note .xml bit
I guess that somewhere on your server-side you have code that authenticates in-bound requests via a before filter. This needs to work something like this ...
#
# If you haven't authenticated already then you are either
# reqirected to the logon screen (for HTML formats) or
# the browser prompts you. You are always allowed to pass
# the username/password in the URL
#
def login_required
#current_user = valid_session?
unless #current_user
if params["format"]
#
# If you specify a format you must authenticate now
#
do_basic_authentication
else
display_logon_screen
end
end
end
#
# Ask Rails for the login and password then authenticate as if this
# were a new login.
#
def do_basic_authentication
user = authenticate_with_http_basic do |login, password|
User.authenticate(login, password)
end
if user
current_user(#current_user = user)
else
request_http_basic_authentication
end
end
That's from our own app and is triggered by a before_filter in ApplicationController.
Also, I don't think you need the :content_type => 'application/xml'. What I normally do is just call post or put directly like this ..
response = RestClient.post URI.encode(url), :record => args
where the url contains the basic authentication and the ".xml"
Happy coding
Chris
Since your application is a Rails app, it might be easier to use ActiveResource for the client.
Something like:
require 'active_resource'
class Foo < ActiveResource::Base
self.site = 'http://localhost:3000/'
end
foo = Foo.new(:name => 'somethingwitty')
foo.save
You can read up on how to do the authentication on the rdoc site.

Resources