William asks:
I want to build a "portal" to access different web apps. I would like to have a single login in the portal and some sort of trust between the portal and the other web applications so users will not have to login multiple times. Is this possible using the cflogin framework?
Ok, so I like to rag a bit on the cflogin framework. In the "celebrity" sphere of ColdFusion, cflogin is the Stephen Baldwin of features. But I will say one thing for it - it does make sharing a login among multiple applications pretty easy.
The CFLOGIN tag supports an applicationToken variable. By default, this will be set to the current application name. But if you specify a value, and use the same value in your other applications, your login credentials will be available to all the applications.
If you don't want to use CFLOGIN, then you have to roll your own. Assuming that you must have multiple application names, then the only values that will be cross-application will be cookie values. You could use a cookie to store the logged in status as well as a pointer to some unique user ID that all your applications can use as a reference.
Archived Comments
I have been meaning to post an article about this. I am not a big fan of cflogin and I ran across the same problem. If you have 2 applications on the same server there is a way to loop all of the sessions from the server and check credentials that way.
"Stephen Baldwin of features." - Coffee meet monitor.
We currently do this. We actually have an ASP Portal application that writes a cookie with an ID for an individual user. We have 4 servers on the same domain. Two run ASP and two run ColdFusion apps. The login procedure is currently written in ASP. The cookie domain is set so each server in the domain should be able to read the cookie.
We use the following code to read in the cookie information into the ColdFusion Application Session. This is a method within a CFC:
<code>
<cffunction access="public" name="LoginUserWithCookie" output="false" returntype="boolean">
<cfif IsDefined("cookie.Application")>
<cfset Session.Person=StructNew()>
<cfoutput>
<cfloop index="thisKey" list="#cookie.Application#" delimiters="&">
<cfif ListLen(thisKey,"=") gt 1>
<cfset Session.Person[ListFirst(thiskey,"=")]>
</cfif>
</cfloop>
</cfoutput>
<cfreturn true>
<cfelse>
<cfreturn false>
</cfif>
</cffunction>
</code>
All the information in the cookie is duplicated in the session.
Hope that helps.
Dan - my issue with getting other sessions is that it really isn't "technically" supported, unless you use the Admin SM API, which normally doesn't make sense for most apps. I know there is code out there that uses internal CF objects, but that kind of stuff makes me nervous.
I know you will not find this code in any documentation but It works fine for me. Again, I am just not a big fan of cflogin so for those who do not mind it, your solution is the recommended.
<cfset tracker = createObject("java","coldfusion.runtime.SessionTracker")>
<cfset sessions = tracker.getSessionCollection("myapp")>
Sorry, correction to a line above. The line should read:
<cfset Session.Person[ListFirst(thiskey,"=")] = ListLast(thiskey,"=")>
We then use the ID that is captured from the cookie and query our database for permission information, though it could be stored in the cookie as well.
For clarification, I named my cookie "Application".
Dan - yep, that's the code I meant. I'd consider that dangerous code. Shoot, CF8 even now has an option to block stuff like that from the admin.
I love CFLOGIN, I stand alone here hehehe if I need anything else from it I usually create a UserSession.cfc
What about using something OpenID?
It is somewhat of a open source solution to .Net Passport 1 sign-on for many websites.
Your portal would be a OpenID provider, and each web-app could be a OpenID consumer.
http://openid.net/
There is also a really good Google Tech Talk about OpenID that i would suggest you view.
http://video.google.com/vid...
Just my suggestion may not be what your looking for
I second OpenID. I first discovered it via 37signals who use it for for Backpack, Basecamp and Highrise. I'm going to be using it for a project in the near future.
The cookie solution (as well as the cflogin solution) would only work still if the two applications were on the same server correct? If you had multiple applications on multiple servers you would need to set up an application on a server that would be like a localized authentication system and get it to tell all of your other applications if you were logged in or not through a webservice or something like that right?
@Ryan - yes. I was assuming he was all on one box, or one domain at least.
If you are on the same box and the domains do match then cookie solution is not going to work. This is the problem I ran into and even if it is just a sub domain you can not share cookies
Eh? You can set cookies for *.foo.com. That way if you have a.foo.com and b.foo.com, the cookies are shared.
can you set cookies through a function called as a webservice, for the domain that the webservice is for?
Example: I have a domain called auth.com, I have a site called site.com. Can site.com call a webservice on auth.com to set a cookie for auth.com, and then call another webservice on auth.com to see if that cookie is set?
Ryan:
If you use CFHTTP hit a remote URL, you can get anything back, including cookies.
If you use CFINVOKE, I'm not 100% sure. There is a getSOAPResponseHeader() method which MAY do it. Worse comes to worse, you can use CFHTTP and parse the WSDL yourself.
@ William, I had the same problem you did a while ago. Here's what I did, I'll pseudo-code it for you.
I made extensive use of folders and App.cfc in my example. I also stole Adobe's CF Login wizard in Dreamweaver and made parts of it my own. I basically expand on the session's structure... I had a ton of keys: IsLoggedIn, UserApps, AdminApps, FullName, UserName, EmailAddress, Etc... that I create for valuelists when I do a db query on their username.
Each subfolder includes an Application.cfc that extends the main App.cfc in the root. The OnRequestStart method checks the isloggedin value and also check to see if one of the struct key's values match the application name.
Check out the Login Wizard... it will really help you out.
I read over my last post and I'm not sure that I made myself clear.
A snip of the App.cfc OnRequestStart function:
<cffunction name="OnRequestStart" returntype="boolean" output="false">
<cfargument name="thePage" type="string" required="true">
<cfif IsDefined("url.logout")>
<cfset structDelete(session, "myapp")>
</cfif>
<cfinclude template="force_login.cfm">
<cfreturn true>
</cffunction>
And now the force_login.cfm page:
<!---If the User is NOT logged in the session variable will not exist, otherwise skip this logical block--->
<cfif not IsDefined("session.myapp.isloggedin")>
<!--- If the user hasn't gotten the login form yet, display it --->
<cfif not (IsDefined("form.login") and IsDefined("form.username") and IsDefined("form.password"))>
<cfinclude template="login.cfm">
<cfabort>
<!--- Otherwise, the user is submitting the login form --->
<cfelse>
<!--- Invoke the Authenticate Function, if returns TRUE then move on --->
<cfinvoke component="myapp.componets.security" method="authenticate" returnvariable="auth">
<cfinvokeargument name="username" value="#form.username#"/>
<cfinvokeargument name="password" value="#form.password#"/>
</cfinvoke>
<!--- If the username and password are correct... --->
<cfif auth>
<cfinvoke component="myapp.componets.security" method="userinfo" returnvariable="info">
<cfinvokeargument name="username" value="#form.username#"/>
</cfinvoke>
<cfset session.myapp.isloggedin = true>
<cfset session.myapp.username = #form.username#>
<cfset session.myapp.role = #info.role#>
<cfset session.myapp.name = #info.name#>
<cfset session.myapp.emailaddress = #info.emailaddress#>
<cfset session.myapp.deptid = #info.deptid#>
<!--- Otherwise, re-prompt for a valid username and password --->
<cfelse>
<cfinclude template="login.cfm">
<cfabort>
</cfif>
</cfif>
</cfif>
Hope that makes sense to you William
I set this up a t a previous employers. I started it with CF 6, you can use the approach on CF 7 and greater, but can't use application.cfc, have to use application.cfm. The approach is similar to what Ray points out here except it doesn't use cflogin and it shares the user info with the application after login. The short story is: you use two cfapplication tags in the code. One cf application, call it MASTER, handles login and stores the user info. Each sub app first checks this to see if user is logged in by using cfapplication name=MASTER. The app makes a copy of the user data then uses cfapplication name=SUBAPP1 to switch to the new cf application and copies the user data into it...bamm! There are a few details you need to handle, but that's the outline.
DK
DK
So no one here can give an example of setting up a single login to work across multiple applications on multiple servers with differing domains? Using applicationtoken probably wont work across servers, and I know the Master/Sub application setup wont work across servers. Cookie based probably wont work across domains.
There has to be some architecture that can handle that scenario, but so far the closest I saw was the idea to use a <cfhttp> call to hit a web service that returns cookie info...
Well to be fair, thats a big example and probably wouldn't be good for a comment here. If someone wants to blog it and post a URL, that's fine.