Changing CSRF Token With AJAX - ruby-on-rails

So I have AJAX login/logout using Devise. If I logout with AJAX, the session is reset and I'm sitting on a stale CSRF token. To work around that issue, I thought I would generate a new token in my logout server-side code, pass it back to the client, and have the client set it in the proper place. So I return JSON like so:
return render :json => {:success => true,·
:user_registration_path => user_registration_path,
:csrfToken => form_authenticity_token}
which I then handle in my ajax success handler, like so:
logoutAuth: function(e, data, status, xhr) {
console.log(data);
console.log(status);
console.log(data.csrfToken);
$('.calendar').hide();
$('.sign-out-button').hide();
$('.right').append($('<li class="btn log-in-button"><a class="standout" href="#" data-reveal-id="login">Member Log in</a></li>'));
$('.right').append($('<li class="btn sign-in-button"><a class="standout-primary" href="' + data.user_registration_path +·
'" data-reveal-id="sign-up">Member Sign up</a></li>'));
// reset CSRF token with new token generated after sign out -- to allow AJAX with CSRF protection
$('meta[name="csrf-token"]').attr('content', data.csrfToken);
}
Here's the weird part: when I see what the results of calling form_authenticity_token are on the server, I get some big ass randomly generated hash. Expected. When I console.log(data.csrfToken), what that hash should have been mapped to, I get undefined. Yet other variables in my data object are accessible. Moreover, I see the token in the XHR response in my developer tools. What's up? Also, is this the preferred way of resetting an authenticity token?

Related

devise-jwt generating token before authentication?

So I am writing e2e automation tests using Cypress. In an effort to keep my tests atomic I try to generate required data for THAT specific test.
So for example I try to start out with a clean database (with migrations ran) and generate a user via the Cypress API request.
The problem is using devise-jwt I need to send along the auth: bearer token along with it. Usually this is generated on sign-in. So there-in lies the problem: To make a POST request to create a user, I need to have the token to authenticate....but without a user to sign in as and grab the token I can't know what the token is going to be.
I hate to seed the database with just one user, since I am trying to keep dependencies minimal and tests atomic as possible. Is there some way to "grab/generate" the auth token through devise and use that in my test somehow?
This is a Rails/React app fwiw.
I don't know the workflow for fetching the devise-jwt token, but if it results in a cookie, sessionStorage or localStorage value, you can preserve it with cy.session().
Cypress.config('experimentalSessionSupport', true)
beforeEach(() => {
cy.session('mySession', () => {
// this "setup" callback is called once only,
// thereafter results are returned from cache
// actions to seed database, get user data
const window = cy.state('window')
window.fetch(LOGIN_URL, dataWithLoginInfo).then(response => {
const jwt = response.headers.get('Authorization').split('Bearer ')[1];
window.sessionStorage.setItem('jwt', jwt) // this is now cached - same token each time
})
})
})
it('uses jwt token', () => {
const token = cy.state('window').sessionStorage.getItem('jwt')
const headers = { Authorization: `Bearer ${token}` }
...
})

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,
}),
})

Authentication for Node.js App with Angular.js and iOS Clients

I've tried to read as many different answers and posts as possible, but I still can't quite settle on a solution that fits my needs. I'm trying to work out the best (most efficient, but mostly more secure) way to handle user authentication, log in, etc.
I have a Node.js server, running on Express; I have an Angular.js web app; and I have an iOS app. I expose a RESTful API with Express/Node.js.
Cookies
The first things I read said to use cookies, and to store a session id/login token on the server side (hashed) and on the client side (unhashed). The client would transfer this id with each request, the server would hash it, parse it and process the request accordingly. This does not feel RESTful (not a huge issue), but more importantly, would I have to duplicate my API: one for username/password authentication (e.g. done via curl) and one for cookie-based authentication (e.g. my web app)?
Another problem with this: what I would do if I had multiple connections from the one user, e.g. they're logged in in two browsers, an iPhone and an iPad. Would my storage of their session ids need to now be an array?
HTTP Basic Auth
The next idea was to use HTTP Basic Auth (with SSL), which seems easy enough, but is not recommended because you need to transfer a username and password with each request. If I were to do it with HTTP Basic Auth, would I then store the username and password in cookies (or HTML local storage) to allow for 'Remember Me' functionality? Or could I combine the two: use HTTP Basic Auth for the actual requests (post a new post, etc.) and just use a session id stored in a cookie for the initial log in sequence/remember me aspects?
Is transmitting a session id more secure than just transmitting the user's password? How?
The session id is going to act ostensibly as a password, so to me transmitting it would have the same security issues as transmitting a password.
Basic Auth seems to be supported across all platforms, which is ideal. The main downside seems to be needing to transfer client authentication data with each request. Is there a way to mitigate this issue?
OAuth
OAuth seems like overkill for my needs. I think I would lose the ability to do curl commands to test my API. How is OAuth an improvement over the cookies method?
As you can probably tell, I'm a little confused by the diverse information available, so if you have a set of good links—applicable to this scenario—I would love to read them. I'm trying to find a solution that fits across all platforms, but is still as secure as possible. Also, if I have any of my terminology wrong, please correct me because it will make searching easier for me.
Thanks.
Update:
I've been thinking about this problem, and I've had an idea. Please tell me if this is dumb/insecure/any feedback, because I'm not sure if it's good.
When the user logs in, we generate a random session id (salted etc.). This optional session id is sent to the client, which the client can store (e.g. in cookies) if they choose; the session id is stored in the database.
This session id is then optionally sent with each request as either an HTTP Authentication header or query string, or the client can just send the username and password if they want (which gives us our regular REST API). At the server end, we check first for a session id parameter, if it's not present, we check for username/password. If neither are there—error.
On the server, we check that the session id is associated with the correct username. If it is, we complete the request.
Every time the user logs in, we create a new session id or delete the current one, and send this with the response to the log in request.
I think this lets me use the regular REST API, where appropriate, with Basic Auth, and maintain sessions/remember me functionality. It doesn't solve the multiple log ins issue, but otherwise I think this way should would. Please let me know.
I would use a token based authentication where you can send a token (automatically) with each request. You'll have to log in once, the server will provide you with a token which you can then use to send with each request. This token will be added to the HTML header, so that you don't have to modify each request to the browser.
You can set certain calls in the API so that they always need a token, while others might not be token protected.
For Express, you can use express-jwt (https://www.npmjs.org/package/express-jwt)
var expressJwt = require('express-jwt');
// Protect the /api routes with JWT
app.use('/api', expressJwt({secret: secret}));
app.use(express.json());
app.use(express.urlencoded());
If you want to authenticate you can create this function in your express server:
app.post('/authenticate', function (req, res) {
//if is invalid, return 401
if (!(req.body.username === 'john.doe' && req.body.password === 'foobar')) {
res.send(401, 'Wrong user or password');
return;
}
var profile = {
first_name: 'John',
last_name: 'Doe',
email: 'john#doe.com',
id: 123
};
// We are sending the profile inside the token
var token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 });
res.json({ token: token });
});
And for protected calls something that starts with /api:
app.get('/api/restricted', function (req, res) {
console.log('user ' + req.user.email + ' is calling /api/restricted');
res.json({
name: 'foo'
});
});
In your Angular application you can login with:
$http
.post('/authenticate', $scope.user)
.success(function (data, status, headers, config) {
$window.sessionStorage.token = data.token;
$scope.message = 'Welcome';
})
.error(function (data, status, headers, config) {
// Erase the token if the user fails to log in
delete $window.sessionStorage.token;
// Handle login errors here
$scope.message = 'Error: Invalid user or password';
});
And by creating an authentication interceptor, it will automatically send the token with every request:
myApp.factory('authInterceptor', function ($rootScope, $q, $window) {
return {
request: function (config) {
config.headers = config.headers || {};
if ($window.sessionStorage.token) {
config.headers.Authorization = 'Bearer ' + $window.sessionStorage.token;
}
return config;
},
response: function (response) {
if (response.status === 401) {
// handle the case where the user is not authenticated
}
return response || $q.when(response);
}
};
});
myApp.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor');
});
If you have to support old browsers which do not support local storage. You can swap the $window.sessionStorage with a library like AmplifyJS (http://amplifyjs.com/). Amplify for example uses whatever localstorage is available. This would translate in something like this:
if (data.status === 'OK') {
//Save the data using Amplify.js
localStorage.save('sessionToken', data.token);
//This doesn't work on the file protocol or on some older browsers
//$window.sessionStorage.token = data.token;
$location.path('/pep');
}
}).error(function (error) {
// Erase the token if the user fails to log in
localStorage.save('sessionToken', null);
// Handle login errors here
$scope.message = 'Error: Invalid user or password';
});
And the authintercepter we swap for:
angular.module('myApp.authInterceptor', ['myApp.localStorage']).factory('authInterceptor', [
'$rootScope',
'$q',
'localStorage',
function ($rootScope, $q, localStorage) {
return {
request: function (config) {
config.headers = config.headers || {};
config.headers.Authorization = 'Bearer ' + localStorage.retrieve('sessionToken');
return config;
},
response: function (response) {
if (response.status === 401) {
}
return response || $q.when(response);
}
};
}
]);
You can find everything except AmplifyJS in this article:
http://blog.auth0.com/2014/01/07/angularjs-authentication-with-cookies-vs-token/
Have a look to the yeoman generator for angular and node? The generator-angular-fullstack have a very nice structure for user authentification using passport.
You can see an example here :
the code: https://github.com/DaftMonk/fullstack-demo
the result: http://fullstack-demo.herokuapp.com/
Hope it helps!
I use generator-angular-fullstack, the /api services are not secured, get your _id from /api/users/me, logout, and go to /api/users/your_id_here, you will figure out that the /api not secured.

Facebook Authorization on Rails app: why do we need to do Both server and client side authorization?

In Ryan's Railscast on Facebook authorization, he adds some Facebook SDK javascript at the end to "degrade facebook client side authorization with server side authorization." However, I do not see the use of it. If we already set up the authorization from the server side using omniauth, why do we have to add the client-side authorization again? What difference does it make?
The referenced javascript code is (From the linked Railscast):
jQuery ->
$('body').prepend('<div id="fb-root"></div>')
$.ajax
url: "#{window.location.protocol}//connect.facebook.net/en_US/all.js"
dataType: 'script'
cache: true
window.fbAsyncInit = ->
FB.init(appId: '<%= ENV["FACEBOOK_APP_ID"] %>', cookie: true)
$('#sign_in').click (e) ->
e.preventDefault()
FB.login (response) ->
window.location = '/auth/facebook/callback' if response.authResponse
$('#sign_out').click (e) ->
FB.getLoginStatus (response) ->
FB.logout() if response.authResponse
true
UPDATE:
One of the reasons we need to integrate FB.login authorization with the server-side authorization might be that the Omniauth server-side authorization does NOT work if it's accessed within the Facebook iFrame. If the user accesses the application for the first time, the application must ask for permissions; however, oAuth permission dialog cannot be loaded within the iFrame to prevent clickjacking. Calling FB.login can avoid such problem, because it will show the permission box as a popup(Omniauth popup option will not work).
So now I have a genuine reason to integrate client-side authorization, but the code from Railscasts does not work with my current settings. I've chosen to do it the following way.
Right now, I have the following script in my application.html.erb:
<script>
// Additional JS functions here
window.fbAsyncInit = function() {
FB.init({
appId : <%= ENV['FACEBOOK_KEY'] %>, // App ID
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
xfbml : true // parse XFBML
});
};
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
</script>
And in my view, I have the following link invoking the Facebook log in action:
<%= link_to 'log in with facebook', '/auth/facebook', id: 'fb_log_in_link' %>
I add the following script to the view page where I have the login link.
function login() {
FB.login(function(response) {
if (response.authResponse) {
window.location = '/auth/facebook/callback'
}
});
}
Also, I need to change the link to call the function instead of directing to /auth/facebook/
<%= link_to_function 'log in with facebook', 'login()' %>
Done! The server-side and client-side authorization are fully integrated. Since I was still confused after watching Ryan's Railscast, I want to add a little bit of explanation for those who might be also confused.
The way this works:
Facebook SDK is initailized when the while the page is loaded.
The user clicks the "log in with Facebook" link.
FB.login function is called by the link, and the user goes through all the permissions process (e.g. permission dialog showing up asking for the user's permissions).
Then, the user is directed to /auth/facebook/callback. From routes.rb we have the line match 'auth/:provider/callback', to: 'sessions#create'. Therefore, now the server will either create a new user or simply create a session if the user has already registered before.
Done! The user is logged in.
Merging server-side and client-side authorization has two major advantages:
1. If the user is logged into the application either inside Facebook(via appcenter) he will be logged into the application outside Facebook as well. Vice versa, if the user logs in outside Facebook, he will be logged in automatically if he accesses it within Facebook after.
2. Logging in with /auth/facebook does not work if the user logs in within Facebook iFrame. To prevent clickjacking Facebook prohibits prompting users to auth permissions dialog within Facebook iFrame. The only way to avoid this is to open the dialog in a separate popup, and logging in with FB.login solves the problem.
the short answer is - you don't.
you can choose between client side login (via javascript SDK) and server side login using omniauth.
the disadventage of server-side login is overloading the server for a call you can do from the client.
the advantage is that usually the token is longer (3 months token and not 1-2 hours like client side).
i suggest combine the two. use the client side for initial login, once you do that have an async call from the server side for extended token (only if you have to).
It just says,
Facebook provides a JavaScript SDK that we can use to authenticate a user on the client-side so that it doesn’t look to them like they’ve left our application then returned.
It means that this is for the client side understanding that when user returned from the application, it doesn't look like that they have indeed left it.

MVC3 AntiForgeryToken breaks on Ajax login

ASP.NET MVC's AntiForgeryToken mechanism is based on the current HttpContext.User. It uses that value to construct the token when you call Html.AntiForgeryToken(). Basically it is OK (see an explanation in the last paragraph here) but a problem arises when you log in through an Ajax call.
In my code, when a user logs in, the credentials are sent as a Json object in Ajax (the AntiForgeryToken hidden field value is also sent inside the Json), the server authenticates the user, applies FormsAuthentication.SetAuthCookie(), and returns a Json result which contains some user-specific data. In that way, I can avoid full page refresh upon login.
The problem is that every subsequent Ajax request to the server now fails upon ValidateAntiForgeryTokenAttribute, because it now expects an anti-forgery token that is incompatible with the anti-forgery cookie.
How can I get a valid anti-forgery token to put in the client's hidden field so every Json request after login will succeed?
I tried to get a new hidden-field token manually (using AntiForgery.GetHtml() on the action, extracting the token string itself, returning it to the client in Json and placing it in the anti-forgery hidden field manually in JavaScript) but it does not work - a subsequent Ajax call fails on the ValidateAntiForgeryTokenAttribute on the server.
In fact, every call to AntiForgery.GetHtml() (which is essentially what Html.AntiForgeryToken() helper does) produces a different token, which invalidates the previous one.
I also tried to set HttpContext.User = new GenericPrincipal(new GenericIdentity(email), null); as detailed here, but it doesn't work.
Note: This solution doesn't work for me, because of my specific situation: An Ajax login which changes the user identity on the server and hence every token that was generated before the login is invalid; this solution also doesn't apply because it addresses a different problem.
You will need to clear and redo any existing form token you have upon login. This means your login code will have to either refresh the current page (kinda kills the ajax portion of it eh), your own token implementation, or you will need to refresh your token. It is possible to request a partial view, extract the token, and update your form. You could actually have a restful url which returns nothing but a token to an authenticated user. One may argue this is a security issue, but I don't believe so because it is simply an easier way to get a token rather than requesting any view -partial or otherwise.
You should be able to easily get the token instances to replace via:
var token = $('input[name=""__RequestVerificationToken""]');
EDIT
After re-reading a few more times - I question
Why would you have a token on the form if the user isn't logged in. You allow the same form to be 'operated' while not logged in and logged in? Most sites on the net even in this case will redirect for a login. Am I understanding this correctly? If so, you may want to consider skipping the token here or use a second type of token for unauthenticated users. You I believe are saying an unauthenticated user can already submit something in the application - again if I understand this correctly - without being authenticated.
Ok, what I did was combine the answer from here: jQuery Ajax calls and the Html.AntiForgeryToken() with a partial. I'm using knockout but for those of you not familiar with it you should still be able to follow along pretty easily.
First my html:
<form id="__AjaxAntiForgeryForm" action="#" method="post">#{Html.RenderPartial("AntiForgeryToken");}</form>
<div id="loginTestView">
<button data-bind="visible: signedIn() == false,click: signIn">Sign In</button>
<button data-bind="visible: signedIn, click: signOut">Sign Out</button>
<form>
<button data-bind="click: testToken">Test Token</button>
</form>
</div>
The main difference being that instead of #Html.AntiForgeryToken() I have a AntiForgeryToken partial that contain #Html.AntiForgeryToken().
So to really clarify I now have a AntiForgeryToken.cshtml file with just:
#Html.AntiForgeryToken()
Now when you sign in/out you need to update the token so the javascript/jquery looks like:
$(document).ready(function () {
AddAntiForgeryToken = function (data) {
data.__RequestVerificationToken = $('#__AjaxAntiForgeryForm input[name=__RequestVerificationToken]').val();
return data;
};
var viewmodel = function () {
var vm = this;
vm.signedIn = ko.observable(false);
vm.signIn = function () {
$.post('Home/SignIn', function () {
vm.signedIn(true);
$.get('Home/GetAuthToken', function (newToken) {
$('#__AjaxAntiForgeryForm').html(newToken);
});
});
};
vm.signOut = function () {
$.post('Home/SignOut', function () {
vm.signedIn(false);
$.get('Home/GetAuthToken', function (newToken) {
$('#__AjaxAntiForgeryForm').html(newToken);
});
});
};
vm.testToken = function () {
$.post('Home/TestToken', AddAntiForgeryToken({ stuff: 'stuff' }));
};
};
ko.applyBindings(new viewmodel(), $('#loginTestView')[0]);
});
The main thing to pay attention to here is that the $.get needs to happen after the $.post to signIn/Out. This code could be cleaned up a bit, but that's the main take away. If you don't then since the requests are asynchronous the $.get could (and probably will) come back before you are actually signed in.
That should do it. I haven't run into any other times when the token is updated but it would just require just another call to update the partial.

Resources