A few days ago I blogged a simple example of doing an Ajax form post with jQuery to a ColdFusion page. Today I'd like to build upon that simple example by demonstrating a login process that uses jQuery to fire off the authentication requests. I know I keep saying this, but do remember that this is just one way to do this and you could modify this code quite a bit if you wanted it to run a bit differently.
Let's start with a simple Application.cfc file for my application.
<cfcomponent output="false">
<cfset this.name = "jqlogin">
<cfset this.sessionManagement = true>
<!--- Run before the request is processed --->
<cffunction name="onRequestStart" returnType="boolean" output="false">
<cfargument name="thePage" type="string" required="true">
<cfset var page = listLast(arguments.thePage,"/")>
<cfif not listFindNoCase("login.cfm,auth.cfc",page)>
<cfif not structKeyExists(session, "loggedin") or session.loggedin is false>
<cflocation url="login.cfm" addToken="false">
</cfif>
</cfif>
<cfreturn true>
</cffunction>
<!--- Runs when your session starts --->
<cffunction name="onSessionStart" returnType="void" output="false">
<cfset session.loggedin = false>
</cffunction>
</cfcomponent>
There are two methods defined in this Application.cfc. The important one is the onRequest. It will be used to handle security for the application. If the request is not for my login page or my authentication CFC, and if you aren't logged in (notice I check a session variable initialized in the onSessionStart), then we push the user to the login page.
Now let's look at auth.cfc:
<cfcomponent>
<cffunction name="processLogin" access="remote" output="false" returnType="string">
<cfargument name="username" type="string" required="true">
<cfargument name="password" type="string" required="true">
<cfif arguments.username is "mcp" and arguments.password is "tron">
<!--- store login success in session --->
<cfset session.loggedin = true>
<cfreturn "success">
<cfelse>
<cfreturn "failure">
</cfif>
</cffunction>
</cfcomponent>
Obviously a real authentication CFC would have a bit more to it. This one simply checks for a hard coded username and password. Depending on if these values are specified correctly, the CFC will either return the word failure, or return success while also setting the session variable. Oh, and of course, I set the method to have remote access. (And yes, I did almost forget that.)
Ok, so far, nothing really special has been done. Let's look at login.cfm and how I used jQuery.
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
function sendForm() {
$("#status").html('Logging in...');
$.post('auth.cfc?method=processLogin&returnFormat=plain',$("#loginForm").serialize(),function(data,status){
data = $.trim(data)
if (data == 'failure') {
$("#status").html('<b>Your login failed...</b>');
} else {
//good login
$("#status").html('');
document.location.href='index.cfm'
}
});
return false
}
$(document).ready(function() {
$("#loginForm").submit(sendForm)
})
</script>
</head>
<body>
<form id="loginForm">
Username: <input type="text" name="username"><br/>
Password: <input type="password" name="password"><br/>
<input type="submit" value="Login" />
</form>
<div id="status"></div>
</body>
</html>
Let's take this from the bottom up. At the bottom of my page I have my login form with two simple fields. Below that I have a blank div with the id of status.
Moving up into the script code block, notice the my document.ready code simply hijacks the form's submit action. The send form should look pretty familiar to the one I did in the last blog entry. I start off by creating a message in the status div. This will let my users know that something is going on. Next, I use the built in serialize() function to convert the form into data and send it to my ColdFusion code. This time I'm posting to a CFC method, so I specify a returnFormat to keep the result in simple text. If a failure was returned, I set a message in the status div. If the result was good, I clear the status and then send the user to the home page. Why clear the status? If the home page takes a few seconds to load, I don't want the user to think that their login failed.
That's it. You can demo this here: http://www.coldfusionjedi.com/demos/jqlogin. I've also attached the files to this blog entry.
Download attached file. Old demo links removed on 8/29/2017.
Archived Comments
From what I know, doing this will send the authentication information (username/password) in clear-text and would be easily sniffed in a MITM attack. From what I understand of AJAX, this is still true even if the login.cfm page is used under SSL. If I am correct, could we use a JavaScript function to hash the password on the client before sending. Then use an identical CF hashing method to compare what's in the password database with what we get from the client? Maybe there's a better way to secure the communications. If I know the communications can be secured with an encryption method, I would definitely implement similar code in a project I'm currently working on. Ray - I'd like to hear your thoughts on this.
I don't believe you are right. If the pages were under SSL, then the request to hit the CFC would use the same domain and protocol, and would also be SSL.
@Ray As usual... You're correct! My occasional misconceptions of how things work sometimes make me jump to concussions. I've just recently began using jQuery primarily as a client side solution (with ColdFusion) and examples like this are great for sparking ideas and thinking outside the box. Thanks for the great advice and keep up the good work.
Please know, I'm still a Padawan of Ajax, and certainly jQuery. :) Don't assume I'm right. Except when I am. Then I'm brilliant. When I'm wrong, blame the docs! :)
@Ray If I wasn't so busy, I would do some actually testing. After doing some quick research on AJAX/SSL/jQuery, I found that you're most likely correct. I'll give you the benefit of the doubt anyway. I'd definitely like to hear from some other people to verify this claim of security. Can someone please test this under an SSL page to see what server port the AJAX request is coming in on?
confirmed -- SSL roundtrip
Does this mean I'm brilliant? (Lie to me please.)
5.5W White LED brilliant :)
Ha! I did something just like this. Except mine is a bit more verbose. I used .ajax() instead of .post(). Both work though. I've also coupled it with a nice jquery dialog logout screen.
Thanks Ray for showing me that I'm not a complete idiot and I'm on the right track...hahah
@Brian - Thanks!
I have this example working (nice stuff), but I was wondering if you have tried using the jQuery load() function in ColdFusion? I have tried doing something as simple as:
$.('#result').load('post.cfm');
But nothing appears to be returned. All I'm asking it to do is return a string of text, so I don't understand what could go wrong. Are you able to use .load()?
Yes, it does work. I assume you tested in Firebug. What do you see there?
After looking at my code a little more closely, I seemed to feel a disturbance in the force. I saw that there was a single extra period after the dollar sign (shown in my original comment). That period didn't belong there. So with a firm grasp of my light saber, I struck it away and reclaimed the harmony of the force. So satisfying this victory was. I think I'll head over to the Cantina coffee shop for a chat with Greedo. Come on over, I'll buy you a drink!
I'm trying to get this example to work for me but I'm not sure if it is my code or if its that stupid OnRequest in the Application.cfc bug that is causing me problems (I did the work around http://www.coldfusionjedi.c... but I am not sure exactly how it breaks the code I would assume it just wouldn't work at all????).
What happens for me is when I submit the form, my CFC is logging me in. It returns "success" to the javascript code. But the code to deal with the returned data ("Success" or "Failure") doesn't run properly.
The odd thing is if I just display what my CFC returns using jquery it shows "Success" or "Failure" depending on if I supplied the proper login or not.
I changed the code to...
<script>
function sendForm() {
$("#status").html('Logging in...');
$.post('ajaxtest.cfc?method=processLogin&returnFormat=plain',$("#loginForm").serialize(),function(data,status){
data = $.trim(data)
if (data == 'success') {
$("#status").html('<b>You are logged in!</b>');
} else {
//good login
$("#status").html(data);
}
});
return false
}
$(document).ready(function() {
$("#loginForm").submit(sendForm)
})
</script>
As you can see if data equals "success" display that I logged in. Else just display what data is.
This part of the code doesn't work even if data equals "success".
if (data == 'success') {
$("#status").html('<b>You are logged in!</b>');
But this part of the code works.
else {
//good login
$("#status").html(data);
It will display "Success" or "Failure" depending on if I supplied the proper login or not.
My question is, is this caused by the OnRequest bug or is my script messed up??? I'm not sure what the bug does as far as breaking ajax, remoting, etc. I know that is does break it, but I'm not sure in what way, like I said before I'd assuming that it just wouldn't work. But the fact that I'm passing my login to the CFC and the CFC is returning a variable to my jquery and I can display that variable, I just can't process it in the code, doesn't seem like the OnRequest bug would do that. Or am I wrong?
If you console.log (assuming you have Firebug) data, what do you see?
Ray, Thanks for getting back to me. I've been reading you blog a lot lately. Awesome stuff. Thank you!
Any way, I'm super new to firebug so I don't know exactly how every thing works. But I checked the console logs, no errors as far as I could tell. All it said was POST (domain name) 200 OK.
So I broke my test application down to the basics and added one piece at a time. Found out that it was an include file in the onRequestEnd. It had a bit of html in it (</body> and </html>) (since found out that you really shouldn't use OnRequestEnd for the footer but still doesn't explain this...)
<!--- Runs at end of request --->
<cffunction name="onRequestEnd" returnType="void" output="true">
<cfargument name="thePage" type="string" required="true">
<cfinclude template="includes/footer.cfm">
</cffunction>
But if I had CF in it instead, it worked.
I'm still learning about Application.cfc, ajax, jQuery but that just seems odd to me.
The other weird thing I found was if I declared Application variables in my Application.cfc like
<cfset this.name = "ApplicationName">
And didn't have an onRequestStart and onRequest I couldn't call the Application Variable. It would through an error. It wasn't defined.
I didn't think either of those were connected to Application Variables.
Every thing works now.
If I may be so bold, here is a video blog I did on real basic Firebug stuff: http://www.insideria.com/20...
Firebug would have been helpful in seeing that the response included the extra HTML. That 'broke' your "sucess" response.
As for your next issue - I think we need to clarify it. If you definite an Application variable within onApplicationStart, it will be available, period, whether or not you use onRequestStart. If you see otherwise, ping me via email and let's try to reproduce the situation.
I'll watch the FireBug video. Thank you.
Now thinking about it, looking through FireBug earlier it did say </body> and </html> in one of the tabs when I hit the + next to the POST (domain name) 200 OK.
I did just email you the Application.cfc and OnRequestStart/OnRequest problem.
Hopefully you are reproduce what is happening.
This works great except in IE8 it wont redirect to the url using document.location.href='mypage.cfm'. I even tried window.location='mypage.cfm' etc and nothing...works great in FF and Chrome.
It just loads the same page....any work arounds?
I just tried in my IE8 and it worked ok. Can you try again?
Hi there,
I really like the articles - they are clear and easy to follow.
I've replicated this page and got it working OK on my server (which is running Coldfusion MX7 so I can take advantage of returnFormat=plain but I can manipulate the string that is returned and work around it)
My problem is when I use exactly rhe same code on jquery mobile.
So I've got a page linking to the relevant libraries. Nothing at all changes on the page apart from the following:-
On the page that works I've got this form on the page and nothing else really:-
<form name="loginForm" id="loginForm" onsubmit="sendForm();" action="" method="post" >
Account Ref <input type="text" name="client_ref" />
Password <input type="password" name="password" />
<input type="submit" name="button" id="button" value="Log me in" data-theme="b" />
</form>
<div id="status"></div>
That works fine - as soon as you click submit you get the 'logging in' text in the status div and then:- incorrect client_ref and password give an error message in the status div; correct data sends you to another page
Great I thought - I'll now put the mobile stuff in there so I commented out the above and added in the following instead:-
<div data-role="page" data-theme="b" id="home">
<div data-role="content">
<form name="loginForm" id="loginForm" onsubmit="sendForm();" action="" method="post" >
Account Ref <input type="text" name="client_ref" />
Password <input type="password" name="password" />
<input type="submit" name="button" id="button" value="Log me in" data-theme="b" />
</form>
<div id="status"></div>
</div>
</div>
It's the same code except wrapped in the data-role divs
Now it seems to fall over when making the .post call - it briefly shows the 'logging in' message (which is the line before the .post line)
$.post('auth.cfc?method=processLogin&returnFormat=plain',$("#loginForm").serialize(),function(data,status){
.... Heres my checking......
});
And then it does nothing - the page returns to as it was before. The status div is wiped each time but I'm not sure how. The result is the same whether I give correct or incorrect login details. I've tried adding data-ajax="false" to the second version to see if that helped but it didn't do anything.
Any ideas?
Thanks for your help
Mark Slade
Can you post it online (with a valid username/password we can use)?
Thanks Ray,
If you go to www.fidler.co.uk/mobtest/in... I've set up links to the two versions of the file - one that works, one that doesn't. Logging in with guest and secret should succeed - anything else should fail.
Thanks
Mark
I don't think it will matter, but I'd change
<form name="loginForm" id="loginForm" onSubmit="sendForm();" action="" method="post" >
to
<form name="loginForm" id="loginForm" onSubmit="return sendForm();" action="" method="post" >
And actually I'd get rid of the onSubmit and use a submit handler in your jQuery code. But let's take it slowly.
Ok thanks - do you want me to make those changes and see what happens?
Yep.
Thanks again Ray - I've done that now - made those changes in both files - no change.
I did have a jQuery submit handler in the code and it's still there - I only added in the onsubmit thing because it wasn't working. Currently I have both in there
Here's the submit handler I have:-
$(document).ready(function() {
$("#loginForm").submit(sendForm)
})
(I also just tried taking out the onsubmit altogether but that didn't seem to change anything)
Cheers
Mark
If you have both, go ahead and remove the onsubmit. Switch to what you have in the comment, update the code, and let me know. I'll take another look at it.
Thanks - I've done that now - I've taken out the onsubmit() on both pages - can't see any change. On the incorrect one the page flickers though so not sure if something is being displayed that I can't see
You forgot to prevent the default action of the submit. In your handler, ensure you have e as an argument:
function sendForm(e)
and do
e.preventDefault();
as your first line.
Also, your code is removing WDDX from the result. Why? You specified returnformat=plain.
It may be even simpler - change your submit button to a regular button and bind to the click event for it.
Brilliant! - your second version worked. I still put in the e.preventdefault() line as well though. Sorry about the delay in replying - for some reason I'm not getting emails when you post a response.
Anyway, Many many thanks for sorting this out
Cheers,
mark
Forgot to add - you asked why I'm removing the WDDX from the result because I'd specified returnformat=plain.
I'm running CFMX7 which doesn't recognise the returnformat option - I think it was introduced with CF8. Hence I manipulate the output to make it work.
Thanks again
Whenever you post a comment to BlogCFC, even if you've done it N times in a thread, you have to reclick subscribe. I did that because I wanted to be REAL anal about sending emails to folks. If you don't REALLY want it, I err on the side of not sending. There's also a link to just subscribe though if you forget.
re: WDDX comment: Ah ok. I'd probably recommend pointing your Ajax to a CFM then. Have the CFM call the CFC and use a CFC to generate your JSON. Or just output since you were ok with plain anyway.
Thanks again ray