Integrating LDAP Authentication with Seaside
For one of my projects at school, I am building an application using the Seaside web development framework. The project will eventually become a piece of courseware for a class I teach, and as such has requirements for authentication. I wanted to use our university’s authentication system to accomplish this. It took a fair amount of digging through the Seaside source code, and following the dead ends that the documentation provided (for example, WAAuthMain doesn’t even exist).
I decided to use the smalltalk LDAPLayer package to allow me to do what I needed to do. First order of business was to set up a way to do encrypted connections to the LDAP server. The LDAPLayer package does not support SSL connections out of the box (as far as i could tell), so rather than try to modify the source to allow it, I decided to run a local stunnel instance on my machine to provide a SSL proxy to the directory:
sudo stunnel -c -d 389 -r ad.uiuc.edu:636
I then can connect to localhost on the regular LDAP port, 389, and it will be securely tunneled to our campus’ Active Directory cluster.
The next step was to write an authenticator that would do the actual authentication. Mine turned out to look something like:
LDAPAuthenticator>>#verifyPassword: aPassword forUser: aUsername | dn req tempConn | aPassword ifNil: [^false]. aPassword ifNil: [^false]. dn := self getDNForUsername: aUsername . tempConn := LDAPConnection to: self class hostname port: self class port. req := tempConn bindAs: dn credentials: aPassword. [req wait] on: LDAPException do: [ :exception | tempConn disconnect. ^false ]. tempConn disconnect. ^true
I also implemented LDAPAuthenticator>>#getDNForUsername which converts a username into a fully qualified distinguished name for binding to the directory with. In addition, LDAPAuthenticator class>>#hostname and LDAPAuthenticator class>>#port return localhost and 139 respectively. It is important to note that the message implemented has to exactly match #verifyPassword:forUsername: and the message also needs to respond with false if either are nil (it was a bug that threw me off for a bit). Then its simply a matter of tying it all together in the initialize method of the application’s root component:
initialize "self initialize" | app authFilter | app := (WAAdmin register: self asApplicationAt: 'mypath') addLibrary: PTDeploymentLibrary; addLibrary: SUDeploymentLibrary; addLibrary: CTLibrary; yourself. app preferenceAt: #sessionClass put: MyCustomSession. authFilter := WAAuthenticationFilter new. authFilter realm: 'Active Directory. Use your username and Active Directory Password'. authFilter authenticator: LDAPAuthenticator new. app addFilter: authFilter .
Once that is complete, authentication works like a charm. Of course I could modify the authenticator to only allow members of certain groups, etc, but for my needs, letting anyone with valid credentials is quite alright. I also wanted to tie a user object representing the logged in user to my custom session, so I did the following to get the username of the authenticated user in my overridden #initialrequest in my root component:
username := self requestContext request user.
That took care of the whole mess for me…