At 9fin, we build AI-powered intelligence for debt markets. We’re scaling faster than ever before and are spinning up APIs internally for better decoupling and separation of concerns, while quickly introducing new product surfaces such as MCPs and agents. As our architecture evolved, this highlighted an opportunity to broaden our authentication capabilities beyond the core application supporting our web interface, which handled user-session verification in-app.
Consequently, several use cases routed through the application to handle authentication. While effective, this added a hop to every request and limited our ability to deploy services independently and improve database separation.
Elsewhere, internal calls relied primarily on network separation to establish trust, since they originated within our private cloud, with API keys used for additional cases. To move more data between our systems and become an API-first company, we needed to fix both these constraints.
Service to Service
Service-to-service authentication is how one of our systems proves its identity when it calls another. Previously, service identity was inferred from network location: requests originating inside our private cloud were treated as internal. As our architecture grew, we wanted each service to distinguish more precisely between requests from our web application, admin tooling, and other systems.
Every system needed to be able to answer two questions about every request it receives: which of our services is asking, and is it allowed to ask for this?
Four steps to secure
Step one to answering those two questions is a token service. This is a small, central service with one job: minting short-lived JWTs.
What is a JWT?
When one of our systems calls another, it asks the token service. The token service confirms it recognises that system from the identity our cloud provider already gives it, and mints a short-lived token that names the system. Every other system can validate that token by verifying the signature at the load balancer, before a request even hits the target server.

Four-step token service.
Step two extends those tokens to cover customers.
Many requests are not just "our web application is calling" but "our web application is calling on behalf of this customer." For those requests, we mint a token that is valid for a much shorter duration and is tied to a single customer and a single destination. This token coarsely scopes what that customer is entitled to see.
Step three made onboarding a dream: a shared library that every team drops into their service.
This handles the request for a token, renewal before it expires, and authorises inbound requests to ensure they’ve come from a permitted service. Adopting the new model is a few lines of code rather than a project — running on the mantra of a security control that is hard to implement is one that never gets prioritised.
Step four is the migration.
Each service moves through a three position switch: off, migrating, and enforcing. In migrating mode, the service validates incoming tokens without yet requiring one, allowing teams to confirm compatibility with downstream services and workflows before enforcement. We proved the whole path first, then wrote it up as a playbook for other teams to implement.
Throughout, we treated the token service as critical infrastructure. The service is monitored and alerted on like anything else our customers depend on, and we tested it under far more load than the service will ever see before any other service was made to rely on it.
Services, authenticated and authorised
Any service can now confirm who is calling it and decide whether to action that request, moving from location-based trust to verifiable identity. Internal calls are now trusted based on the identity they present rather than solely on where they originate. The new model also provides a single, consistent alternative to previously varied API-key approaches.
External user authentication
For web application requests on behalf of the customer, how do we know who that customer is? This highlights the need for a clear authentication boundary between 9fin services and the outside world.
Previously, authentication lived at the SaaS application layer within 9fin.com. That suited us while >99% of traffic flowed through 9fin.com, but it doesn’t scale now we’re exposing additional surfaces (MCP, API, agents) that can serve traffic directly.
The universal 9fin gate
A universal authentication layer provides six compounding benefits:
- Centralised authentication enforcement: one place to validate sessions/tokens and enforce policy, instead of every service re-implementing (or proxying) authentication.
- Consistent identity and claims everywhere: all downstream surfaces (web, API, MCP, agents) receive the same identity model and claim set.
- Reduced attack surface: fewer components handle credentials or cookies directly, concentrating controls and simplifying secure configuration.
- Uniform security controls: MFA, step-up authentication, anomaly detection, rate limiting, and revocation are applied consistently at the boundary.
- Better observability: a single choke point to log “who accessed what, when, and how”, improving traceability and incident response.
- Faster independent delivery: teams can ship/deploy services autonomously while inheriting the same security posture and compliance guarantees.
We create a gate at the boundary into our secured systems (within our VPC) by leveraging the following technologies.
AWS CloudFront
Placing CloudFront at the edge of user requests gives us a fast, consistent, and controllable edge layer.
For performance and resilience, we can cache content in AWS regions near users to reduce latency and shield origin services from traffic spikes.
Regarding security and compliance, we apply AWS WAF rules centrally to rate-limit and block abusive patterns before they reach our VPC. This also strengthens our compliance posture by enforcing geo-based access controls and request filtering at the boundary.
For observability and auditing, CloudFront and WAF give us edge-level logging and metrics for incident response, abuse investigations, and compliance reporting, without relying on each origin service to implement consistent request logging.
AWS Load Balancer
Load balancers are the gate into our VPC, splitting responsibility across two layers so authentication and application scaling can evolve independently.
First, is a dedicated authentication load balancer behind CloudFront/WAF. It terminates user-facing traffic, performs authentication, and once identity is established, issues OIDC tokens that are trusted across downstream 9fin services.
Second, is a set of application-specific load balancers (one per product surface). These handle request routing and performance/scaling concerns for each application independently (capacity, timeouts, health checks, and rollout strategy like blue/green or canaries), avoiding cross-application coupling and keeping blast radius contained.

Request flow in action.
WorkOS
We use WorkOS as the system of record for external user identity and PII, effectively treating it as our user directory.
While WorkOS does offer an out-of-the-box hosted login page, its configurability is intentionally limited. We need additional functionality on our login surface; most notably the ability to embed tools like Intercom so users can get real-time assistance while trying to access our systems. As a result, we built our own self-hosted boundary authentication layer application (9auth) to provide the configurability we need while still leveraging the fundamental authentication principles WorkOS provides out of the box.
When a user logs in, 9auth delegates the sign-in flow to WorkOS AuthKit, which performs the authentication (including factors like SSO/MFA where configured) and returns a verified identity we can trust.
Downstream 9fin services then consume the resulting user claims/tokens rather than holding passwords or duplicating sensitive profile data. This keeps credential handling and user PII centralised and reduces the amount of sensitive data that lives inside our systems.
The Architecture

9auth within 9fin’s authentication ecosystem.
Coarse grained authorisation
WorkOS's user metadata function also allows us to define a user’s permitted packages and products such that this is reflected in the token claims provided to the service.
This not only authenticates the user, but provides a high-level authorisation functionality allowing services to restrict access to a service entirely at the router level, upstream of any business logic built within the service.
One login to rule them all
We now have a scalable solution in which a user is authenticated once, and has access to the whole universe of 9fin services.
9fin's seamless user experience combined with its scaling of a wide array of products is bringing credit markets quickly and securely into the AI age.
If you enjoy making systems faster and more secure, while earning user trust — we’re hiring, and want to speak with you!






