Know Who May Read the Order
Separate trusted caller identity, tenant ownership, server TLS and secret delivery, with explicit local and platform boundaries.
Retailer A’s caller knows the identifier A-1001. So does retailer B’s caller. An identifier is not proof that either caller may read the order. The service must receive a trusted caller identity and use it when selecting business state.
Authentication establishes who is calling under a particular trust contract. Authorization decides what that caller may do. TLS protects a connection and authenticates a peer according to its certificate configuration. Secrets let an application prove its own identity to dependencies. These responsibilities interact, but one does not automatically complete the others. A gateway establishes a trusted caller and enforces broad access rules; the application decides whether that caller may perform this operation on this order.

Open this checkpoint in ACB
Stop the previous local application, then use File → Open Folder to open book/checkpoints/23-order-ownership. Open src/main/mule/app.xml and use Flow List to select the named flow for each example. Start the controlled dependency in a separate terminal from the companion root with python3 book/stubs/server.py. Choose Run and Debug → Run Mule Application and wait for deployment. Save canvas edits and use Save and Hot-deploy to Local Runtime before repeating a request.
Run python3 book/run.py verify 23 from the companion root to exercise this running checkpoint with its synthetic fixtures. The verifier supplies requests and checks results; it does not start the ACB application. Keep the editor on this checkpoint while reading a failure so an old deployment cannot supply a misleading answer.
Make the application’s trusted input explicit
Checkpoint 23 provides a synthetic principal from a local property. It exists so ownership can be tested without an identity provider. It is not token validation and must not be copied into a public deployment as an authentication mechanism. Nor should an internal network location become the reason to skip the check — a compromised application inside that network may have exactly the connectivity a legitimate one has.
Example 062 — Supply a local test principal
The complete source is in checkpoints/23-order-ownership/src/main/mule/app.xml.
Choose Flow List → local-test-principal. Its Set Variable has Name principal and Value expression {tenantId: p('local.testTenant')}. The next Flow Reference calls require-tenant.
Open src/main/resources/config-local.yaml in Explorer to find the synthetic tenant setting. This deliberately local principal is a fixture, not a token-validation component.
Example 063 — Require a caller tenant
The complete source is in checkpoints/23-order-ownership/src/main/mule/app.xml.
Choose Flow List → require-tenant and expand Choice. Inspect both rejecting routes: not ((vars.principal.tenantId default null) is String) and isEmpty(vars.principal.tenantId). Each raises APP:UNAUTHENTICATED. The first condition checks type before the later branch uses a string operation.
The entry flow obtains this context before APIkit routes the request. The guard rejects a missing or empty tenant. It does not inspect a client-provided tenant header, query parameter or order body and treat that value as authenticated fact — a caller that can name its own tenant has chosen its own authorization context.
The local service uses retailer A by default. A dedicated diagnostic route calls the lookup with retailer B, and another route supplies no principal. Those fixed test seams let us establish the application’s ownership decision separately from platform authentication.
Example 064 — Read an order within its owner tenant
The complete source is in checkpoints/23-order-ownership/src/main/mule/app.xml.
Choose Flow List → get-order. Set Variable retains vars.principal.tenantId as tenant. The Database Select uses Orders_DB and queries by both ownership and order identity:
SELECT response AS "response" FROM accepted_orders WHERE tenant_id = :tenant AND order_id = :id
Its Input Parameters expression is {tenant: vars.tenant, id: attributes.uriParams.orderId}. The following Choice raises APP:NOT_FOUND when isEmpty(payload). Transform Message reads the retained response as JSON. Inspect the SQL predicate, not just the HTTP route, when reviewing ownership.
The bound query includes tenant and order ID. For the order created by retailer A, the ordinary lookup succeeds and /lab/tenant-b/orders/A-1001 returns 404. Returning not-found avoids revealing another tenant’s order through this endpoint. /lab/anonymous returns 401 from the local principal guard.
A production service may also need subject-level permissions within a tenant. This example defines tenant ownership only; it does not invent roles or assume every user of a retailer may cancel every order. Write that finer rule into the business operation before adding an endpoint, and apply it to updates and bulk paths too: a service that checks ownership on GET and then updates by order ID alone is inconsistent in the direction that costs the most.

Database Select constrains the order by tenant and order identity.
Replace the local seam with verified claims
The cloud reference project has a gateway-principal subflow and no local-principal fallback. It reads authentication.properties.claims.tenantId, the claim location used by the selected Mule Gateway JWT policy contract. Configure signature validation and required issuer, audience, expiry and tenant claims before trusting that value. A token signed by a trusted provider for a different service carries a perfectly valid signature — a tenantId string inside it is not sufficient by itself.
JWT validation and an OAuth token-provider policy are different integrations. JWT validation checks the signed token and configured claim rules; an OAuth provider policy may involve the provider’s validation protocol and availability. Client ID Enforcement identifies an application under its contract — a person using that application needs a separate identity model — and it does not by itself grant order ownership. Choose the mechanism that supplies the identity the application actually needs.
Test missing, expired, incorrectly signed and wrong-audience tokens, then test a valid identity that does not own the order. Record the selected policy’s actual statuses: even missing-token behavior need not match the application’s own 401 response. JWT validation policy.
Establish server trust with a small TLS application
An HTTPS URL says nothing about which peers a listener actually trusts — only a handshake that fails when it should. The workshop here is deliberately small: one listener with its own key store, and two requests that differ in nothing except whether the client trusts the certificate it is offered.
Example 065 — Trust a local TLS server
Source: platform/tls/src/main/mule/app.xml.
Open book/platform/tls as a separate ACB project and follow its README to generate the disposable local certificate. On the hello-over-tls canvas, select Listener and open its connection: HTTPS, Host 127.0.0.1, Port 18884.
Inspect the TLS context and Key Store configuration: enabled protocols TLSv1.2,TLSv1.3, Type PKCS12, Path server.p12, Password and Key Password changeit for this generated teaching certificate only. The flow’s path is /hello; Set Payload supplies Hello over TLS.
Run this project independently of the orders application. Use the workshop’s certificate-aware curl command below; the trust check is part of the exercise.
The complete workshop supplies a certificate-generation script, POM and descriptor. It creates a disposable PKCS12 identity for localhost and 127.0.0.1, copies that generated file into gitignored application resources for this lab, then listens on 18884. The runtime loads it as the server.p12 resource. Do not package a real private key using this disposable-lab procedure. The demonstration password is intentionally public and must never protect a real service identity.
After packaging and deploying the workshop, run:
curl --cacert "$MULE_HOME/orders-data/server.crt" https://127.0.0.1:18884/hello
curl https://127.0.0.1:18884/hello
The first request explicitly trusts the generated certificate and should return Hello over TLS. The second should reject that self-signed certificate. Using -k would bypass the trust check and defeat the comparison.
This is server TLS. Mutual TLS additionally needs a client trust configuration and tests with trusted, untrusted, missing and expired client certificates. At a platform ingress, identify where TLS terminates and what protects the last connection to the application — the public endpoint and that last hop are two different connections. An HTTPS public URL alone does not establish authenticated client-certificate context inside Mule.
Keep secret delivery separate from source code
The cloud reference uses runtime properties and marks db.password as sensitive in its descriptor and deployment configuration. Secure Configuration Properties is another option: it decrypts packaged encrypted values through an independently supplied key. The secure:: prefix selects that property provider; it does not encrypt a value by itself.
If you choose encrypted files, install the module with its exact dependency, generate ciphertext with matching algorithm/mode/IV settings, and supply the decryption key outside the archive. Appendix B preserves a complete configuration worksheet and the dummy-value tool command. Neither a checked-in ciphertext file nor a masked console view removes plaintext from the running application’s memory, and an environment variable stays readable to processes and operators with sufficient access. Plaintext exists somewhere in the end, because the application has to present real credentials to the dependency’s authentication protocol. What you control is how the secret is delivered and who can reach the runtime.
The application’s own identity is separate again. When the order service calls the catalogue or the warehouse, forwarding the consumer’s token may be wrong: its audience may be the orders API, and the dependency may use a different authorization model altogether. A service credential grants the integration its own permissions, and the original actor is preserved separately, for audit rather than for access.
Rotation involves different objects: a downstream credential, a file-decryption key, and a TLS private key. Changing one does not rotate the others. Verify a fresh outbound connection after credential rotation; an existing pooled connection can conceal a bad new password. Keep an overlap when supported and record which secret version remains valid for rollback. Plan that rollback before the old credential is revoked — a previous archive is only one part of the previous working deployment, and compatible configuration and a live credential complete it.
Try it
1. Use a valid but foreign caller. Why is a valid token insufficient to read A-1001?
Show answer
It establishes an identity under the token policy. The application must still restrict the query to that identity’s tenant and any required subject permissions. Another tenant receives the defined not-found response.
2. Explain the TLS comparison. What does the ordinary curl failure establish that a successful curl -k cannot?
Show answer
It shows the default trust configuration does not trust this disposable identity. The explicit CA request tests the intended trust path; -k skips certificate verification and supplies no such evidence.
3. Rotate and roll back. The old database password has been revoked. Will restoring yesterday’s JAR restore service?
Show answer
Only if compatible configuration supplies a still-valid credential. Artifact rollback cannot un-revoke a password. Retain configuration and secret-version identifiers with the release, without recording secret values.
The application now has a clear caller-context boundary. API Manager can govern the requests that reach it, provided the managed instance is associated with the actual entry flow.
Comments