Assessing the Effectiveness of Digital Business Cards
TL;DR
The problem with chained api calls
Ever had a simple feature request turn into a security nightmare because one api started talking to five others behind the scenes? It happens way faster than you think, especially when you're building out microservices or b2b saas platforms.
When we first build these things, the temptation is to just pass the same jwt you got from the user all the way down the chain. It’s easy, right? But honestly, it’s a massive risk because you end up with "god tokens" that have way too much power.
- Audience (aud) mismatch: If your frontend gets a token for "api-gateway", but you pass that same token to a "billing-service", the billing service shouldn't really accept it. The
audclaim is wrong, but developers often disable that check just to make the call work. - Service impersonation: If a low-privilege service like a "product-catalog" gets a user's token, and it's compromised, an attacker can use that same token to call the "payment-gateway" because the token hasn't changed.
- Scope creep: Tokens often carry scopes like
read:user. If you pass that to every downstream api, you're violating the principle of least privilege. According to a 2023 report by Salt Security, 94% of organizations experienced security problems in their production apis, often due to poor authorization logic like this. (API Security Report Shows 400% Increase in Attackers)
In a retail app, your "shipping-service" doesn't need to know the user's sensitive pii (personally identifiable information), but if you're just forwarding the original oidc token, that data might be sitting right there in the claims. It's a mess for compliance and a gift for hackers.
Anyway, just passing the buck with tokens creates a fragile system where one weak link breaks everything. Next, we’ll look at why "token exchange" is the better way to handle this without losing your mind.
Core patterns for service to service security
So, how do we actually stop passing those "god tokens" around without breaking the entire app? Most of the time, you're looking at two main patterns: swapping tokens for something more specific or just letting the services talk privately when no user is involved.
When your "order-api" needs to call the "shipping-api", it shouldn't just hand over the user's original jwt. Instead, it should go back to the identity provider (idp) and say, "Hey, I have this valid user token, can I get a new one specifically for the shipping service?"
This is the token exchange pattern. It lets you keep the user context (so you know who is ordering) but limits the audience and scopes. If the shipping service token gets leaked, it can't be used to access the payroll api because the aud claim won't match.
Setting this up requires your idp to support rfc 8693. (Configuring and using token exchange - Keycloak) You have to configure "trust relationships" so the idp knows the order service is allowed to ask for tokens on behalf of users. It’s a bit more work upfront, but it’s the gold standard for zero trust.
Sometimes, there isn't even a user involved. Think about a nightly cron job in a healthcare system that syncs patient records to a backup vault, or a finance bot that pulls exchange rates.
In these cases, we use the client credentials flow. The service authenticates as itself using a client_id and client_secret.
- No user context: There’s no
subclaim for a human; the "user" is the service itself. - Strict Scopes: You should give these machine-to-machine (m2m) tokens the bare minimum. If a service only needs to write logs, don't give it
adminrights. - Rotation: Since these secrets are often stored in config or vaults, you need a solid rotation strategy.
According to a 2024 report by Cloudflare, api traffic now makes up 57% of all dynamic web traffic, and machine-to-machine calls are growing the fastest. If you aren't securing these background tasks, you're leaving a massive door open.
Next, we're gonna dive into how to manage all these different keys and secrets without losing your mind—or your job.
Advanced security layers for modern architectures
So you've got your tokens moving around, but is the pipe itself actually secure? Even with the best oidc setup, if someone sniffs the traffic between your "payment-service" and "ledger-api", you're basically toast.
Most people think of tls as just the padlock in their browser, but for service-to-service, we need mutual TLS (mTLS). In a standard setup, the client checks the server's cert, but with mtls, the server also demands a cert from the client. It's like a two-way handshake where nobody gets in the door without showing an id.
- Cryptographic Identity: Instead of just relying on a bearer token that can be stolen, the connection itself is tied to a specific certificate.
- Zero Trust at the Transport Layer: Even if an attacker gets inside your vpc, they can't just start talking to random apis because they don't have the right certs to establish a handshake.
- Observability and Correlation IDs: When you have ten services talking to each other, debugging is a nightmare. You need to attach a "correlation id" to the header of every request. This is just a unique string that follows the request everywhere. If "Service G" fails, you can search your logs for that id and see exactly what "Service A" did to trigger it.
I've seen teams get lazy and only check identity at the edge gateway. That is a huge mistake. True Zero Trust means you verify the identity at every single hop in the chain.
This is where a tool like ssojet really helps out. Think of ssojet as an identity provider and security mesh tool that handles the heavy lifting of oidc and mtls for you. It integrates with your services so you don't have to manually code the handshake logic every time you add a new microservice.
According to a 2023 report by Verizon, internal actors or misconfigurations are involved in a massive chunk of breaches. By forcing every hop to re-authenticate, you limit the "blast radius" if one of your containers gets popped.
Anyway, managing all these certs and keys manually is a nightmare. Next, we’re gonna look at how to actually automate all this secret management so you don't end up with expired certs crashing your production cluster at 3 AM.
Automating secret management
If you're still copy-pasting api keys into a .env file, you're living on borrowed time. For real security, you need a dedicated secret manager like HashiCorp Vault, AWS Secrets Manager, or the built-in secret handling in ssojet.
These tools do more than just store strings. They handle dynamic secrets, where the vault actually creates a temporary password for the database that expires in an hour. If an attacker steals it, it's useless by the time they try to use it. They also handle automatic rotation, so your certs and keys get swapped out without you having to touch a single line of code. It’s basically a "set it and forget it" for your most sensitive credentials.
Common mistakes people make with api security
Ever wonder why even the "secure" systems get hacked? Most the time, it's not some crazy zero-day exploit—it's just someone leaving a password in a text file because they were in a rush to hit a deadline.
I've seen this a million times in b2b saas startups. A developer needs the "billing-api" to talk to Stripe, so they just drop the api key right into the environment variables or, god forbid, the source code itself.
- The rotation nightmare: If you hardcode a secret, you can't rotate it without a full redeploy. If that key gets leaked, you're stuck waiting for a ci/cd pipeline while the attacker drains your account.
- Environment leakage: People think
process.envis safe, but logs often dump environment variables when a service crashes. Suddenly, your "secure" key is sitting in a plaintext log file in splunk or datadog. - Missing downstream validation: Just because a request comes from an internal ip doesn't mean it's safe. A 2024 report by CrowdStrike shows that 75% of attacks now involve credential theft rather than malware. If your downstream service doesn't validate the incoming token properly, you're toast.
In a healthcare app, I once saw a dev hardcode a database password because "it's only on the private network." Guess what? One compromised dashboard later and the whole patient database was exposed. Don't be that person. Use a vault or a dedicated secret manager.
Anyway, keeping secrets out of code is only half the battle. Next, we gotta talk about how to actually manage these identities at scale without losing your mind.
Managing IAM at scale
When you only have two services, managing who can talk to who is easy. When you have two hundred, it's a disaster. This is where Identity and Access Management (IAM) at scale comes in. You can't manage individual permissions for every service manually; you need to use Attribute-Based Access Control (ABAC) or groups.
Automation is your best friend here. You should be using "Infrastructure as Code" (IaC) to define your security policies. If a new service gets deployed, it should automatically get its own identity and the specific permissions it needs based on its role. Tools like ssojet or even Kubernetes service accounts help automate this so you don't have to manually click buttons in a dashboard every time a dev pushes a new feature.
Summary of best practices
Look, securing api chains is basically just staying paranoid about who's talking to whom. If you've followed along, you know that just "passing the token" is a recipe for a bad time.
- Always validate the 'aud' and 'iss' claims: Seriously, don't skip this. If a token meant for your "retail-api" shows up at your "finance-api", drop it immediately.
- Use short lived tokens for everything: The shorter the better—think minutes, not hours. It makes a leaked token way less useful for an attacker.
- Centralize your logging to track requests: Use a correlation id so you can see how a request flows from the frontend through every single downstream service.
In a healthcare setup, I've seen this save a team during an audit because they could prove exactly which service touched patient data. As mentioned earlier, most breaches come from misconfigurations, so keep your logic tight.
Honestly, just keep it simple. Use oidc patterns, rotate your keys, and don't hardcode secrets. You'll sleep way better.