authentication, or, the gaps where management should be
By stp on Jun 14, 2008
Until then, I wanted to talk about an issue that's come up in several forms recently in the forums. Namely, how authentication works, how the client protocol affects input to authentication, and how this connects (or is supposed to connect) to external components. This is actually something that's been discussed in several threads before, but I don't think that all the issues are collected in any one place. So, here's my first attempt at trying to herd all the different discussions together. Please let me know what you think, or if you've got suggestions about how to proceed!
To start, here's some quick background. When we were designing the core components for Darkstar we knew that authentication would be important. So too is account management, billing, etc. All of these really fall into a larger topic of management outside the Darkstar stack itself. This is an important problem, but not one that we're trying to solve; third parties and existing systems already do this really well. So, we wanted a simple point for connecting with the management infrastructure without having to actually address the implementation since we were (well, still are) focusing on the core of our stack. We also wanted something flexible, so that any system could be used but would still work well with a Darkstar application.
Since I'm the "security guy" on the team, I took a crack at this problem, and what we ended up with is modeled on some of the more successful and useful systems I've played with. The idea is to have an abstract identifier for a given user which tags all the work they're doing, but separates out the enforcement, policy, and identity/account management from the application code. This makes it easy to plugin different management systems, and swap different authentication mechanisms, while application developers don't have to do anything different or adapt their code to the underlying infrastructure. More importantly, it provides a clear separation point for stuff that's part of the Darkstar stack and services that need to be separate. More on both these points in a little bit.
At the same time that we were thinking about these issues, we were thinking about a connected issue: the protocol between the client and the server. If you've looked at the current protocol at all, you know that we were trying to create something extremely simple so that any language or platform could be supported on the client-side. We also knew that we couldn't get all the right features into the protocol on our first try, and that different systems would require different protocol features, so we planned to support the same notion of a pluggable protocol stack that the EA release had. That is, you can swap in any protocol implementation you like, depending on what you need carried between the client and server. With this as a goal, we punted on any kind of flexible authentication mechanism in the default protocol and supplied space for a username and password only. Of course, we still haven't implemented the flexible protocol interfaces yet, but that's in the plan.
So, to support authentication today, the Darkstar stack has an
IdentityAuthenticator interface. It gets called when a client connects to the system, and is provided an instance of
IdentityCredentials. If authentication is successful, the authenticator returns an instance of
Identity used to represent the connected client. That's it. This is the point that (in theory) ties together all the details I've been hand-waving about to this point. [Note that because there's only one possible protocol right now, there's only one possible type of credentials (name-password credentials), but when new protocols can be plugged in they could be designed to provide alternate types of credentials.]
The idea here is that, for a given infrastructure, an authenticator can be implemented. Swapping in or adding a different authenticators (you can actually have any number of authenticators, ordered based on how you want to prioritize authentication mechanisms) won't affect the application code. Because these authenticators are not part of the application, they aren't invoked in a transaction. This means you can't get at the
AppContext and its associated
Managers, but you can interact with any services outside the system. This was an intentional design, since most developers I've talked with want to store things like passwords, account details, etc. in something like a MySQL instance that they can manage separately from the Darkstar datastore. Really, the datastore management is an application issue, so if you want to have account detail there, that is something that should be done in an application-specific way within your application code (or at the
Service-level, as discussed in a bit).
Among other things, this makes it easy to think about having different kinds of clients that are authenticated in different ways. For instance, an admin tool that is really a darkstar client could connect and be authenticated via a completely separate database or set of credentials. Conversely, users who can login to multiple games can easily be authenticated against a single set of credentials. You get a lot of flexibility here.
Ok. We all good so far? To re-cap, there's a simple notion of an authenticator that gets called when a client tries to login. It's given some credentials, and asked to validate them. This is the connection point to external management of identity, account detail, etc. This is abstracted from the details of the application and the protocol, although the protocol implementation dictates what kinds of credentials will be provided. If the client is successfully authenticated, then an Identity is provided that represents the client.
This is the point where some confusion tends to arise. What is this Identity, and who is supposed to use it? Why is it hidden from the application and is it a good way to share your external data with the game itself?
Abstractly, we use notions of Identity throughout the system. All tasks and transactions are associated with an owning identity. For instance, when a message arrives from a client, the transaction run to handle that message is owned by the identity associated with the client that sent the message. We can look at the identity to make scheduling decisions, to order events and messages, and to provide detailed per-client profiling data. In the multi-node system, we use Identity as the key for doing load-balancing and clustering, assigning each Identity a home node, and then trying to do all work for that Identity on that node. The application itself has an Identity as well, which is what owns tasks that aren't running on behalf of anyone specific. So, this Identity abstraction, while pretty simple, is a key concept throughout the stack.
The Identity implementation serves another purpose, however, and that's to be a link back to the infrastructure that created it. The idea is that if you're managing (for instance) account details about a given player outside the stack, you can use the Identity as a way to get back to that detail, either by including it directly in the Identity instance, or implementing the Identity returned from your authenticator to have accessor methods that can call out to your external system.
In order to keep all these things abstract from the application code, and to make sure that you don't do things like call an external service directly from your application code, the Identity itself is hidden from the application. If you want to access any custom features, this should be done via a Service (which can see the Identity) and a Manager (which is the bridge between your application code and the Service). This also acts as another abstraction point, so that again the actual Identity implementation can change without the Manager interface to the application code being affected. Of course we don't want everyone who writes an application to have to write this kind of detailed code, so why does this design make sense? Well, this finally brings me back to the title of this entry.
When we designed all of this, we never intended that application developers would have to write any authentication code. I mean, obviously in the short-term everyone will have to, 'cause you need something that verifies clients. But in the long-term, it's our hope that some number of third-party developers and community groups will build authentication and account management software for Darkstar. This can be developed completely separate from any specific game implementation. It would mean that game developers can focus on their application logic, and then just use whatever authentication mechanism they want. The infrastructure developers, in turn, can write detailed connections between the stack and their infrastructure, and decide what and how to expose to game developers.
The catch here, of course, is that this is something of a boot-strapping problem. Until we have serious games, there won't be too much development on tools. And until there are good tools, it's hard to write good games. So, for the moment, there's some serious pain for the bleeding-edge game developers who are trying to actually use this component of our system. Sorry about that. I've worked with a lot of security systems, and I know how how maddening it can be to do development without the kind of support that they require. All I can say is that we're moving as quickly as we can. In the meantime, I'm more than happy to help support y'all in your development efforts, and of course if anyone wants to propose some community projects for generalized management in this space, I'd be more than happy to act as a contributor and advisor!
Is that the end of the story? Well, not really, If you've paying attention, you may have noticed that I glossed over one key issue. Every task in our system has an associated Identity, and this is how we do load-balancing, resource-management, etc. This makes sense for clients, and for the tasks that the application itself has to run (initialization, maintenance, etc.). This does not, however, address things like NPCs, which are also "identities" in the system, just without a human (usually) driving them. Why do they get the short shrift here?
The answer is that they shouldn't, we just haven't gotten organized on this point yet. Ultimately, we want to expose some mechanism to the application code for defining new entities in the system that should have their own (little-i) identity. We don't know exactly what this will look like, and a lot of it has to do with how we handle movement through the cluster, something we're still learning as we go. When we address this issue, it may result in more explicit notions of Identity being exposed to the application, but nothing is firm here. If you have thoughts, please let us know!
One quick aside. Several folks have asked why there's no API to get the Identity associated with a given ClientSession at the
Service level. This is a totally valid question There probably should be some way to do this, and I expect that we'll add this in some form as we make some of the changes discussed above.
Whew. Sorry for the long caffeine-powered discussion, but given all the recent questions I figured it would be useful to try laying out all these issues in one place. I hope this helps provide some insight into what's going on, and where we're heading. I would definitely like to hear questions and suggestions about all of this, especially if you've been implementing against these APIs and have any good/bad/other experiences to report. The discussions have been great thus far, and very useful; please keep it going!