Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

If you want a good presentation on OAuth 2.0, I recommend the SpringDeveloper video "Securing RESTful Web Services with OAuth2". It's long and dry, but it goes into far more detail on the precise server interactions and deployment options than other videos I've seen. Many presentations go for a 20 minute overview of how great OAuth 2.0 is as a client, without touching on any of the out-of-spec topics you need to actually build a useful system when you're a provider.

...

The Client is required to treat the token as opaque, but the RS and AS can agree to whatever conventions they want. Another common approach is to make the access token a JSON Web Token (JWT). JWTs are simple, digitally signed, base64 encoded JSON structures. The Analytics API would inspect the token, verify the signature, and then just trust whatever group/permission/scope/user info comes with the token. The plus side is that this removes the need for the RS to call the AS at all, meaning there is one less network hop and the various services are more resilient to transient failures in the AS. The disadvantage is that token invalidation gets a little more complicated, and services may see outdated scope information.

...

I believe that using a period for namespacing makes a lot of sense for us, in the style of Microsoft or Facebook (e.g. xqueue.submit). Google uses fully qualified URLs as scope names, which seems like overkill. One interesting idea is the Facebook example of dynamic scopes. So I could say xqueue.read_course:course-v1:edX+100+2014A or xqueue.read_problem:peerassessment. While that might have possibilities, most companies have restrict themselves to a very limited number of scopes. GitHub has around 20 of them. Google has many, many more, but each individual service usually only has 2-3 (e.g. "read", "write"). Which brings us to the next point...

...

  1. LMS makes request to XBUS for the problem state history for user jarvis and sends XBUS a signed JWT access token that it got from the AS.
  2. XBUS inspects and verifies the token, and sees that the user is carlos and one of the scopes is xbus.read.
  3. At this point, if carlos was accessing his own state, XBUS could reply "sure, here you go". But carlos is asking for jarvis's data, so XBUS needs to know whether carlos is a course staff, edX admin, etc.

At this point, there are a few couple of plausible strategies that come to mind:

  1. We have a separate service that determines whether that XBUS can call that will return the various roles a user has in a given course (e.g. "student", "staff", "beta-tester"). Based on that information, XBUS makes its own determination as to whether carlos should be allowed to see jarvis's information.
  2. We store permissions in a more centralized manner. XBUS declares that it has some parameterized permission read_user_state_course (whether that's dynamic, or we use some naming convention), and it asks a central authorization service whether carlos has that permission for this course.

Option #2 theoretically gives us more flexibility, but what little I found discussing this (an O'Reilly microservices book, a Quora post, a video presentation) describes having a centralized permissions system as extremely painful. Some quotes:

"This is a nightmare to maintain and gives very little scope for our services to have their own independent lifecycle, as suddenly a chunk of information about how a service behaves lives elsewhere, perhaps in a system managed by a different part of the organization."

"These decisions need to be local to the microservice in question."

"First, because of the complexity, services must manage their own access control. Don't try to build a central service that says "yes" or "no" to a ton of requests for different services. That's a dark, dark road to go down. Instead, ensure that a request to a service contains the requisite information required to determine if it should be answered. This leads to .. 

Second, try to have a uniform way to document and express the security requirements and responses for each service, ideally a uniform protocol so that you can debug the security interactions between two services that are not related. For example, always passing through the user ID and their group IDs in the request header. Whatever it takes.

Finally, keep separate services truly separate: don't have a central database that stores all of the permissions and is utilized by different services, or you'll be right back at my first point ... unless you're still at a point where a shared data store is scaling just fine for you, which brings me to ..."

The service code should still express things in terms of permissions internally, but the mapping of roles to permissions should happen at the service level.