Recently I had been tasked with creating a middle-tier web service which would essentially allow us to implement SSO, essentially allowing people that are a member of a specific AD group to access the application. As it turns out, this was no easy feat, there was a lot of very old & awful examples out there & the documentation around this subject area is far from what I would consider even acceptable.
But that aside, my colleague & I were determined to get this to work as we wanted our application to run on a Linux box rather than relying on Microsoft infrastructure. After many days & hours of tinkering & learning more about Kerberos & SPNEGO, we eventually got there. I can’t take all of the credit, not by any means, it was mostly my colleague that did the heavy lifting, but I’m mostly writing about this to raise a bit more awareness & hopefully provide an example for other people to be able to get over this hurdle without having to endure the pain that we went through.
So for those of you that don’t know, first off, if you desire to do what we did, then you’ll want to create some express middleware, it’s simple enough to do, if you’re unsure on how you can create your own express middleware, then take a look at this documentation as a starting point. As we wanted to essentially ensure that we’ve protected our resources on our middle-tier API, we’ll be covered & as we’re using middleware, we don’t need to manually implement it on each & every endpoint that we define within our API.
High Level Theory
Rather than giving you a super deep dive lesson on how SPNEGO, Kerberos, AD, NTLM & so on work at a low level, feel free to look that up in your own time, because if I did cover all aspects that make up this solution, well it would go from being an article to being an essay.
Instead, I’ll try to give a relatively high level explanation of how Kerberos works, I am sorry if this turns into a bit of a small & messy essay… 😂
I think we should start with Kerberos, as a summary, it’s the native authentication protocol that’s used in active directory. With Kerberos, you have 3 key components, the client, the server & the KDC(key distribution centre).
I hope by now you’re starting to see why the name Kerberos is so applicable to this authentication mechanism. Ironically, the client will take on the majority of the work load when dealing with Kerberos, as there is no communication between your server & your KDC.
When using Kerberos, an authenticator is created by the client when they try to access some protected resource, or when they try to log on to the network. To ensure that this authenticator package is secure, it’ll include some ID, the date & time. This authenticator is only valid for a certain period of time, this is by design to prevent a potential attacker from capturing & replaying that package on the network. Cool stuff so far! 👍
Kerberos will use your password to encrypt some of this payload, it will never transfer your password from one node to another, that’s what adds some value to the Kerberos mechanism. Your password is essentially a shared secret in this instance, the KDC will be able to decrypt the part of the authenticator that was previously encrypted by the client.
Now that the KDC is able to authenticate the authenticator package, it’ll return a TGT(ticket granting ticket). The KDC/domain controller will use its own encryption key to encrypt a portion of this TGT before sending it back to the client. Once the TGT reaches the client, the client will then store this TGT in a special section of memory called the Kerberos tray, this is a special section of memory as it can’t be swapped out to disk. – Again, seems like a secure process this far! 😎
So, now that the user has the TGT & want’s to request some secured resource, they will send the TGT back to the KDC along with a request stating that it needs a ticket for the protected resource. As the KDC has the key to decrypt the TGT, it doesn’t need to re-authenticate the user, it can decrypt the encrypted portion of the payload right away. Provided that the KDC was able to decrypt the TGT successfully, it can now generate a ticket for the resource & it can then send it to the client.
Now that the client has this new ticket stored in the Kerberos tray, it’s able to send that a copy of this newly generated ticket off to the protected resource. The protected server/resource will use a similar mechanism as mentioned previously, it will use it’s own password to decrypt the encrypted portion of the ticket, if it successfully decrypts the encrypted portion, then it’ll essentially assume that the payload must’ve been generated via the KDC, as the KDC is the only other resource that knows of this password.
… Quite a mouthful! 😂
At Last! The Code!
Before we actually get around to writing any code, you’ll want to do the barebones first, i.e. npm ini, let’s just assume for arguments sake that you’ve already done that. Now that you have your npm project setup, you can install some of the core dependencies using the following snippet:
Next, we need to create some configuration file, i.e. storing the service principle, etc. It’ll mostly be used to complement two of our packages, one being the Kerberos package & the other being the active directory package.
So far it’s pretty simple stuff, however I want to essentially skip ahead a step, because I found that by wrapping these methods up in my own function, it made the overall SSO middleware a lot nicer to work with & a lot nicer to read/maintain. Here I’ve wrapped up the active directory isUserMemberOf & the userExists functions to return promises. This way the overall SSO middleware can just take advantage of using the async/await pattern.
Anyway, here’s my isMemberOf function that is not much more than the isUserMemberOf, plus a promise:
Now here’s the userExists function that I’ve written, again, same idea:
Drum roll please… 🥁 … Now, finally the SSO middleware implementation:
As you can tell, to ensure that everyone is able to make sense of this code, if like myself, you’re not super familiar with the likes of SPNEGO, Kerberos & all that jazzy good stuff, I decided to add a ton of comments.
To clarify, as I’ve mentioned in the code comments, the initial phase to this process requires the server to return a 401 to the client with the WWW-Authenticate header having a value of “Negotiate”, provided that the client didn’t make a request with an Authorization header. This will essentially cause the browser to automatically try & authenticate itself, queue the Kerberos protocol…
The above code worked because my colleague & decided to make use of a keytab file, to clarify a keytab file pretty much consists of principal names & encrypted keys. These encrypted keys are derived from the passwords, as mentioned in the theory section. As many sources online will inform you, keytab files are useful in the case where you want to be able to automatically authenticate with Kerberos.
This hasn’t been an easy process, it required my colleague & I to learn more about how Kerberos actually works & the surrounding approaches to getting this mechanism to work. If you’re going through a similar pain, I can honestly empathise! The documentation is appalling we only came to this approach due to the fact that my colleague was just relentless, he refused to give up! 😂
If I’m being completely honest with you guys, my biggest contribution was mostly tidying up the codebase & adding a bunch of code comments. Though I am happy with the end result considering the initial code base looked like indentation hell, something like this springs to mind:
But in all fairness, it was just a result from a reasonable amount of continuous trial & error.
I will be adding more to this post, eventually… I’ve had a long day & it’s Friday, so I’m gonna chill out for the remainder of this weekend! – Hope you all have a nice weekend! 😁