I've been battling this for about 24 hours now, and nothing I'm finding in my searches is leading to a solution.
My issue is my session data is not persisting and I can not log in to my app. Everything worked in Dev mode, but has not yet worked in Production. I'm using a Rails 6 Api hosted on Heroku and a React front end. I can successfully make the api call, find the user, and log them in using (I use "puts" to help me log the session at that instance. The session hash has a session_id and user_id at this point):
def login!
session[:user_id] = #user.id
puts "login_session: #{session.to_hash}"
end
After this the app redirects to the user page or an admin page depending on the users authorization.
When the redirect happens that the user or admin page calls the api to see if the user is authorized using:
def logged_in?
puts "logged_in_session: #{session.to_hash}"
!!session[:user_id]
end
The session is empty. Here is my sessions controller:
class SessionsController < ApplicationController
def create
#user = User.find_by(email: session_params[:email])
puts #user.inspect
if #user && #user.authenticate(session_params[:password])
login!
render json: {
logged_in: true,
user: UserSerializer.new(#user)
}
else
render json: {
status: 401,
errors: ['no such user', 'verify credentials and try again or signup']
}
end
end
def is_logged_in?
if logged_in? && current_user
render json: {
logged_in: true,
user: UserSerializer.new(current_user)
}
else
render json: {
logged_in: false,
message: 'no such user or you need to login'
}
end
end
def is_authorized_user?
user = User.find(params[:user_id][:id])
if user == current_user
render json: {
authorized: true
}
else
render json:{
authorized: false
}
end
end
def destroy
logout!
render json: {
status: 200,
logged_out: true
}
end
def omniauth
#user = User.from_omniauth(auth)
#user.save
login!
render json: UserSerializer.new(#user)
end
private
def session_params
params.require(:user).permit(:username, :email, :password)
end
def auth
request.env['omniauth.auth']
end
Would any be able to point me the right direction??
Thank you
I would verify the following:
When first authenticated, does the response from the endpoint include the cookie data?
Check the cookie store in your browser (there's a few extensions you can use to make this easier) and verify that the domain names match and the content in the cookie is what you'd expect.
You can cross reference the cookie ID with the ID in your session store (depending on where you've chosen to store this).
Can you verify the cookie contents (user_id) and session contents in the session store.
Make sure that the cookie data is being sent on the next request after authenticating (check the request headers in the network tab of your dev tools in the browser).
This is all assuming that you're using a browser to talk to this JSON endpoint. APIs usually don't use cookies as it's a browser thing. Alternative authentication mechanisms might be a short lived token (JWT for example) that is generated when authenticating that can be used for subsequent requests.
Quick update: I am able to get the "Set-Cookie: _session_id=..." in the response but it is blocked to due to "SameSite=lax" attribute.
I believe I need to change to SameSite = none, but I'm not sure were to do that.
Any advice?
A bit late but if you're using Rails 6 API, session has been disabled. You need to add the middleware manually. Here is the documentation using-session-middlewares
# This also configures session_options for use below
config.session_store :cookie_store, key: '_interslice_session'
# Required for all session management (regardless of session_store)
config.middleware.use ActionDispatch::Cookies
config.middleware.use config.session_store, config.session_options
Related
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.
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
I have decided to deal with sessions in my application on a cookie level, so I have a session controller that looks like:
module Xaaron
class SessionsController < ApplicationController
def new
end
def create
user = Xaaron::User.authenticate_user(params[:user_name], params[:password])
if sign_in(user)
if params[:remember_me]
cookies.permanent[:auth_token] = user.auth_token
else
cookies[:auth_token] = user.auth_token
end
flash[:notice] = "Welcome back, #{user.first_name}."
redirect_to root_path
else
flash[:alert] = "You have entered incorrect credentials."
redirect_to login_path
end
end
def destroy
cookies.delete(:auth_token)
redirect_to root_path
end
end
end
My application is kind of a "gate keeper" application so the user can login into say site.com and from there go to product1.site.com, their information such as user name, api key, all that jazz is shared between these two apps via promiscuous gem.
How ever my question is:
is the cookie created in site.com viable for use in product1.site.com thus allowing me to use specific helper methods such as: current_user in product1.site.com to see if said user is logged in?
The purpose for this is that if user A is not signed into site.com they cannot access product1.site.com
RFC 6265 has the answer in section 4.1.2.3. If the cookie domain attribute is set to dom.ain, then the cookie is sent by the user agent when making requests to dom.ain, www.dom.ain, sub.dom.ain, and other subdomains of dom.ain. You can control the cookie domain attribute via the domain key in the cookies hash, like this
cookies.signed[:secure_session] = {domain: 'dom.ain', value: "#{user.salt}#{user.id}"}
I made Devise authentication to log out via GET, but couldn't make it log out using this Angular.js code:
$scope.logout = ->
$http.get('/users/sign_out').success ->
#If it does not redirect from 'editor' to 'login' then you haven't actually logged out
$location.path('editor')
Devise's logout behaviour seems to be random - sometimes it logs out, sometimes not.
And if I enter /users/sign_out into browser's address bar, it logs out always.
Ok, I switched the Devise authentication's log out to POST request to get rid of caching problems and used following Angular.js code:
$scope.logout = ->
$http.post('/users/sign_out').success ->
$location.path('editor')
The first time it logged out fine, as always, but then I couldn't make it to log out.
I decided to make my own method to see what happens:
match '/logout' => 'api#logout', :via => :post
class ApiController < ApplicationController
before_filter :authenticate_user!
def logout
sign_out
if current_user
puts 'Has not signed out!'
else
puts 'Has signed out!'
end
head :ok
end
end
and detected that after sign_out the current_user is always nil, but then the Angular application by some miracle manages to access other methods of ApiController, and current_user isn't nil there!
I do not understand that. Ok, let us suppose that there may follow some other HTTP request, right after (or at the same time as) logout request, passing the authentication cookie and Devise re-logins, but shouldn't the session ID passed in cookie be expired immediately after call of sign_out method?!
sorry I never responded earlier, hope this helps
My Sesisons Controller
$scope.signOutUser = function () {
$http.delete('/api/users/sign_out', {
auth_token: Session.currentUser // just a cookie storing my token from devise token authentication.
}).success( function(result) {
$cookieStore.remove('_pf_session');
$cookieStore.remove('_pf_name');
$cookieStore.remove('_pf_email');
location.reload(true); // I need to refresh the page to update cookies
}).error( function(result) {
console.log(result);
});
}
My Devise Sessions Controller I overrode
class SessionsController < Devise::SessionsController
before_filter :authenticate_user!, only: :destroy
def destroy
token = params[:auth_token]
#user = User.find_by_authentication_token(token)
#user.reset_authentication_token!
sign_out(#user)
render status: :ok, json: {message: "You have successfully logged out"}
end
end
As you can see, I'm not using Rails cookies and thus my answer may not pertain. If I did I would probably add a line like session[:user] = nil in my destroy action.
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..