I’ve worked on several projects now and practically every one of enough scale of them had the login use case. In in almost all those cases, I tend to be the one working on it. Even with other tools I have used such as Sonar, Redmine and Jenkins I had to deal with the login because they each had developed their own systems and kept it with the application. It’s only Curam projects that I worked on and one web app of my entire career where I actually used container managed authentication. My preference … container managed authentication hands down.
However, container managed authentication is not really the most trivial thing to do. In the past every application server had its own way of doing things. Thankfully Java EE 6 had brought in JASPIC as part of the standard (even if it is quite out of date in implementation). Now if we mix it with OAuth 2.0, we can push the authentication out of the app and the app server itself and put it in an enterprise OAuth 2.0 server instead of having just a thin veil in the application using Form based login.
I had recently implemented HTTP Header based authentication with JASPIC; this post talks about using the OAuth 2.0 system on JASPIC.
By doing things in JASPIC, we eliminate having the application deal with the responsibility of dealing with users. With OAuth 2.0 and OpenID Connect we can move things further by delegating the authentication to a totally separate service.
Just imagine this on an enterprise:
- users just have to login once to access the resources for your organization without relying on vendor specific extensions like Microsoft SSPI as this is an open standard.
- your internal development will be simplified as there is no need to deal with the login use case
- you can change the way your logins work (i.e. adding two factor authentication) in one spot.
- you only need to deal with one protocol HTTPS (no LDAP or database connections)
- you can readily expose your OAuth service endpoints to third party for external authentication
- if you’re small enough you can let Google, Salesforce or even your WordPress blog deal with your user management
I had one key objective in my implementation: no e-mails. In one of my previous projects and practically all Curam projects I have been on, the user ID is tied to the name of the user. This gives a lot of headaches as people have a tendency of getting married and changing their names. Therefore, some unique IDs must be used and OpenID specifications provide for it as the combination of the “sub” value and “iss” values in the JWT. So in my implementation I had use that as the user name which would look something like https://firstname.lastname@example.org.
My other objective was it should have the least amount of configuration by the administrator. In the end its just three configuration items that are required: client_id, client_secret and issuer_uri. The issuer_uri resolved with “.well-known/openid-configuration” points to the Open ID provider configuration that sets everything else up.
I had also added a cookie_context which defines whether I would share the token throughout the application server or just with the application.
I also wanted it so if I bookmark a page that is a “GET” then I should go directly to that page after I get authenticated. This is done through the “state” parameter that I encoded a return URI to that is relative to the application in Base64 without the padding to save a little bit of space.
One of the biggest dilemmas I had with doing this was how to keep the authentication without actually having a session created. In the current world we have the notion of REST and web sockets where the application server should not keep a session. One way this is done is through BASIC authentication where the user ID and password are sent over HTTP headers. However, the cost of login is very expensive to do for every request. In the end I found a good way of doing this… storing the JWT in a cookie.
The JWT is a digitally signed object so at the very least it would be hard to tamper without the private key for the signature. It is also set to expire after a while using the “exp” value. So it acts like a session except it is hosted on the client side rather than on the server side. As far the the server side goes, it is just a matter of calculations no need for network I/O except for getting the key.
Signature validations are expensive, it is generally cheaper to just do a simple symmetric encryption to just make sure that the data is valid. I also do not need the algorithm information once I had validated the data so for the cookie, I just stored an encrypted version of the payload. The secret key is generated on the fly using the
clientSecret for the password and
clientId for the salt. The payload validity is done on decryption where the system will throw a BadPaddingException or just be unable to parse the data. Of course this depends on how secret the clientSecret is.
Now the biggest downside with the JASPIC spec is the lifecycle of the ServerAuthModule. A new ServerAuthModule gets created and initialize gets invoked for every HttpServletRequest. This means I cannot store any data like public/private keys locally. The good part was I managed to avoid the issue altogether by using dynamically generated secret keys for the token.
validateRequest goes like this:
- If there is a token cookie, validate it. If successful update the subject principal with the token data and return SUCCESS, otherwise CONTINUE with redirect to authorization end point.
- If there is no “code” or “state” (i.e. request from the user), then CONTINUE with redirect to authorization end point.
- Otherwise… request a token from the token endpoint providing the code and save the token in the cookie then redirect back to the state also update the subject principal.
Reneg on personal information
Now I am going to backtrack on one of my objectives which was to avoid getting personal information. The reason being that sometimes knowing the profile that is managed on the authorization server provided it is the authoritative one will help reduce the effort to look it up or store in the application database. To do this, I allowed the “scope” option to be set to include “profile” which makes a request to the
userinfo_endpoint and retrieve the user profile information including e-mails. The data is stored in a cookie and encrypted the same way as the JWT token payload.
From my experience, the only rocket sciencey parts are figuring out how to deploy it to the container as the deployment is container specific and how to reduce the network I/O while maintaining integrity. The source for the server auth modules is on Github. This also includes the HTTP Header Server Auth Module.
So to summarize the features:
- E-mail addresses are not required to be known in order to authorize
- No network activity to authorization server if the token is valid
- Minimal configuration: client_id, client_secret, issuer_url
- Redirect to requested URI (as long as it is a GET request)
- Allow retrieval of full profile by adding