Rails Devise 401 from Safari Extension - ruby-on-rails

I'm trying to make a simple $.ajax request to a Rails app running locally.
The request works without any issues from the console but when I make the call from the safari extension the app returns a 401 Unauthorized. I'm not sure if I need to create an api token for each user and pass that in the url string to authenticate or if there's a simple reason why devise is not processing the request even though I'm logged in. My guess is that the culprit is the before filter I have on my controller which looks like this:
before_filter: authenticate_user!
But again, I get the 401 when I am signed in to the app. Just for reference, here's the call I'm making from the extension:
$.ajax({
type: 'GET',
url: 'http://localhost:3000/playlists.json?callback=?,
dataType: 'jsonp',
success: function(data) { console.log(data); },
error: function() { console.log('Uh Oh!'); },
jsonp: 'jsonp',
crossdomain: true
});
Any help would be much appreciated.

Ok figured it out. The cookie being set was set to expire after 'session' which basically means that as soon as you navigate away from the page the session is no longer there. Thus the extension did not have access to this extension cookie. The key was to set an expiration date by default on the session cookie which you can do like this in config/initializers/session_store.rb:
YourApp::Application.config.session_store :cookie_store, {
   :key => '_yourapp_session',
   :expire_after => 60*24*60*60
}
You can read more about cookie types here: http://en.wikipedia.org/wiki/HTTP_cookie#Session_cookie

Related

How are cookie-http-only sessions supposed to work on a SPA with a separate API server?

When trying to figure out how to authenticate with Facebook/Google in the context of an SPA I'm building, someone pointed me to Stop using JWT for sessions.
I'm trying to give it a try, using HTTP-Only Cookies. My server is Ruby on Rails with Devise and my client is JavaScript with React, although the conceptual solution is independent of specific tech I believe.
My app gets loaded by going to projectx.lvh.me and then it makes a query to api.projectx.lvh.me to fetch the current user. At the beginning it's null because the user is not logged in. When a call request is made to sign in, the response from api.projectx.lvh.me contains the session cookie, hurra! But the next request that projectx.lvh.me makes to api.projectx.lvh.me doesn't carry the cookie, so, it seems the cookie is forever lost. Even opening api.projectx.lvh.me on another tab doesn't show the cookie. Is this supposed to work? What am I missing?
I thought this was blocked by third-party cookie blocking and that's why we can't use cookies in this scenario and we have to use jwt tokens (stored on a cookie, local storage or session storage).
I managed to get cookies working in this scenario by adding config/initializers/session_store.rb to my Rails app containing:
Rails.application.config.session_store :cookie_store, key: 'session', domain: :all
which caused the session cookie to not be for api.projectx.lvh.me but for .projectx.lvh.me.
On the frontend, the API calls needed to include withCredentials, which with Axios it was the withCredentials option set to true:
Axios.post(`${apiEndPoint()}/users`, { user: values }, { withCredentials: true })
and with fetch it was the credentials option set to "include":
fetch(`${apiEndPoint()}/graphql`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: "include",
body: JSON.stringify({
query: operation.text,
variables,
}),
})

devise not set flash messages when dealing with ajax request

When user wants to perform an action that need him to login in first, devise will redirect him to the signin page and set a flash message: 'Please signin or signup before continue'. This works well with no-ajax request. But with an ajax request that also need signin first, it will response with 401 error and we should handle it manually as follows:
$.ajax({
url: '/books/user_score',
type: 'POST',
data: { score: value, book_id: book_id },
dataType: 'json',
success: function(data) {
bookStarWidget.data('starInfo', data.book_star);
setVote(bookStarWidget);
$("[name='my_vote']").rating('disable');
},
error: function(jqXHR, textStatus, errorThrown) {
if (jqXHR.status == 401) {
window.location.assign('/users/sign_in');
}
}
});
In the above code, we manually open the signin page when received the 401 error devise sends out.
The problem is that there is NO flash message set in this way. So could someone knows how to set the flash message as usual to keep a consistent behavior?
It looks like you're using javascript to set the location in the error case. Devise has a configuration option that will redirect on failure instead of returning http headers. This setting defaults to true which forces you to handle failure on xhr manually. Here's the explanation from the source: https://github.com/plataformatec/devise/blob/master/lib/devise/failure_app.rb#L120
So, in your Devise config:
config.http_authenticatable_on_xhr = false
Try the unauthenticated ajax request again and Devise will do the redirect for you, which should set up the flash messages as you expect.

Phonegap App communicate with Rails 3 App using authentication

I am trying to write an iOS app using Phonegap to communicate with my Rails3 app. I cannot figure out how to handle authentication. I am using Devise/Warden in my Rails app.
I am able to login successfully in my iOS app via ajax but then subsequent calls are giving me a "Unauthorized". It appears that the session cookie isn't getting set in my iOS app.
How do I keep my iOS app aware of my rails authenticated session?
The answer was two fold.
First I had to add this to my ajax requests in the iOS app:
xhrFields: {
withCredentials: true
}
as in...
$.ajax({
url: ....,
type: "GET",
xhrFields: {
withCredentials: true
},
complete: hideLoader,
error: function(xhr,txt,err) {
// you can't get back anyways
window.location.hash = "";
},
success: function(data,status,res) {
window.location.hash = "";
}
});
Second I had to add this to my Application Controller in the Rails app:
def protect_against_forgery?
unless request.format.json?
super
end
end

Setting the cookies for forge.request.ajax in Trigger.io

I'm trying to set an extra cookie in the forge.request.ajax call. Is there a way to dot this?
You could use the headers argument to request.ajax manually if you wanted, e.g.
window.forge.ajax({
type: 'GET',
url: 'http://my.server.com/protected/,
headers: {
'COOKIE': 'csrftoken=47ac86bb7965b343e8ca21343b164ef3',
},
success: function (data) { },
error: function (error) { alert(JSON.stringify(error)); }
});
Making requests through forge.request.ajax actually uses a whole different context to your JavaScript - this is to get around the cross-domain restrictions that can be imposed on normal JavaScript.
We don't currently allow for manually settings cookies in the forge.request context, but note that any cookies which are set as part of a request will be persisted (as applicable) to subsequent requests, so we weren't expecting people to have to set their values by hand...

How do you pass devise authentication details to a rails controller action?

I have a controller action that is expected to be called by jquery. Here's the call:
$.ajax({
url: "/comments.json",
type: "POST",
dataType: "json",
data: {
commentable_id: $("#addCommentForm").attr("data-site-update-id"),
commentable_type: 'SiteUpdate',
content: $("#addCommentForm textarea#content").val()
},
success: function(comment) {
}
});
It works just fine, but for some reason, "current_user" is nil inside of the controller. If I force authenticate_user! as a filter, rails returns an HTTP Unauthorized.
How can I pass the authentication details so that this controller action works with devise? Even better, is there a way I can make this transparent? I don't want to pass the authentication details over and over for each ajax request...
In Java, once a user is logged in... they are logged in. You don't have pass anything from url to url anymore - it's in a session somewhere and it's all occuring transparently. Is there a way I can do this with rails? I don't want to focus on these details.
Thanks
EDIT: The solution is to add the following to your javascript somewhere:
$(document).ajaxSend(function(e, xhr, options) {
var token = $("meta[name='csrf-token']").attr("content");
xhr.setRequestHeader("X-CSRF-Token", token);
});
Devise does maintain sessions.
This may be your problem.

Resources