Our site remains ad-free thanks to Linode. See if Linode works for you with $100 in credit. Accelerate innovation with simple, affordable, and accessible Linux cloud solutions and services. See why developers around the world trust Linode.
The post is not sponsored by the above affiliate and the content here is not representative of their company.
Starting with microservice design, it’s easy to follow your old habits of designing each microservice as if it was a normal application. One aspect of this, that I fell into when first moving to microservice applications, was adding authentication to my microservices that were only accessible from internal systems.
The level in which you secure your microservices is dependent on your infrastructure, who will be using the microservices, and the availability of the microservice. Not all microservices need authentication. The simplest example to point to is a memcache server. When your service is running internally and is only used by your applications, authentication may not be required. The same goes for transport encryption, but I usually implement TLS in the unlikely case of routing errors and because most HTTP libraries will have support for it already.
Microservice Authentication is simply the process where requests to your microservice contain some form of authentication. It’s similar to how websites will store a session cookie to authenticate the user in the future. There are a few different ways to go about authentication, some take longer to implement but have more strength in ensuring the request is valid and hasn’t been modified.
When to Implement Authentication
The big question is, when do you need to implement authentication for your microservices? If the service is going to only be accessible internally and not by outside vendors or parties, you likely don’t need authentication. In some cases, you may not have a private IP address if your server is on a different provider. You could implement SSH tunneling to access the microservice without exposing any ports to the public, or you could add an authentication method. Technically, both options are authentication, one is just handled through SSH and the other through your microservice or reverse proxy.
Cases where you need authentication include some of the below. There are likely cases outside of this scope. Use your best judgement. There is essentially one reason to not sure authentication (internal service not accessible from the outside), every other case needs it.
Publicly available service.
Vendors need access to the microservice. Service has to run on a public port. Allow third-party developers to use the microservice. Limit what internal services can access the microservice.
Keep in mind, adding authentication to your microservice doesn’t always require modification to your microservice code. In most cases, it’s better to keep the authentication separate to keep with the microservice design structure.
Below are a few different ways to implement authentication. These can be added directly to the microservice, which may ruin the microservice design pattern. Alternatively, you can create a new microservice that only handles authentication that then calls the final microservice, acting as a reverse proxy. Essentially, after you authenticate the request, you pass the request along to the final microservice and then relay the response.
This is my favorite method for authentication. Using HMAC signatures, you can create a hash from the URI, headers, and body, which prevents the request from being modified in transit unless the shared secret has been compromised. This method gives extra assurance that the request has not been modified, even over unencrypted connections.
HMAC signatures works by having the provider (your microservice) create a public and shared private key (not the same as asymmetric cryptography). The client will never send the shared private key, but uses it to create a hash with HMAC to sign the request. The public key is passed along with all requests to identify the client.
Below is a basic example of how you would generate the HMAC.
Credential = API public key HTTP-Verb = GET|POST|PUT|DELETE RequestPayload = Body of HTTP request Headers = Header [ + "\n" + Headers ] Header = SignedHeader: <value> SignedHeader = Host|Date|Content-Type Signature = Base64(HMAC-SHA256(SecretKey, StringToSign)) StringToSign = HTTP-Verb + "\n" + HTTP-Resource + "\n" + Headers + "\n + Base64(SHA256(RequestPayLoad))
The above will encode the request, a few headers (importantly the Date header), and the body of the request. The date header is important to prevent old requests from being replayed. To fully prevent replayed requests, you can add an additional
nonce header and add it to the signature.
GET / HTTP/1.1 Date: Wed, 01 Jan 2020 12:00:30 GMT Authorization: HMAC-256 Credential=<public_key>,Signature=<signature>
The signature will then be added to the request headers. Be sure you don’t add the
authorization header as one of the headers that’s part of the signature, or else you run into a chicken and egg problem.
- Ensures the request hasn’t been modified.
- Scales well to many clients.
- Secret key isn’t sent over the connection.
- Slightly more difficult to implement.
API keys are a simple way to identify clients that are accessing microservices and to add rate limits and any other client based metrics and limits. API keys are easy for clients to implement since the client only needs to append the key as a header or parameter passed to your service.
GET /api/stats HTTP/1.1 X-API-Key: <key> GET /api/stats?key=<key> HTTP/1.1
It’s also possible to implement the API key into the existing
Authorization header as long as you change the type from
Basic to something unique.
GET /api/stats HTTP/1.1 Authorization: Token <key>
Using this method lacks the ability to verify that the request wasn’t modified unless you’re using TLS. It’s also important that the request does use TLS because the API key must be treated as a password.
- Easy for clients to implement.
- Scales well and is simple to revoke and add new clients.
- Requires encrypted connection as the API key is treated like a password.
- Doesn’t validate the request to check if it’s been modified in transit.
HTTP Basic Authentication
This is the easiest implementation and your reverse proxy can already handle it for simple authentication. HTTP Basic Authentication uses the single header
Authorization with a base64 encoding of the username and password, separated by a colon. Your reverse proxy can generally use a user and password database file and handle the authentication for you.
GET /api/stats HTTP/1.1 Authorization: Basic <base64>
Using this method it’s important that you also use an encrypted connection since the password is transported in plain text.
- Very simple to set up for basic usage.
- No additional software or programming required.
- Well defined authentication method.
- Doesn’t scale well and has limited control.
- Username and password are not encrypted.
- Doesn’t validate the request to check if it’s been modified in transit.
Public Access Authentication
In the case that your microservice is an API you want exposed to the public, there are a few ways to go about authentication and authorization. With a publicly available service, you likely want to limit how many connections can be made per client, their quotas, and who is allowed to connect to the API.
Setting up a reverse proxy that handles authentication keeps with the microservice model. Instead of adding code to each of your microservices to handle authentication, you will implement an authentication program that will then reverse proxy the request to the designated microservice.
This has the added benefit of being able to easily setup access control lists for your microservices.