MVC3 AntiForgeryToken breaks on Ajax login - asp.net-mvc

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.

Related

How to obtain a Google oauth2 refresh token?

The following code uses the Google oauth2 mechanism to sign in a user. We need to process updates to the user's calendar while the user is offline, so we ultimately need the 'refresh token'. Does the result from grantOfflineAccess() return the refresh token (below, I can see that response.code holds a value that might be the refresh token)?
How can I get a refresh token that can be used (server side) to create new access keys for offline access to a user's Google calendar?
<script type="text/javascript">
function handleClientLoad() {
gapi.load('client:auth2', initClient);
}
function initClient() {
gapi.client.init({
apiKey: 'MY_API_KEY',
clientId: 'MY_CLIENT_ID.apps.googleusercontent.com',
discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'],
scope: 'https://www.googleapis.com/auth/calendar'
}).then(function () {
var GoogleAuth = gapi.auth2.getAuthInstance();
GoogleAuth.signIn();
GoogleAuth.grantOfflineAccess().then(function (response) {
var refresh_token = response.code;
});
});
}
</script>
<script async defer src="https://apis.google.com/js/api.js"
onload="this.onload=function(){};handleClientLoad()"
onreadystatechange="if (this.readyState === 'complete') this.onload()">
</script>
There is a reason why you are having a problem getting a refresh token out of JavaScript. That reason being that it's not possible.
JavaScript is a client side programming language, for it to work you would have to have your client id and client secret embedded in the code along with the refresh token. This would be visible to anyone who did a view source on the web page.
I think you realize why that's probably a bad idea. The main issue is that gapi won't return it the library just doesn't have that ability (not that I have tried in raw JavaScript to see if the OAuth server would return it if I asked nicely).
You will need to switch to some server side language. I have heard that this can be done with Node.js, but haven't tried myself. And Java, PHP, Python are all valid options too.
Based from this post, you should include the specific scopes in your requests. Your client configuration should have $client->setAccessType("offline"); and $client->setApprovalPrompt("force");.
After allowing access, you will be returned an access code that you can exchange for an access token. The access token returned is the one you need to save in a database. Later on, if the user needs to use the calendar service, you simply use the access token you already saved.
Here's a sample code:
/*
* #$accessToken - json encoded array (access token saved to database)
*/
$client = new Google_Client();
$client->setAuthConfig("client_secret.json");
$client->addScope("https://www.googleapis.com/auth/calendar");
$_SESSION["access_token"] = json_decode($accessToken, true);
$client->setAccessToken($_SESSION['access_token']);
$service = new Google_Service_Calendar($client);
//REST OF THE PROCESS HERE

Using a JSON user object for Authentication in AngularJS

So I've got an question about authentication and have been wondering how other people might handle this situation. I'm currently running an Angular app that is built on a Rails API.
So far for authentication I have a form that does a post to the Rails side which logs the user in and then sends them back to the Angular app on success. Once the cookie is set and the user is logged in, I'm able to access a user.json file which contains all the User information one might expect (Id, username, roles, rights, etc). Since verification all happens on Rails, if the user logs out then this information is removed. So the two states look like so...
Logged in
{
id: 99384,
name: "Username",
url: "//www.test.com/profiles/Username",
timezone: null,
rights: [ ],
roles: [
"admin"
],
}
Logged out
{
error: "You need to login or join before continuing."
}
So far I've seen all these millions of different ways to do auth for Angular, but it seems like nothing fits this type of method. So my question is, since the server is handling all of the verification, is there a way to just check if they user.json file is empty (displaying the error message) and if it is send the Angular app to the Rails login page? Is there really any point messing with Cookies, Tokens, etc when I can base it all on the JSON file?
You are already using cookies - the server is setting them. What you have done is a fairly standard way of doing things.
To check the json file, you can do something like this stub shows in your controller:
app.controller('AppControl', function($scope, $http, $location){
// Get the JSON file.
$http.get('/path/to/json/file')
.then(response){
if(response.data.error){
// redirect to login
$location.path('login');
}
else{
$scope.user = response.data;
// your app code here.
}
})
.catch(function (error){
// unable to reach the json file - handle this.
});
});
Of course, you should really move this out into a service so you can re-use it, and also cache the data, rather than getting the user every time you change route/page, but this gives you a vague idea.
EDIT Example factory:
.factory('User', function( $http ){
// Create a user object - this is ultimately what the factory will return.
// it's a singleton, so there will only ever by one instance of it.
var user = {};
// NOTE: I am assigning the "then" function of the login promise to
// "whenLoggedIn" - your controller code is then very easy to read.
user.whenLoggedIn = $http.get('user.json')
.then(function(response){
// Check to see if there is an error.
if (response.data.error !== undefined) {
// You could be more thorough with this check to determine the
// correct action (examine the error)
user.loggedIn = false;
}
else {
// the user is logged in
user.loggedIn = true;
user.details = response.data;
return user;
}
}).then; // <-- make sure you understand why that .then is there.
return user;
})
Usage in the controller
.controller('ExampleController', function($scope, User){
// It's handy to have the user on the scope - you can use it in your markup
// like I have with ng-show on index.html.
$scope.User = User;
// Do stuff only if the user is loggedin.
// See how neat this is because of the use of the .then function
User.whenLoggedIn( function (user){
console.log(user.details.name + " is logged in");
});
});
Because it's on the scope, we can do this in the html:
<body ng-controller="ExampleController">
<h1 ng-show="User.loggedIn == null">Logging in..</h1>
<h1 ng-show="User.loggedIn == true">Logged in as {{ User.details.name }}</h1>
<h1 ng-show="User.loggedIn == false">Not logged in</h1>
</body>
Here is an example on plunker where this is working.
Note the following:
If the user is/was already logged in, when you inject the service in the future, it won't check the file again. You could create other methods on the service that would re-check the file, and also log the user out, back in, etc. I will leave that up to you.
There are other ways to do this - this is just one possible option!
This might be obvious, but it's always worth saying. You need to primarily handle authentication and security on the server side. The client side is just user experience, and makes sure the user doesn't see confusing or conflicting screens.

Changing CSRF Token With AJAX

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?

jQuery Mobile Site using an ajax $.get() to check username availability returning previous page code in return data

I have a simple JQM site I'm working on. I'm trying to validate the availability of a username on the fly in a form. I'm using jquery $.get() ajax to return "success" or "fail" however the return data is being replace with the code of the previous page.
$(document).on('pageinit', function () {
// check to see if username is available
$("#username").change(function() {
$.get("controller.php", { action: "check_username", username: username }, function(data) {
console.log(data);
}
});
The controller.php is checking for availability of the username and return "pass" or "fail" When I do the console.log(data) which I'm expecting to be pass or fail, it's logging out the code from the previous page??
I'm thinking maybe it's a JQM caching issue so I tried to disable cache with no effect. I was orginally using a JQM dialog box to display the form. Thinking that had something to do with it I pulled that out and loaded a straight link. That didn't fix it so I tried to load the page directly using
$.mobile.changePage( "user-new.php", { reloadPage: true});
I am stumped. Why would a $.get ajax call return data be returning code from the previous page?
Here's a face palm! My controller was authenticating and kicking it back out to a login page. Apparently php redirects act funky with ajax return data. Rather then returning the login page code it was returning the previous page code. I Removed the authentication and it works fine. Unbelievable! I'm going to go work at a gas station or something :)

Can you pick a browser target server-side?

I have a form that lets users select checks, and when submitted, creates a PDF, which opens in a new browser tab. It doesn't have any branding, and will probably open in a plugin anyway, so I don't want it taking over my site's tab. So I set the form's target to _blank.
But it's possible for the user to submit the form without enough information to create the PDF, in which case I flag the error (server-side) and re-render the form. But because I set the form's target, this re-render opens in a new tab as well, and that's not what I want - in this case, I want it to behave as if target were _top.
So the question is: Can I change the browser's rendering target server-side?
Yes, I know that this can be done with client-side JavaScript, but JS annoys me, and I have to do the validation server-side anyway. I may end up having to use it, but please don't suggest it as an answer - I'm more curious if what I'm attempting can even be done.
PS: I'm on Ruby on Rails 2.3.8, in case anyone knows a framework-specific solution.
A workaround on this problem would be to use the content-disposition header on the pdf, in order to force the file to be downloaded, and avoid the whole "target" approach..
Content-type: application/pdf
Content-Disposition: attachment; filename="downloaded.pdf"
No. This is a purely client-specific feature. As a matter of fact, it's quite possible to get a browser that supports only one window and where the target attribute would have simply no effect. There were even efforts to make this attribute disappear from future HTML standards completely (for instance, the XHTML branch had no such attribute).
The only overlap that I can think of between HTML and HTTP are the <meta http-equiv> tags (where HTML can affect otherwise HTTP-controlled behavior). HTTP is a transfer protocol, designed to work with about just any kind of data. Letting it control presentation would be a pretty terrible mix of concerns.
Fortunately, we live in a JavaScript-enabled world. It is rather easy to validate a form using an AJAX request, especially with libraries like jQuery.
For instance, this script performs a POST request to an URL (in this case, /pdf/validate) and expects the page to send back "ok" (if everything's good) or something else if there was an error.
<form method="post" action="/pdf/send" id="pdf-form">
<!-- form stuff here -->
</form>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$(document).ready(function()
{
// set to true if we are to bypass the check
// this will happen once we've confirmed the parameters are okay
var programmaticSubmit = false;
// attach an event handler for when the form is submitted
// this allows us to perform our own checks beforehand; we'll do so by
// cancelling the event the user triggered, and do the submit ourselves if
// we detect no error
$('#pdf-form').submit(function(event)
{
if (!programmaticSubmit)
{
// first off, cancel the event
event.preventDefault();
// do an AJAX request to /pdf/validate
$.ajax("/pdf/validate", {
type: "POST",
data: $(this).serialize(), // send the form data as POST data
success: function(result)
{
// this gets called if the HTTP request did not end
// abnormally (i.e. no 4xx or 5xx status);
// you may also want to specify an "error" function to
// handle such cases
if (result == "ok")
{
// since the server says the data is okay, we trigger
// the event again by ourselves, but bypassing the
// checks this time
programmaticSubmit = true;
$(this).submit();
}
else // something went wrong! somehow display the error
alert(result);
}
});
}
});
});
</script>

Resources