Session not maintained on post request, angular2, rails5 - ruby-on-rails

I'm trying to get my head around the difference of how sessions are handled between GET and POST request.
In rails I'm setting a current_user with a session variable. This works fine for all get requests BUT when I do a POST it seems like the session variable is not carried over. This results in current_user = null
I guess these pictures explains it well.
Cookies on a working GET request - Working get request
Cookies on a NOT working POST request - enter image description here
Why is that?
Do I have to change the header in the angular2 request?
Is it a setting in rails to allow sessions with POST requests.
Here is some of my code...
Angular: Version 1 - Doesn't set my current_user
postSomeData( id : number ){
return this._http.post( "/api/something/" + id,
JSON.stringify("{id: id}") )
.map( response => response.json() )
}
Angular: Version 2 - Doesn't set my current_user
postSomeData( id : number ){
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this._http.post(
"/api/lists/private/translation/" + id,
JSON.stringify("{id: id}"),
{ headers: headers, withCredentials: true } )
.map( response => response.json() )
}
Rails: ApplicationController
class ApplicationController < ActionController::Base
protect_from_forgery with: :null_session
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
Rails 5.0.0.1
Angular 4.0.5

People usually do something like this in the ApplicationController or in a helper:
def set_user
unless #current_user.present?
#current_user = User.where(id: session[:user_id]).take || User.new
end
end
Then you can query if the user has any relations or has an ID, etc...

I faced the very same situation. Turned out that the problem is with the rails server, not angular.
When I make http post request the following statement shows in the logs and my session gets terminated.
WARNING: Can't verify CSRF token authenticity
I searched for this and the discussion on this thread did help me.
Adding the following line in my controller allowed me to make http post reqests.
skip_before_filter :verify_authenticity_token

Related

How to redirect user to sign in page if not logged in

I am trying to redirect the user to sign-in page if the user tries to access MyAccountController. Now the issue is that the sign-in route is defined in router.js and I am not able to figure-out a way to access vue routes in rails controller.
class MyAccountController < ApplicationController
before_action :authenticate_user!
before_action :require_user
private
def require_user
head(401) unless user_signed_in?
end
def authenticate_user
if user_signed_in?
super
else
redirect_to 'sign-in'
end
end
end
router.js
const SessionsVue = () => import('views/sessions/new.vue')
const routes = [
{ 'path': '/sign-in', component: SessionsVue, meta: { requiresAuth: true } }
]
You cannot access Vue routes in Rails controller. Vue code, which is just JavaScript code running in your browser, communicates to your Rails controller via HTTP requests and responses.
From here, it’s totally up to you how you choose to communicate somehow from a controller to the JS running in the browser.
You don’t have many options:
Response status
Response headers
Response body
Response statuses correspond to predefined messages. There’s a status that suits you perfectly: 401 Unauthorized. You could
head :unauthorized
from an action and check response status in Vue somehow.
Response headers and body don’t differ much: headers always transmit a table of keys/values, for the body you can pass any string, but most often for Vue you would choose a string with JSON, which is a table of keys/values as well.
You could pass a
{ errors: ["Unauthorized"], ok: false }
in the body and check those values in Vue somehow.

Logout function not working React-Native to Ruby on Rails? I can't delete the session

I am having trouble with my authentication actions from react-native to my ruby on rails backend api. I thought my signup/ sign in actions were working fine until I made my logout action and I began receiving a rails controller error. My logout function, requests a delete of user's session and can't find it, but my login/signup functions have no problem creating a session.
My Sessions#Controller:
class SessionsController < ApplicationController
def create
#user = User.find_by_credentials(
params[:user][:username],
params[:user][:password]
)
if #user
login(#user)
render "users/show.json.jbuilder"
else
render json: ["Invalid username/password combination"], status: 401
end
end
def destroy
#user = current_user # I believe the problem is here, as it can't find current_user see
# application controller below
if #user
logout
render json: {}
else
render json: ["Nobody signed in"], status: 404
end
end
end
Application#controller , where I find the current user based on the session_token
class ApplicationController < ActionController::Base
skip_before_action :verify_authenticity_token
helper_method :current_user, :logged_in?, :logout
# private
def current_user
return nil unless session[:session_token]
#current_user ||= User.find_by(session_token: session[:session_token])
end
def logged_in?
!!current_user
end
def login(user)
user.reset_session_token!
session[:session_token] = user.session_token
#current_user = user
end
def logout
current_user.reset_session_token!
session[:session_token] = nil
#current_user = nil
end
end
My logout action:
export const testLogout = () => dispatch => {
dispatch(loadSpinner());
axios({
method: 'delete',
url: 'http://10.0.2.2:3000/session',
})
.then((response) => {
console.log("response: ", response);
if(reponse.status === 200) {
{dispatch(logoutCurrentUser(response))}
} else {
console.log("status", response.status);
};
});
};
My routes:
Rails.application.routes.draw do
resources :users, only: [:create, :show]
resource :session, only: [:create, :destroy, :show]
end
The errors (1):
Started DELETE "/session" for 127.0.0.1 at 2020-05-27 10:23:23 -0700
Processing by SessionsController#destroy as HTML
Completed 404 Not Found in 0ms (Views: 0.2ms | ActiveRecord: 0.0ms).
(2):
Possible Unhandled Promise Rejection (id: 0):
Error: Request failed with status code 404
Error 2 from the testLogout() function never even makes it to the .then(response) in the function.
Now this 404 error is my own from the #destroy method in my sessions_controller and I am a bit unsure how to get a better error here but from what I am reading online is that react-native handles browser cookies differently and I am concerned I am missing something conceptually but I am very unsure of what it is. I know I could use AsyncStorage for persisting sessions but how does that factor in with the back-end rails authentication? Any help figuring out how to log out a user is greatly appreciated thank you!
I highly recommend using some form of a JWT for authentication handling. Using sessions with an external client is certainly possible, but it's a headache you probably don't want to deal with. Read about CORS. In short, you don't want to deal with CORS if you don't have to.
This seems like a fairly decent guide if you're using devise: https://medium.com/#eth3rnit3/crud-react-native-ror-backend-with-devise-auth-token-4407cac3aa0b

Force Omniauth to use json for callback?

I'm attempting to integrate Omniauth into an API written in rails, to be used by an Android application. This means that I want to be able to handle the omniauth callback with JSON.
By default, Omniauth sends its callbacks to /auth/:provider/callback, is there a way that I can force Omniauth to instead send its callbacks to /auth/:provider/callback.json?
You can specify format in action where handling callback:
# in route.rb
match '/auth/:provider/callback' => 'authentications#create'
# in authentications_controller.rb
class AuthenticationsController < ApplicationController
def create
# your code here
respond_to do |format|
format.json { ... } # content to return
end
end
end
I managed to do that by inspecting the request object on my rails backend.
When I make the request on my app, I add data on the submition defining the format:
format: "json"
And the omniauth then makes the callback for
/auth/:provider/callback
Wich in my case matches
sessions#create
as an HTML request. But once there, if you look at your request object in rails, and search for the omniauth.params hash you'll see that one of the values there is the format passed on tha data of the initial request:
"omniauth.params"=>{"format"=>"json", "provider"=>"facebook", "code"=>"..."}
Its a mather of you searching for this "format"=>"json" and doing a render json as an answear.
I hope it solves your problem.
# app/controllers/users_controller.rb
def authenticate
#credentials = request.env['omniauth.auth']
render json: #credentials
end
# config/routes.rb
get '/auth/:provider/callback', to: 'users#authenticate', as: 'user_auth'
And then all requests made to /auth/:provider/callback will return a JSON response by default.

Cannot make Devise to log out from Angular.js

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.

How can you use Rails AuthenticityToken infrastructure to explicitly protect a GET action

Rails AuthenticityToken automatically protects POST/PUT/DELETE requests from CSRF attacks. But I have another use case in mind.
I am showing a video on my site that I don't want to be embeddable on other sites. How this works is that my flash player sends a request for a signed URL from my CDN that expires in a few seconds. Up until now a user had to be logged in to watch videos, so that was the authentication. However now I want any visitor to the site to be able to watch the video without allowing the signed URL to be requested from another site (such as if they embedded our player on their site).
My first thought went to AuthenticityToken since it seems to have these exact semantics... all I need to do is plug it into a GET request. Any ideas?
Rails, opinionated as it is believes that all GET requests should be idempotent. This means Rails of course does not check authenticity tokens for GET requests, even verified_request? gives every GET a pass.
def verified_request?
!protect_against_forgery? ||
request.method == :get ||
!verifiable_request_format? ||
form_authenticity_token == params[request_forgery_protection_token]
end
So we have to write our own logic. We can use form_authenticity token. All this does is create a random string and cache it in the session:
def form_authenticity_token
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
end
We can therefore make a before filter that tests the equality of a url parameter to the session token. Thereby ensuring that only bonafide visitors can view videos.
Controller:
class CDNController < ActionController::Base
# You probably only want to verify the show action
before_filter :verify_request, :only => 'show'
# Regular controller actions…
protected
def verify_request
# Correct HTTP response code is 403 forbidden, not 404 not found.
render(:status => 403) unless form_authenticity_token == params[:token]
end
end
The view:
<%= video_path(:token => form_authenticity_token) %>
To plug the authenticity token in your url:
<%= video_path(:token => form_authenticity_token) %>
In your CDN's controller, you could check if the authenticity token is correct with a before_filter:
def verify_token
render_404 unless form_authenticity_token == params[:token]
end
def render_404
render :file => "#{RAILS_ROOT}/public/404.html", :status => 404
end

Resources