I've recently started a project at work to replace an on-premise Identity solution, with a cloud based product. It's my first time working on Identity (aka Authentication & Authorisation & more), so there was a lot of learning involved. This is what I wish I could have read at the beginning of January, when I started.
I knew this already, but reassurance is always nice. I'm lucky enough to be surrounded by a super nice, knowledgeable and supportive team.
At the end of the day, for the purpose of what we're doing, there's not that much to it. We'd like to know who a user is before they can use the system, and once we know we'd like to present them with relevant information. All this means, is that once they log in they get a "token" (whatever that is), and we use that to keep the backend in the know of who they are. This is because HTTP requests are by nature stateless, so each interaction is a brand new mystery message from a magical browser to a blessed backend.
At the same time there's a lot of nuance around how you get that token, what you do with it, how often do you check it, and more. There's also tons of processes and business decisions around how users are managed, where, and why.
Luckily, we use tools that are quite mature, so there's not really that much to do, except for putting things together. The complexity lies in using the tools correctly, understanding how your system works now, why it works that way, and how to move it forward.
With that out the way, let's dive into some of the details of how this works, focusing on some of the stuff that I never really thought of, and that confused the hell out of me when I started thinking about.
It's actually fairly basic stuff, that I was mildly embarrassed to not have known already (Imposters Unite!), but until you do it, you don't know it. So here's some stuff that I just did not know a few weeks back, and that I'm now getting my head around.
So you try to go to a website, and before you can use it you have to login, so you get redirected to some login page.
Sure, I experienced that before. But wait, how does that actually work? How do you magically try to go to one place, and end up in another place instead?
Well, in our case, it's all due to some middleware we created (middlewhat? More on that in a minute). The first thing we do when someone tries to get to our app, is check whether they carry a "token" with them. If they don't, we simply ask them politely to go login.
This confused me for a while. What's this "token" thing that they talk of.
I think of a "token" as a temporary nickname. When someone logs in with their username and password, instead of carrying them around with them, they get a nickname by the Identity service. This means that when they come to us, they don't have to send us their username and password constantly, and keep them safe instead. The nickname is only temporary, so less risky.
So when Usually User with a Private Password comes to us, we just ask them to go to other apps and present themselves as Temporarily Tammy. Our system can then check it with the Security team (aka the Identity service), to ensure they know them. If they do, swell, the ID service lets us know they are actually Usually User.
The reason we use Temporarily Tammy is that when they log out, or if they navigate away for too long, we can forget the temporary name, preventing further access to our apps, but still remember Usually User. When they come back, they can get another temporary name, and carry this out again.
Alright then, they get given and nickname, and then send it to us. Wait, how exactly? Segue!
Mmh, delicious delicious cookies. Who isn't familiar with the adorable little pop-ups that adorn our many websites and delight us with joy.
Turns out, they are quite useful for keeping people logged in. I was vaguely familiar with the idea, but didn't really know how it actually worked. Turns out, it's all about them HTTP headers.
The way we ask our users to carry around their nicknames once they log in, is that when we respond to them, we include in the response a special HTTP header that instructs the browser to save some data somewhere, and on subsequent requests to our website, include it as a header.
So, our Identity service replies with a special header called
set-cookie with value
token=TemporarilyTammy, the browser recognises it and makes a note of its value. When the user is sent back to our app, along with the request
GET https://super.duper.com/theBestAppEver, their browser includes a header called
Cookie, with value
token=TemporarilyTammy. When our application receives the request, our app's middleware (that middlestuff again..) looks at the headers that came with the request, finds the cookie, and checks with the Identity service that we know
TemporarilyTammy. If that checks out, we let them through to our application (pending some other extra checks, like whether they can indeed use our application).
So what does all this checks for us before we get to the actual heart of our business logic in the backend?
So our app has some middleware that checks for the presence of a cookie header, and double-checks it with the Identity service. But what's this middleware about anyway?
I found several definitions of middleware, but for the purpose of what we're doing, in our Node.js apps, I think of middleware as a filter. When we ask your app to use some middleware before some endpoint, we're saying to filter all requests coming in through it, before passing them to the endpoint logic. If all is well and the middleware is happy, the request will continues on its journey through our system, otherwise our function will respond back immediately, and the request will never even reach our endpoint.
In our case, it means that our auth middleware filters requests coming in before they can reach the rest of our application logic, or even retrieve files to render in the browser. When a request comes in, if there's no
Cookie header, or its value does not include a
token=value section, that's an automatic no-no. If the
Cookie is there, it then extracts the
token=value and sends the value off to our Identity service. If they don't recognise the value, again, no-no. This means either the token has expired, or it was tampered with. If it is recognised, our function verifies that the user can actually use our app. If they can't, darn. If they can, it's finally time to let our endpoint do its job, or files to flow back to the browser.
In all negative cases, our middleware sends back to the browser a response with status code
403 (respectively, "who dis", and "can't touch this cue music"). The response also includes a handy
location=https://please.login.com header, which instructs the browser or our app to leave the current page and go login.
So there it is, in a nutshell, how we implement logging into our website. There's many other ways of doing it, like JWT (JSON Web Tokens), OAuth, OpenID and more. There's also stuff like caching sessions on the backend to avoid pestering the Identity service every single time a request is made, and discussions about what should actually go in a cookie.
But these are questions and answers that would have definitely helped clear some of the mist in my mind. I hope you'll find it useful too!
I debated with myself whether to include samples of how to implement this in Node.js, or even a full sample repo and write up a walkthrough. Would you find that useful? Let me know!