After my last post I thought I ought to write something about Single Sign On (SSO) – this post will cover a bit more than just SSO.
I’ve done a lot of work with SSO but it’s one of those things you only visit periodically so it’s easy to forget things – also it’s harder than it feels like it should be!
This post is intended to be fairly generic, although I will use some examples from applications that I’ve worked on.
For anyone especially interested in Alfresco please note that I haven’t looked at any of the version 6/Identity Service or ADF stuff as yet.
My first point is that if you’re working on a new SSO project then the first thing you need to do is to work out how you are going to merge your user data from the different apps – this is generally the hardest part and I’ve seen quite a number of projects give up because of this. It’s a good argument for getting your SSO sorted out as soon as possible.
Single Sign On is where you log in once and are authenticated to all applications (also potentially logged out).
Shared Sign On is where you use a shared user dictionary e.g. LDAP but each application is responsible for it’s own authentication.
Authentication is who are you, authorization is what you can do.
There are many, many guides to this on the internet and if you’re really, really lucky you might find one you can understand.
Single Sign On
My first point is that if you do this right then the protocol/technology doesn’t matter all that much – this area is a lot more mature than it was even a couple of years ago.
What you are trying to achieve is to protect a list of endpoints (URLs) (probably not all e.g. CSS) and communicate a user id through to your application.
Try and keep this as (logically) separate as possible – just identify the user. It can be tempting to link this up to authorization but try not to, it just causes trouble.
The aim here is to intercept the incoming request and process it before it gets to your application. There are different ways of doing this e.g. in Apache, in Tomcat, in filters.
For java what you are aiming for is that a call to request.getRemoteUser() will get you the user id.
For java applications my preference is to try and use a web-fragment.xml to define the filters and endpoints. The problem with this is that any filters defined in the web-fragment are applied after filters in the web.xml so depending on how the application is structured this may not be possible (sometimes you can get away with writing another application specific filter but that’s not ideal – I managed to do this for 5.1 Share and repo but not 5.2 Share).
See https://issues.alfresco.com/jira/browse/ALF-21848 for a suggestion for restructuring the Share web.xml to make this easier.
Another common approach is to just edit the web.xml, sometimes using a maven profile, but that’s not ideal.
A quick aside here – be careful about your username/id – for example we log in using email address but use a different attribute as the user id. Single sign on systems will support this but you might need to be careful in your configuration.
Username/password log in
Sometimes you will want to log in using a username and password – this might be for using a non web or mobile client e.g. in Alfresco CMIS, mobile or IMAP.
Probably the cleanest choice here is to make the client do the work by obtaining a token from SSO and using that in conjunction with your SSO mechanism of choice, however that’s not always practical.
Another alternative is to identify the request, one way is look for the authorization header, and proceed from there either by using SSO proxy authentication (see below) or carrying on to your normal username/password auth method (note Alfresco doesn’t support using a different username attribute out of the box)
This is where you have logged in to one application and want to pass that authentication information through to another.
The idea here is that after the client application (C) has authenticated and thus allowed the server side application (A) to authenticate itself using the SSO mechanism then application A can obtain a token from the SSO server and passes that token through to the other server side application (B) it wants to talk to. Application B then validates the token against the SSO server and as a result obtains the identity information.
What you are doing is intercepting and wrapping the outgoing request from A to B and including the SSO token (typically as part of a header but this can be handled by a library)
An Alfresco specific aside, at least up to version 5.2, Share SSO communicates with the repo/platform/ACS (whatever you want to call it…) by setting a custom HTTP header containing the username (you have to be careful about the security of your configuration!)
So in summary this splits into the following parts:
- Application A obtains an access token from the SSO server (standard part of libraries)
- Application A injects the access token into requests to application B (depends on how requests are made)
- Application B intercepts the request and uses the token to obtain the username (this will be the same configuration as is used for the normal SSO authentication, and subject to the same security constraints)
As I said earlier try and keep authentication and authorization separate.
It probably helps to understand this if you consider the evolution of (a lot of) applications.
Start off with a custom authentication and authorization layer, then realize that you need to use shared authentication (probably keeping the original method) so add in synchronization (with LDAP and others), then you realize that you want SSO so add that in later.
There are a few approaches you can take here:
(consider the performance implications, how look ups will be cached – you’ll be doing this a lot and the caching mechanisms, time out etc are not as well considered as for authorization)
Most SSO systems support some form of attribute release – you can use this information to set the rights within your application e.g. OAuth scope or parse a list of LDAP group memberships.
This means that the application must be used in an SSO context.
This can be, and probably would be, done with the same validation request as is used for authorization but do try to keep it logically separate.
Mixed SSO/Shared Auth backend
Use the SSO authorization and then an additional query to the shared backend to determine rights e.g. run a query against LDAP to determine group membership.
Mixed SSO/Custom/Shared Auth
This is probably the most common model that I’ve seen even though it’s relatively painful to configure.
Custom authorization model is set to sync with the shared auth backend.
Use the SSO authorization and then an additional query to the custom model to determine rights.
Custom model can contain rights(groups/group membership) not held in the shared backend.
There can be timing problems waiting for the custom model to be updated from the shared backend.
(Technically can be done without the shared auth but that’s really not a good idea so I’m not including it)
Proxy authorization service
Make a proxy authenticated request to a separate service which can manage the lookup(s)
This is a more flexible version of native SSO but comes with potential performance issues.
I’m throwing this in as an extra because it’s pretty similar to authorization in concept.
You might want to have information about the user, for example, their name to use in your application.
This information can be retrieved using the same methods as for the authorization information.
I’ve put this separately mainly because if you want to use an avatar or picture for the user then you potentially have a much larger piece of data to consider (and might want to convert the image into a more suitable format)
(Alfresco note – the use of the an image isn’t supported out of the box but it’s something that can be done with customizations)
Chances are that if you are using SSO then there will some external process for managing authorization e.g. LDAP groups.
If you want to manage authorization from within your application then ideally you need to authenticate against the existing management system before making any changes – this is one of the few occasions where you may want to be able to retrieve the user password from the SSO system e.g. to authenticate against LDAP. The alternative is use some form of super user from within your application but that isn’t ideal as it potentially gives elevated rights to your user by mistake.
Single Sign Out
You may not care about this e.g. by default Share in SSO configuration removes the log out option from the menu and there’s no way to log out. (It’s not too hard to put back in however – see previous posts and the alfresco-cas project on github)
Most applications have their own way of determining whether you are logged in as well as whatever is used by SSO. This is to support non SSO log ins.
The important thing to remember here is that you are logged in to both the SSO system and the application and when you log out of one, you need to log out of both (otherwise you’ll just be logged straight back in again)
Normally the idea would be to log out of the application first and then forward to the SSO logout page (probably with a redirection parameter to send you somewhere afterwards).
This doesn’t cover the more complex case where the user logs out of a different application. In this case the SSO application will send a logout request to your application (as part of the SSO logout process) – this logout request can then be handled to ensure that the local application logout also happens. i.e. logging out from application A also logs you out from applications B, C, D… as well as SSO.
It’s common for the first case to be handled but more unusual to handle the second case.
SSO is in a much better place than it was a few years ago but there’s still no one right way to do it and that’s likely to remain the case. (I’ve spent a lot of time helping people with CAS SSO for Alfresco)
SSO brings big benefits, both to the users and by providing a single place to manage authentication and, potentially, authorization.
Be careful! SSO provides a single point of failure for the organization and while this can mitigated by suitable configuration you still need to be careful especially during upgrades.
Don’t forget to keep on top of the upgrades!
Keep it as simple as possible as any customization makes upgrading more difficult (you are keeping on top of the upgrades aren’t you?)
Try to keep to only using one SSO system otherwise it’s not really SSO, as well as being more difficult to maintain – you’ll probably end up with some sort of shared LDAP based backend e.g. OpenLDAP or ActiveDirectory.
Make sure that you keep authentication and authorization (logically) separate.
If you write your application in a suitably flexible way then it should be possible to easily support any current or future protocols. To achieve this there are three main parts to consider:
- Make sure the client application can handle the authentication protocol – don’t forget authentication failures (should be fairly easy with client libraries, response interceptors etc)
- abstract the mechanism for protecting incoming requests – how to specify the endpoints to protect, and how to pass the validated information through to your application. (e.g. web-fragment.xml and getRemoteUser for java)
- Provide an abstraction layer for proxy requests i.e. make sure it’s easy to modify any requests between different application components.