I'm not sure if this is the right site as this question sits under both software engineering and security domains.
Assume you have a financial web application that receives sensitive requests from its users (e.g. money transfers) and you want your application to be able to process sensitive operations only if they were originated by the client. Your application also has a "back-office" interface, which allows specific employees to perform bulk operations such as a promotional giveaway.
Your architecture is based on micro-services with loosely coupled design principles in mind, hosted on a public cloud (e.g. AWS, GCP, Azure). Some of the services are frontends, some are backends. The communication method is varied, some utilize REST, some utilize message queues.
You decide you want to embrace the defense-in-depth approach by applying multiple trust boundaries instead of a single perimeter to avoid a security single point of failure (having one of the services breached to bypass all security boundaries inside the network). With that approach, each service authenticates its consumers, checks their authorization scope, validates their supplied input, and so forth.
The actual question:
How can a complete backend service be sure that a request, along with its data was initiated by a client, while not having an increased coupling/being as agnostic as possible between the service and the client?
Alternatively, how trust can be achieved between the service and its consumer?
Things I have considered so far:
- Client-side Digital Signature: the client digitally signs the payload, where the private key is only known to the client. The service can verify the integrity of the message before execution (signed message is passed the whole way)
- Provides the highest degree of data integrity and non-repudiation
- Backend service can be designed in a way of message validation, which is relatively not that coupled (request can be either signed by a customer, or by the back-office interface operator)
- Key management on the client-side has many problems
- The backend service would still need to map the verification key to the request initiator
- Cannot support bulk operations unless requests are stacked along with their signatures (increases used storage)
- Side-channel Verification: before final execution, a separated channel reaches to the client to confirm the details of the transaction
- Adds a service that handles just that without a significant modification of code on the rest of the services
- Avoids having key materials stored on the client-side
- Requires an additional "moving part" that might break the system if malfunctioned
- Does not provide non-repudiation
- Friction with the client as she needs to be available to confirm the operation twice
- If the client is not available it would not be possible to process the request