How to properly redirect in NodeJS/ExpressJS? - ajax

I am building a shopping cart application in nodejs/expressjs. I am having some issues with redirecting to the correct page after a user has signed in. I have found several articles that relate closely but all of my attempts to fix it from those given articles is not doing the trick.
access: function(req, res){
var data = JSON.parse(req.body.data);
console.log(data);
Customers.findOne({"custEmail": data.custEmail}, function(err, user){
if(err){
console.log(err);
res.send("error");
}if(!user){
console.log("user not found");
res.send("error");
}else{
console.log("user found");
bcrypt.compare(data.custPassword, user.custPassword, function(err, reponse){
if(reponse){
req.session.regenerate(function(err){
if(err){
console.log(err);
res.send("error");
}else{
req.session.success = true;
res.send("Success");
//res.redirect('/');
}
});
}else{
console.log("error");
}
});
}
});
What happens is, a user enters their login information and clicks the login button. An Ajax request sends the data to this access function. The function starts by checking to see if the user exists in the database. If it does than it continues to compare the bcrypt password, if they are equal they successfully login and if they do not equal then obviously it errors out.
The issue seems to be here: }else{
req.session.success = true;
res.send("Success");
//res.redirect('/');
}
When the user email and the password match this is hit right here. It says.. set this session to true, so the user can access pages where this session is required, and then sends a Success message back to the ajax request. This currently works perfectly fine and the res.send is sent to the front end. When I uncomment the res.redirect, the expected result of 'cannot set headers after they are set' happens. So when I comment out the res.send, the res.redirect simply ends up not working at all. No error, no breaking of anything just simply nothing.
I am curious to why this redirect here would not work? The headers are not being set anywhere else in this function and the expected result should be that the redirect would work perfectly fine. Any help is greatly appreciated. Thanks.

When you send an ajax call with either XMLHttpRequest or fetch() and get the response, the browser does not do anything to the current page in the browser based on that response. So, it doesn't matter whether you send a redirect response to the ajax call or not. The browser won't change the current page in the browser, no matter what. Ajax responses generate data for your Javascript and your Javascript must then decide what to do with it.
FYI, the browser will change the current page only when the redirect happens from a browser page request or a browser submitted form post (not Javascript submitted). Since that is not how you are logging in, you can't use a redirect to change the current browser page.
So, if you're going to use an ajax call on the client for login and you want the client to change pages after a successful Ajax login, then you probably want to send the redirect URL in the login response and have the client process it and set window.location to do the redirect on login.
For example, you could change:
res.send("Success");
to this:
res.json({status: "Success", redirect: '/'});
And, then have your client code examine the return from the ajax call and if the status is successful, it can grab the redirect property and do something like:
if (ajaxResult.status === "Success") {
window.location = ajaxResult.redirect;
}
I am curious to why this redirect here would not work? The headers are
not being set anywhere else in this function and the expected result
should be that the redirect would work perfectly fine. Any help is
greatly appreciated.
The message about headers have already been sent means that the whole response has already been sent with res.send("Success"); and you are now trying to send another response. You only get one response for a given request. Once you've done res.send("Success");, the http server object has sent the whole response and then closed off that response. You can't send more data at that point.

I ask a question a question somewhere else, and stackoverflow leads me here.
Why can't res.redirect() after res.render()
Just for people bump into same question as mine, I made a summary here.
After res.render(), the response ends. And res.redirect() will set the Header to the new url in Location, which we have already sent to client, and can't be set again.
My solution for this is to code in the client side location.href ="/", if that page receive a variable like redirectAttribute = true in html tag.

Related

Laravel redirect to intended keeps sending my users to json requests http get

Sometimes, a user session expires, no matter the reason. The user is redirected to the login page, as my routes sends the user there in case of Auth::guest().
Then it sends them back after successful login to an ajax/json page (like a list of images in json) - this happens because I'm using the Redirect::intended() call. This is completely unusable then - how do people work around this?
Using Redirect::guest() saves the requested URL to the session key url.intended. If you use Redirect::guest() in a filter on an AJAX route, this will still be the case and the URL of that AJAX call will be where the user is sent.
You can use if(Request::ajax()) {} to do something different for AJAX responses, like returning an error JSON instead of a redirect.

Node authorization and redirect using AJAX post

I am using node and trying to redirect a client after he authorizes himself. For this reason I am using a POST method using ajax on the client side which has the form:
$.ajax({
type: "POST",
url: '/login',
dataType: "json",
async: false,
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(credentials.username + ":" + credentials.password));
},
data: credentials,
success: function () {
window.alert("Success! (whatever that means)");
}
});
Then on the server side I am trying to redirect to the actual page with the commands
app.get('/login', loginInternalApp);
app.post('/login', function (req, res){
var postContents = req.body;
req.session.username = postContents.username;
res.redirect('/my_secret_page');
});
app.get('/my_secret_page', checkAuth, function (req, res) {
console.log("sending the message!");
res.send('if you are viewing this page it means you are logged in');
res.end();
});
where checkAuth was taken from here how to implement login auth in node.js (instead of user_id I am using username; that's not the problem).
Perhaps what I do not know is how to treat this ajax call correctly. I print some console messages and the server goes all the way to the res.send('if you are viewing this page...') however, nothing happens on the client. Then when I press a control-C to terminate the server, the alert window pops up. Meanwhile, I can see that the function passed on success can have parameters (guessing now: errorCode, errorString, otherData, perhaps more).
So what am I doing wrong?
The issue here is that you are a little confused between the concepts of JavaScript-powered navigation and straight up server request-response powered nav. Let's go through this for a minute and see if it makes more sense.
You are using ajax to submit your login form, which means you stay on the page that you are on, and just submit your http request with JavaScript, collecting the response. You put up the POST, and your server logs the user in, then returns a 3XX indicating a redirect. Your JavaScript collects the response, which you can see if you open up your inspector under the "network" tab. But the page doesn't go anywhere because you are just collecting your response with JavaScript.
In this case, you want to choose one or the other -- use JavaScript to handle your routing (tools like backbone are super useful in cases like these and come with nice routing classes), or submit the login form normally, not through ajax. If you let the form submit when the user hits the button and do not catch it with JS, this should fix the problem - if you return a redirect from the server, the page will redirect as expected. Alternately, if rather than sending a redirect response, you could send back JSON indicating success or failure, then use JavaScript to display the appropriate view, and this would also solve the issue.
Looking at your server-side code, I assume the reason you are using ajax here is in order to set the authorization headers. I'm not sure why you need those, as you hid the internal auth function, but you might see an issue with not using ajax being that you would not have those custom set headers by default in a normal form submission. There are certainly ways to collect the same information and move it in the same ways (you can set headers, delegate to other methods, and even send off http requests from express), but I'd need more details on how specifically you are handling logins to advise on how to streamline that piece : )

Ajax request redirect

I am doing a Ajax request, in the response depending on some condition. I might send a 301 status code with a location(redirect) URL. But when I do that there is a ajax request to the redirect URL, but I want it to be a normal request not a ajax request.
Is there a way to do that?
it's pretty easy, you might want the server to return an url instead of performing redirection, end then use window.location to perform redirection in javascript :).
Javascript can't see the redirect response, only the final response from the URL the browser was redirected to.
Javascript can try to recognize the situation by analyzing the response content: Maybe it expects JSON but gets HTML (e.g. a login page :-) )
To do it right you'd need to modify the service to return a non-redirect response code to the Javascript which it can then handle (e.g. 401 when the session expires and the user must log in again)

Kohana request security

I've searched previous answers but can't seem to find the exact answer.
I am using ajax to call a controller action. This controller action does some other stuff then calls a public function of my user controller:
Request::factory("/user/savepoints/".$new_total);
How can i secure my action_savepoints in the User controller from people just entering it as a URL?
I currently have this at the top of my function but it doesn't do what im looking for.
if( ! $this->request->is_initial() ):
HTTP::redirect('/error');
endif;
Thanks for your help.
Either use an HTTP POST request, which can't be done just by entering a URL. (Though it can spoofed, or done via a form)
Or:
How about generating a kind of token on the server, getting it to the ajax code somehow, then passing it back in the ajax request.
Obviously they could still forge the request manually if they pull the token out of your page, but you issued them the token in the first place.
You could make the token single-use, time limited, or user-specific.
Make sure it contains some kind of checksum with a server secret to prevent people building their own tokens.
Do you want to prevent any unauthorized users to run the script or do you want to make sure that the script only can be run via AJAX calls?
For the first, you can just check if the user is logged in. The AJAX request uses the same session as the ordinary requests.
if (Auth::instance()->logged_in())
For the second you need to check the HTTP headers if it's an AJAX call. But note that this is not a safe way to do it as HTTP headers can be altered by the client and can not be trusteed.
if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
{
//This is an ajax call
}
Unfortunately, there's no bullet proof or safe way to detect an AJAX request other than this.

phonegap: cookie based authentication (PHP) not working [webview]

I'm working on a mobile web-app using sencha touch, HTML5 and phonegap as a wrapper.
I'm using PHP-Authentication (Cookie) and ajax-requests. Everything works fine on safari or chrome, but after the deployment with phonegap (webview) it does't work anymore...
Any help would be appreciated :)
Some more details:
All data for my app is loaded via ajax requests to my server component "mobile.php".
I use basic PHP-Auth to autenticate the user:
AJAX-Request [username, password] -> mobile.php
-> Session established (cookie)
All other requests if auth was successful
What's the difference between a normal safari website and the webview?
i figured it out:
you have to change the phonegap_delegate.m file and add the following to the init method:
- (id) init
{
/** If you need to do any extra app-specific initialization, you can do it here
* -jm
**/
//special setting to accept cookies via ajax-request
NSHTTPCookieStorage *cookieStorage = [NSHTTPCookieStorage
sharedHTTPCookieStorage];
[cookieStorage setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
return [super init];
}
it enables webview to accept cookies from ajax requests
If your Phonegap AJAX requests are not firing callbacks like they're supposed to, this may be the reason.
If the response you're getting attempts to set cookies and you haven't done Michael's fix then your (jquery) AJAX request will fail quietly -- neither success: nor error: callbacks will fire despite the fact that the server actually received the request and sent a response. It appears you must do this even if you don't care about the cookies.
I hope this helps someone.
I didn't care about the cookies but just spent a few hours trying to figure out why the callbacks didn't fire!
There is a solution that works on android too:
Install plugin https://github.com/wymsee/cordova-HTTP to perform arbitrary HTTP(S) requests.
Replace XMLHttpRequest with the plugin alternative (cordovaHTTP.get or cordovaHTTP.post):
cordovaHTTP.post("https://example.com/login", {email: 'xyz#example.com', passwd: "s3cr3t"}, {}, function(response) {
console.log('success');
console.log(response);
}, function(response) {
console.log('failure');
console.log(response);
});
The response will contain status, data and response.headers["Set-Cookie"], that can be parsed for name, value, domain, path and even HttpOnly flags ;-)
Said cookie can be saved in LocalStorage and sent in subsequent requests (see cordovaHTTP.setHeader() or header parameter of .get/.post methods) to simulate an authenticated user on a desktop browser.
Best ways to store get and delete cookie its working fine in my app which is on live
To store value in cookie
window.localStorage.setItem("key", "value");
To Get value in cookie
var value = window.localStorage.getItem("key");
To Delete cookie value
window.localStorage.removeItem("key");
window.localStorage.clear();

Resources