Network-level access controls for legacy applications
Apply network-level access controls to legacy applications by hosting the application within a restricted subnet and/or applying IP allow-listing to prevent arbitrary users from interacting with public-facing legacy applications from arbitrary hosts. In some circumstances the application may be required to run in an air-gapped environment.
Disable high-risk functionalities in legacy applications
Authorization controls should be considered at a granular level by reducing the feature set available to end users. Disable certain high risk functionalities, particularly administrative functionalities, in legacy applications.
MCP Authentication, Authorization & Transport Security controls
Enforce authentication on all remote MCP server endpoints. Use OAuth 2.0 with PKCE for remote server authorization flows. Bind session IDs to user-specific context (e.g., <user_id>:<session_id>) to prevent session hijacking. Validate on each request that the session or token belongs to the current requester; reject the call if it does not to prevent confused deputy attacks. Use secure, non-deterministic session IDs generated via cryptographic random sources, not sequential. Always use TLS for remote HTTP/SSE transports. Verify server identity via certificate pinning or cryptographic server verification. Bind MCP HTTP/SSE servers to specific interfaces like 127.0.0.1, never 0.0.0.0 unless explicitly required. Validate the Host header on every incoming request and reject requests with unexpected hostnames.
Always log authorization (access control) failures
Access control failures must always be logged as part of security event logging requirements.
Edge-level authorization with API gateway
Authorization can be centralized at the API gateway to enforce access control for all downstream microservices. The API gateway can eliminate the need to provide authentication and access control for each individual service. NIST recommends implementing mitigating controls such as mutual authentication to prevent direct, anonymous connections to internal services and bypass of the API gateway.
Limitations of edge-level authorization
Edge-level authorization at the API gateway has three key limitations: (1) Authorization decisions can quickly become hard to manage in complex ecosystems with many roles and access control rules; (2) The API gateway may become a single point of decision that violates the defense in depth principle; (3) Operation teams typically own the API gateway, so development teams cannot directly make authorization changes, slowing down velocity due to additional communication and process overhead.
Defense in depth authorization implementation
In most cases, development teams implement authorization in both places: at the edge level at a coarse level of granularity, and at the service level. To authenticate external entities, the edge can use access tokens (referenced token or self-contained token) transmitted via HTTP headers such as Cookie or Authorization, or use mTLS.
NIST access control framework components
NIST SP 800-162 defines four functional components of an access control system: (1) Policy Administration Point (PAP) - provides user interface for creating, managing, testing, and debugging access control rules; (2) Policy Decision Point (PDP) - computes access decisions by evaluating applicable access control policy; (3) Policy Enforcement Point (PEP) - enforces policy decisions in response to access requests; (4) Policy Information Point (PIP) - serves as retrieval source of attributes or data required for policy evaluation.
Decentralized authorization pattern
Development teams implement PDP and PEP directly at the microservice code level. All access control rules and attributes are defined and stored on each microservice. When a microservice receives a request with authorization metadata (e.g., end user context or requested resource ID), the microservice analyzes it to generate an access control policy decision and then enforces authorization. Implementing authorization at source code level means code must be updated whenever authorization logic changes. Spring Security allows developers to enable scopes checking using scopes extracted from incoming JWT in the resource server.
Centralized pattern with single policy decision point
Access control rules are defined, stored, and evaluated centrally. Rules are defined using PAP and delivered to a centralized PDP along with required attributes. When a microservice receives a request, the microservice invokes the centralized PDP via network call, and the PDP generates an access control policy decision by evaluating the query against access control rules and attributes. The microservice then enforces authorization based on the PDP decision. This pattern can cause latency issues due to additional network calls to the remote PDP endpoint, but this can be mitigated by caching authorization policy decisions at the microservice level. The PDP must be operated in high-availability mode to prevent resilience and availability issues.
Centralized pattern with embedded policy decision point
Access control rules are defined centrally but stored and evaluated at the microservice level. Rules are defined using PAP and delivered to an embedded PDP along with required attributes. When a microservice receives a request, the microservice invokes the PDP to generate an access control policy decision by evaluating the query against access control rules and attributes. The PDP can be implemented as a microservice built-in library or sidecar in a service mesh architecture. Embedded PDP should be deployed on the same host as the microservice to minimize external dependencies and network latency. Embedded PDP usually stores authorization policy and policy-related data in-memory. The key difference from centralized single PDP is that authorization decisions do not store on the microservice side; instead, up-to-date authorization policy is stored on the microservice side. Caching authorization decisions may lead to applying outdated authorization rules and access control violations.
Recommended authorization pattern
The recommended pattern for service-level authorization is Centralized pattern with embedded PDP due to its resilience and wide adoption. To achieve scalability, it is not advisable to hardcode authorization policy in source code but to use a special language to express policy instead, externalizing authorization from code.
Authorization solution platform requirements
The authorization solution should be a platform-level solution with a dedicated team (e.g., Platform security team) accountable for development and operation of the authorization solution as well as sharing microservice blueprint, library, and components that implement authorization among development teams.
Authorization solution should use widely-used solutions
The authorization solution should be based on widely-used solutions because implementing a custom solution has cons: (1) Security or engineering teams have to build and maintain a custom solution; (2) Client library SDKs must be built and maintained for every language used in the system architecture; (3) Every developer must be trained on custom authorization service API and integration, and there is no open-source community to source information from.
Defense in depth authorization enforcement levels
Authorization should be enforced at three levels: (1) Gateway and proxy level, at a coarse level of granularity; (2) Microservice level, using shared authorization library and components to enforce fine-grained decisions; (3) Microservice business code level, to implement business-specific access control rules.
Access control policy procedures
Formal procedures on access control policy must be implemented on development, approval and rolling-out.
Insecurity of reusing external access tokens internally
Reusing external access tokens and passing them to internal microservices is highly insecure due to possible external access token leakage and may increase attack surface. If an internal service is unintentionally exposed to external network, it can be directly accessed using the leaked access token. This pattern also is not external access token agnostic, so internal services have to understand external access tokens and support a wide range of authentication techniques to extract identity from different token types (e.g., JWT, cookie, OpenID Connect token).
Sending external entity identity as clear or self-signed structures
The microservice extracts external entity identity from incoming request by parsing the incoming access token, creates a data structure (e.g., JSON or self-signed JWT) with that context, and passes it to an internal microservice. The recipient microservice must trust the calling microservice. If the calling microservice wants to violate access control rules, it can set any user/client ID or user roles in the HTTP header. This approach is suitable only in highly trusted environments where every microservice is developed by a trusted development team that applies secure software development practices.
Identity propagation using signed data structures
After external request is authenticated by authentication service at edge layer, a data structure representing external entity identity (e.g., containing user ID, user roles/groups, or permissions) is generated, signed, or encrypted by trusted issuer and propagated to internal microservices. This pattern is external access token agnostic and allows decoupling of external entities from their internal representations. Netflix implemented this with a structure called Passport containing user ID and attributes, HMAC protected at edge level for each incoming request, and propagated to internal microservices without exposure outside.
Netflix Passport identity propagation steps
Netflix Passport identity propagation follows these steps: (1) Edge Authentication Service (EAS) obtains secret key from Key Management System; (2) EAS receives access token from incoming request; (3) EAS decrypts access token, resolves external entity identity, and sends it to internal services in signed Passport structure; (4) Internal services extract user identity to enforce authorization using wrappers; (5) If necessary, internal service can propagate Passport structure to downstream services in call chain.
Identity propagation implementation recommendation
Decouple access tokens issued for an external entity from its internal representation. Use a single data structure to represent and propagate external entity identity among microservices. Edge-level service must verify incoming external access token, issue an internal entity representation structure, and propagate it to downstream services. The internal entity representation structure should be signed by a trusted issuer using symmetric or asymmetric encryption. The structure should be extensible to enable adding more claims that may lead to low latency. The internal entity representation structure must not be exposed outside (e.g., to a browser or external device).
Mutual TLS for service-to-service authentication
With mTLS approach, each microservice can legitimately identify who it talks to, in addition to achieving confidentiality and integrity of transmitted data. Each microservice in deployment must carry a public/private key pair and use that pair to authenticate to recipient microservices via mTLS. mTLS is usually implemented with a self-hosted Public Key Infrastructure. Main challenges of using mTLS are key provisioning and trust bootstrap, certificate revocation, and key rotation.
Token-based service-to-service authentication
Token-based approach works at application layer. A token is a container that may contain caller ID (microservice ID) and its permissions (scopes). Caller microservice can obtain a signed token by invoking a special security token service using its own service ID and password, then attaches it to every outgoing request via HTTP headers. Called microservice can extract the token and validate it online or offline. In most cases, token-based authentication works over TLS, which provides confidentiality and integrity of data in transit.
Offline token validation for service-to-service
Offline token validation scenario: (1) Microservice uses downloaded service token service public key to validate incoming tokens; (2) Revoked or compromised tokens may not be detected; (3) Results in low latency; (4) Should be applied to non-critical requests.
Microservices logging architecture pattern
High-level logging architecture pattern: (1) Each microservice writes log message to local file using standard output (via stdout, stderr); (2) Logging agent periodically pulls log messages and sends (publishes) them to message broker (e.g., NATS, Apache Kafka); (3) Central logging service subscribes to messages in message broker, receives them, and processes them.
Microservice logging to local file requirement
Microservice shall not send log messages directly to central logging subsystem using network communication. Microservice shall write its log message to local log file. This allows mitigating threat of data loss due to logging service failure due to attack or flooding by legitimate microservice. In case of logging service outage, microservice will still write log messages to local file without data loss, and after logging service recovery, logs will be available to shipping.
Dedicated logging agent requirement
There shall be a dedicated component (logging agent) decoupled from microservice. Logging agent shall collect log data on microservice (read local log file) and send it to central logging subsystem. Due to possible network latency issues, logging agent shall be deployed on same host (virtual or physical machine) with microservice. This allows mitigating threat of data loss due to logging service failure due to attack or flooding by legitimate microservice. In case of logging agent failure, microservice still writes information to log file, and after logging agent recovery, it will read the file and send information to message broker.
Message broker requirement for logging DoS mitigation
To prevent possible DoS attack on central logging subsystem, logging agent shall not use synchronous request/response pattern to send log messages. There shall be a message broker to implement asynchronous connection between logging agent and central logging service. This allows mitigating threat of data loss due to logging service failure in case of flooding by legitimate microservice. In case of logging service outage, microservice will still write log messages to local file without data loss, and after logging service recovery, logs will be available to shipping.
Logging agent and message broker mutual authentication
Logging agent and message broker shall use mutual authentication (e.g., based on TLS) to encrypt all transmitted data (log messages) and authenticate themselves. This allows mitigating threats such as: microservice spoofing, logging/transport system spoofing, network traffic injection, sniffing network traffic.
Message broker access control enforcement
Message broker shall enforce access control policy to mitigate unauthorized access and implement principle of least privileges. This allows mitigating threat of microservice elevation of privileges.
Logging agent data sanitization requirement
Logging agent shall filter and sanitize output log messages to make sure that sensitive data (e.g., PII, passwords, API keys) is never sent to central logging subsystem, following data minimization principle. For comprehensive overview of items that should be excluded from logging, see the OWASP Logging Cheat Sheet.
Correlation ID for microservices logging
Microservices shall generate a correlation ID that uniquely identifies every call chain and helps group log messages to investigate them. Logging agent shall include correlation ID in every log message.
Logging agent health and status reporting
Logging agent shall periodically provide health and status data to indicate its availability or non-availability.
Structured logs format requirement
Logging agent shall publish log messages in a structured logs format (e.g., JSON, CSV).
Logging agent context data appending
Logging agent shall append log messages with context data, including platform context (hostname, container name) and runtime context (class name, filename).