One of the least glamorous aspects of implementing a Microservices architecture is the security. It’s not fun or cool when compared to things like the circuit breaker or service discovery, yet it is a critical piece of the ecosystem especially in an enterprise setting.
I’m working on a large Microservices project for a healthcare enterprise on the East Coast. One of the first pieces of the infrastructure we assisted with was security, which has turned out to be a lifesaver for everything that has come after it. I was able to see what security works well as well as what does not work so well in a Microservices environment. In this blog post, I will share a medium to high-level look into how security can be implemented in Microservices.
First things first: what does Security actually mean? Most enterprises have three discrete aspects of that go under the umbrella of security: Authentication, Authorization, and Access.
Authentication answers the question “does this user have valid credentials to access the system?”
Authorization answers the question “does this authenticated user have permission to perform this action?”
Access answers the question “does this authenticate and authorized user have permission to perform this action on this entity?”
In this blog post, we’ll start with Authentication, then move to Authorization, then close with Access.
In general, the API Gateway is the best place to validate Authentication as well as enforce Authorization. This allows you to ensure that anything that goes from the edge into the ecosystem is correctly Authenticated.
In the Java world, the out-of-the-box setup for Spring Security OAuth2 will have you set up one microservice that you call to obtain an API token. By virtue of it being an OAuth2 client, your API Gateway then has a servlet filter when it receives a request to validate the API token against the OAuth2 microservice.
At my client, we didn’t want that extra cost to call the Authentication microservice each time you hit the API Gateway. So instead, we injected a secret encryption string at deploy time for both the API Gateway as well as the Authentication microservice. Each environment (DEV, QA, etc) has its own secret encryption string. This has worked really well for us, though I’m not sure how to set it up in Spring Security as my client is a .NET enterprise.
When the Authentication service produces an API token, we embed information in it which is used for Authorization purposes. (More on what that information is later.) This has the side effect of making our API tokens somewhat long, so you do have to keep in mind your application server’s default limit on HTTP Header lengths. The bonus of doing that is we no longer need a session to store that same information, and it’s securely transferred by virtue of the encryption key shared at deploy time.
This has saved us from developers hacking functionality together by storing it in the session (we’ve all done it in our career), and makes it super easy to scale the services as load balancing does not need to worry about sticky sessions.
One thing we also embed in the API token is information on when it expires. Once the API Gateway receives the token, it decrypts it, which is the Authorization check as only the Authorization microservice could have issued that token. Then we validate that the token has not expired.
This took me some getting used to, but with microservices (as I see in the comments below), the annotation for the valid Authentication requirements is level.done at the API Gateway level rather than at the microservice’s controller/method level.
There are two Authorization schemes you can pick from. What most people know is more Role-based Authorization. With this scheme, the endpoint defines the roles.
As an example: the
DELETE /user/:userId API endpoint requires the SuperAdministrator role while
GET /user/favoriteCereal just requires a User role. I would recommend this if you have less than a handful of roles, and their access rarely changes.
Thus, the information you would store in your API token (JWT works well for those, some of our other clients use those) would be an array of role names. Spring Security has support for Role-based Authorization out of the box.
Unfortunately, most of our mid to large clients have a salad bar of roles whose permission to APIs is constantly mutating, and they would kill us if changing permission required a JAR redeployment. These clients instead require Claims-based Authorization. (The role defines the endpoints.)
As an example: The SystemAdministrator role has
* /user/*, the User has
The information you would store in the API token would be whatever information is easiest/fastest for your API Gateway to digest to determine if the decrypted token has the right gusto to execute that call.
Unfortunately I do not think this is built-in to Spring Security. Implementing this for us is probably Q3 or Q4 2017, so I’ll know more then at least from a C# perspective.
Once the API token’s Authorization has been confirmed, you need to pass it along to the microservice that your API Gateway will be invoking. This is necessary so that you can populate fields in your database like
CreatedByUserID and such. You have the option of passing the decrypted JWT to the microservice, which is probably fine.
If you want to be extra secure, you should inject your API Gateway and microservices with their own encryption key at deploy time. This is different than the one that the API Gateway and Authentication microservice share. Or keep them the same. It just comes down to what the company’s security department thinks is best.
The Authorization I’ve talked about so far has really just pertained to executing an API call. In terms of answering “does the entity whose making this request have the ability to perform an action X on a record in the database Y?”, the best way to do that is with an Access Control List (ACL) in the database record for Y.
Larger clients require SSL certs and keys to be rotated on a regular basis. This can cause issues with microservices as there is a lot more you have to deploy if you inject an encryption key at deploy time like I’m suggesting.
You can either do Blue/Green deployments, or have it be an array of encryption keys it validates against. You redeploy a month before one of the keys expires, and include the new key in the API Gateway first. Then later you add the new key to the Authentication microservice. Then when the old key expires, you can remove it from the Authentication microservice first, then the API Gateway after a grace period. If you embed the expiration time in the token, your grace period would need to be at least that length.
The last thing you need to consider with the API token is how you want to handle the expiration. Microsoft’s strategy is to issue essentially two tokens: an API token, and a Refresh token.
The API token is what I have been talking about in this blog, and is used to perform actions on an API.
A Refresh token can only be used to obtain a fresh API token without having to re-authenticate with the Authentication microservice. This is useful if your API token expires every 20 minutes, and you don’t want the end user to put in their password again each time it expires. The Refresh token’s expiration time is generally 2x or 3x the expiration time of the API token.
Lastly, as is true of anything around security, minimize the amount of code your team writes, and maximize the amount of code you can leverage from libraries that are bullet proof. Security is so critical that in most cases you want to trust code written by companies that are in the business of security.