Previously in this series we have examined what is required on an Access Management side in order to support a micro-services architecture, providing services for authentication, user management, assurance, etc. In this post, we expand the scope, looking at how to enable new services to easily implement access and authorisation appropriately, as well as a discussion about how they can authenticate to each other. Ultimately the creation of a secure system involves security of all parts, not just the access management services which facilitate it, and so this post focuses upon working towards enabling that. Security is also built upon organisational culture, and while it is a little difficult to instil that through a blog post, taking steps to create a technical foundation which allows the Access Management teams to be open and collaborative instead of being the team that says ‘no’ is unlikely hinder such cultural development.
Posts in this series
- Part 1: Overview
- Part 2: Authentication and Authorisation
- Part 3: Advanced Authorisation and Assurance
- Part 4: Enabling Other Teams and Inter-Service Authentication
Access Management enablement for other Development teams
A distributed model for services involves validating tokens issued by the authentication services at each service endpoint. As this needs to be done by every authenticated service, the functionality should ideally be rolled up into a library or SDK in order to avoid continually duplicating this validation implementation. While the term ‘polyglot’ is all the rage with relation to microservices, and is used to imply that the choice of development language is completely open. Practically, for maintainability purposes, most organisations will likely standardise on a small handful of languages. It makes sense then for the team responsible for access management to produce and maintain an SDK for these languages. In practice, this will likely involve some cross-team development for those languages which the Access Management team is unfamiliar with, and will probably be a side-by-side development as part of the first service in that language to uptake the access management services.
The core functionality of the SDK is to automate token validation, and populate an object with the information contained within a user’s JWT token. This enables the developers of the service to then perform authorisation logic. Tools such as JWKs (JSON Web Keys), which can be exposed as Access Management endpoints, enable the SDKs to be dynamically configuring, and the code could be made available centrally, to enable teams who have made enhancements to feed them back into the core SDK.
The core functionality of the SDK is to perform token validation, which has the following key steps:
- Obtain the token public key(s) (probably by invoking a JWK endpoint, possibly after authenticating with the access management services via a Client Credentials flow)
- Intercept incoming calls, pulling the user token from headers or cookies
- Read the token header, to check the signing algorithm
- Validate the token using the algorithm and the keys obtained from the JWK
- Optionally perform some common authorisation (such as validating audience)
- Populate a user object, based upon the token payload
- Pass the user object back to the service for authorisation
In addition to token validation, a number of other capabilities could be incorporated into an SDK, such as initialising authentication flows and exposing callback endpoints, or facilitating acquiring additional authorisation information, etc. In some scenarios, separate SDKs for applications which expose UIs versus those that simply provide service endpoints may make sense.
Authenticating Inter-service Calls
Any micro-services architecture is inevitably going to grow into a system in which services need to invoke each other in order to assemble an appropriate response. An example of this is the authorisation information services discussed earlier. In that scenario, our service which required additional authentication utilised its own client credentials in concert with the user token to obtain information about that user in order, but this is only one of many scenarios and potential approaches.
Some common downstream services, such as logging or submitting application performance data, may not depend upon user context at all, and these can be simply implemented by using an OAuth ‘Client Credentials’ flow. In this scenario, leveraging the ‘aud’ (intended audience) attribute is appropriate. When a client registers itself with the access management services, it submits the intended services that it will consume in this manner, which can then be included in the token issued to that service for use with these common services.
For instance, for a service which wishes to use a common logging service as well as access the access management JWK, the payload of the JWT issued as a result of the Client Credentials flow may look like this (with irrelevant claims omitted for clarity):
{ "sub": <client_id>, "aud": [ "urn:com:example:access:jwk", "https://logging.example.com:8443" ], "client_name": "MyMicroService", ... }
This client token can then be used to invoke the services exposed by the access management services, as well as the logging services (in the example, both a url and Uniform Resource Name (urn) are used. urns may make sense when actual locations are configured dynamically). It is assumed that the authorization decision at the service receiving this token will be something like the following:
- Validate the token signature
- Check ‘aud’ claim matches the identifier of the service being invoked
- (optional) Validate that the client_id given in the ‘sub’ claim is registered to use the service
This last point is tagged as optional, and it would require the service to maintain a local registry of client applications (or invoke a service which provides information about registered clients, micro-services architecture et. al). In some scenarios, this is sensible, a central logging system probably needs to know about which applications are logging to them, which log parser should be used, etc. In other scenarios it likely isn’t required, and a validated client is fine. There is no need to maintain a client registry; it just provides slightly more control.
Using the ‘aud’ claim for these common services allows for the use of simple Client Credentials flows for service-to-service authorization when user information isn’t required which can incorporate a least-privilege approach, preventing a compromised client credential from being used to invoke internal service for which they have not registered interest, and can act as part of a larger service governance solution, by providing a representation of which services utilize which common services.
In user-centric scenarios, which require information about the user in order to perform authorisation, re-using the user JWT by passing it to downstream services is a relatively simple approach. Doing so does however risk effectively creating a ‘master-key’ for internal services, so care needs to be taken when requesting initial scopes, and ensure that all services which are forwarded the user JWT are first-party services which can be trusted.
In the event that a downstream service needs to invoke a third-party service with user context, that service should exchange the internal user JWT for a JWT intended solely for that third party. The process for doing so is covered by RFC7523, which basically provides a mechanism by which to use an existing JWT as a claim as part of a token request.
Under RFC7523, a token request looks like this:
POST .../tokens Headers: Authorization: Bearer <service client token> OR Basic <service client credentials> Content-Type: application/x-www-form-urlencoded Body: grant_type= urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<user JWT>&scope=<scopes>
According to the spec, Client authentication in this flow is optional, but due to how OAuth is typically implemented, here it serves an important role, as clients are associated with audiences. By utilising a specific client_id which has been assigned for use when contacting third-parties, the subsequently issued JWT can have an ‘aud’ claim which only includes the third-party, preventing it from being used with internal services. Depending upon the flexibility of the OAuth services, this could deviate slightly from the spec and allow for the use of an additional parameter such as ‘audience’ or ‘token_purpose’.
Throughout the posts in this series, I have tried to capture the abundance of approaches, thoughts and painful discoveries that I have encountered during conversations and engagements with customers on many different paths. Some are attempting access management transformation, others reforming their development approaches across their organisation, others uptaking a wealth of cloud services, etc. The strategies discussed in these posts help, I hope, enable organisations to move more rapidly, to develop new services, uptake offerings from distributed service providers and ensure security, even as traditional perimeters dissolve.
This dissolution of perimeters ought to be liberating for both access management teams and those who are developing services which require access management. The teams responsible for access management no longer need to be swamped exposing resources and defining authorisation policies, and those teams developing new services no longer have to deal with the security team being a team of ‘no’. Service developers need to have a slightly better idea of security approachesin this new world, as they can’t just rely upon the perimeter. That said, those developers gain complete control and independence from the access management teams, allowing them to move faster and implement authorisation in a model that works for them. In this post, we also discussed how resources supplied by the access management team should limit the required security knowledge of the service developers to conceptual, rather than technical.
The core concept of micro-services is to modularise services around business capabilities; Access Management represents a core business capability in and of itself. As a result, it ought to be delivered as a set of services, and once the core capabilities are in place, be wrapped in the same development approaches as any other service, extended independently as new business requirements emerge, and facilitating a much wider network of services to be operated securely. I hope that this series of posts have provided appropriate insight into how this can be achieved, even if, despite requiring splitting into four, they still only managed to capture a very high-level view of the overall architecture and approaches.
3 thoughts on “Access Management and Micro-services – Part 4: Enabling Other Teams and Inter-Service Authentication”