In the not so distant past we were planning to move towards microservice architecture and one of the key aspects was how to secure microservices effectively. Read from this post why we ended up using JWT tokens in our microservices.
From the beginning, it was clear that each microservice must authenticate the user when microservice’s resource gets accessed by the user. Also user’s roles etc. may be checked to verify that user can perform requested operation or that user has privileges to see specific resources.
Especially preventing user to see dedicated data must always take place in microservice itself which owns the data, not in the UI or in some other calling entity. For example, the user may request and see only his/her own organization-specific data and data belonging to other organization must not be returned even if asked for.
Why it’s so important to restrict data access in the owning microservice? If you allow clients to access all the data, it becomes hard to restrict the access later in the call chain. You can easily forget to perform the filtering and probably filtering logic gets duplicated across the system.
We need to use OAuth2 authentication as we already had authentication server set up for other applications in the system. Existing OAuth2 server used the default approach and provided session tokens which then were stored in cookies. The client was expected to send session token with each request from the cookie.
This approach works perfectly fine also in microservice architecture, but having sessions and cookies conflicts with the basic idea of microservices… they should be stateless.
Also, another drawback when using session tokens is that each time when microservice receives a request, it only gets session token without any information about user performing the request. Thus the microservice needs to ask user’s details from the authentication server. When requests are flowing through microservices, each service generates a request to the authentication server. This creates overhead to microservice-microservice communication.
We ended up using JWT tokens which are a self-contained way to transmit information about the user between parties as JSON objects. Because we already had OAuth2 server running in production and we didn’t want to change its behavior (and thus change also other services relying on that server), we ended up to implement proxy service which used the session token to fetch user’s details and then created JWT token from that information. This way we had clear migration path between our old and new services which used OAuth2 as SSO.
As stated before, JWT tokens are self-contained. Thus they contain all necessary data to authenticate and authorize the user. In our case, we store username, email and system-wide roles to JWT token. In the future, we could add more data to the token, for example, organization information etc.
You can read more detailed information about tokens from JWT token homepage, but in short, the token is just JSON object with one part containing header information, then the payload and finally the signature. The payload contains claims about an entity which usually is a user. There are couple reserved claims, but you can define your own claims easily, just add a new key to JSON object.
The payload is Base64 encoded so it can contain almost any data, but you should keep in mind that token gets transmitted on every request so it’s a good idea to keep token as small as possible. Web servers usually have some limit for request sizes, especially for GET-requests.
Because token is just encoded JSON object, anyone could alter it and make receiving party think that you’re someone else. This is prevented by adding a signature part to the token. After encoded header and payload have been formed, those are signed using secret and that signature will be set as third part of the token.
Receiving end will check the signature and if headers or payload have been altered, it will be detected and the request can be discarded.
Digital signing can be performed with either shared secret (like a password), which must be identical in both sending and in receiving end or signing can be done with public/private keys.
JWT tokens are usually stored locally (for example local storage in the browser) and they’re attached to each request which needs to access protected resource. Usually, the token is being sent in
Authorization-header of the request using Bearer-schema:
Authorization: Bearer <token>
And that’s how simple it is!
Because there is no session when using JWT tokens, there is also no “logging out”. As long as the client has valid JWT token, it can be used to access secured resources. Closest thing to logging the user out is to remove JWT token from local storage etc. but this doesn’t, of course, prevent user to copy JWT token and use it later.
You can set the validity period for JWT token and it should be such period which you’re comfortable to allow the user to perform operations without requiring re-login. In our case, tokens are valid for one hour.
One of the key benefits of using JWT tokens is that creating automated integration tests for services is simple. You can easily generate different types of JWT tokens, valid or invalid, and test that your service responds as intended. There are no third party authentication services etc. which needs to be mocked or controlled.
In our project, we perform security testing by generating JWT tokens which either have required roles or don’t and test that secured endpoints are restricting user’s access as intended. We also check that each endpoint checks the validity of JWT token and requests without valid token are discarded.
So we made a considered decision that it’s possible to alter JWT token which is stored in browser’s local storage and thus the user would be able to see sections in UI which are restricted to only certain groups of users, like admins. But because each request to the backend is secured, the user cannot fetch any data or perform any actions using tampered JWT token.
This is something that we haven’t yet addressed properly. One solution might be that we store our application’s state to local storage so the user can continue working from where he/she left off.
At least at the moment, it seems that JWT tokens have been a valid decision for securing our microservices. They provide scalable and stateless authentication method which is not tied to any specific authentication source. As long as you have a valid token, you’re good to go.
Also, testing is straightforward and we don’t need any special arrangements to test our services’ security.
Some problems exist which mainly concern user flow in UI and how user experiences re-authentication.
See also the follow-up post: JWT tokens revisited.
See what we have to offer - Careers