Ask a Jedi: One login, multiple applications
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.
Comments
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.
<cfset tracker = createObject("java","coldfusion.runtime.SessionTracker")>
<cfset sessions = tracker.getSessionCollection("myapp")>
<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".
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/videoplay?docid=2288395847...
Just my suggestion may not be what your looking 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?
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.
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.
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
DK
DK
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...
