Architecting a Polyglot CIAM Engine for Digital Banking
Monolithic architectures failed to balance sub-second biometric authentication spikes with the heavy stateful complexity required for core banking synchronizations.
Achieved zero-downtime authentication spikes with sub-50ms latency at the edge, while safely orchestrating millions of stateful customer profiles via gRPC.

On this page
Biometric mobile logins sound straightforward until 50,000 users tap FaceID simultaneously at 9:00 AM on payday, while your backend is trying to fetch complex customer profiles and sync legacy core banking ledgers.
When designing the identity backbone for UCB Bank’s flagship digital superapp UCB One, we ran into a classic architectural fork in the road: a single monolithic framework would either choke under high-concurrency cryptographic workloads or become an unmaintainable nightmare when managing relational business logic and KYC state machines.
Our solution? A strict polyglot architecture: isolating CPU-heavy cryptographic validation at the edge using Go, while pushing deep, relational profile orchestration to Python (Django) in the core.
graph TD
Client(["📱 Mobile App"])
Client -->|"Signed Challenge (ECDSA)"| Auth["Authentication Service (Go)"]
subgraph GoTier["⚡ Edge Tier - Golang"]
Auth -->|"Verify Signature and Burn"| Store[("Challenge Store")]
Auth -->|"Mint RS512 Token"| JWT["Session Issuer"]
end
JWT -->|"Internal gRPC"| Backend["Orchestration Service (Python)"]
subgraph PyTier["🐍 Orchestration Tier - Python"]
Backend -->|"Connection Interceptor"| ThreadPool["gRPC Thread Pool"]
ThreadPool -->|"Aggregated Reads (CQRS)"| DB[("Profile Database")]
end
Architectural Tradeoff Matrix
| Architectural Tier | Language & Framework | Responsibilities | Key Performance Metric |
|---|---|---|---|
| Edge Gateway | Golang | ECDSA-P256 signature verification, Nonce challenge burn, RS512 JWT minting | < 50ms P99 Latency |
| Core Orchestration | Python (Django) | Onboarding state machine, KYC verification, Bulk ORM prefetching | 2 SQL queries per profile |
| Inter-Service Mesh | gRPC / Protobuf | Binary multiplexed communication & thread connection interceptor | Zero connection leaks |
1. The Edge: Cryptographic Biometric Gateway (Go)
Mobile authentication has to feel instantaneous. If a user taps their fingerprint and waits 2 seconds, the app feels broken. To guarantee sub-50ms response times under heavy load, we built the entire edge-facing gateway in Go.
Hardware-Backed Passkeys & ECDSA Verification
Passwords are a massive liability in modern banking. We replaced traditional credentials with a hardware-backed biometric layer using elliptic curve cryptography (ecdsa).
- During enrollment, the user’s mobile device generates an ECDSA-P256 key pair. The private key never leaves the device’s Secure Enclave.
- The Go server stores only the PEM-encoded public key.
- On every login attempt, the Go gateway issues a short-lived, cryptographically secure challenge.
- The mobile device signs the challenge inside hardware and posts it back. The Go service verifies the signature against the stored public key in memory, guaranteeing identity and physical device binding simultaneously.
// Sanitized edge signature verification in Go
challenge := decode(request.challenge)
if !verifySignature(publicKey, challenge, signature) {
rejectAuthentication()
}
markChallengeAsUsed()
Eliminating Session Fixation & Replay Attacks
A intercepted authentication payload could allow a malicious actor to replay a session. We built anti-replay protection directly into our edge memory layer:
- Dynamic Nonces: Every sensitive payload includes a single-use nonce. The Go service checks our distributed store; if the nonce was already seen, the request is instantly dropped.
- Instant Challenge Burning: The moment a biometric signature is evaluated (whether valid or invalid), the challenge is burned instantly so it can never be submitted twice.
- Strict Device Binding: Biometric credentials are tied 1:1 with an internal
device_id. If a user registers a new phone, we programmatically revoke older biometric tokens to eliminate ghost sessions.
2. Session Minting: Zero-Trust Issuance (Go)
Verifying the biometric signature is only step one. Next, we need to issue a secure, short-lived session token. Instead of calling out to a third-party identity vendor over the network, the Go edge gateway handles token minting locally in zero allocations.
Once the ECDSA signature passes, the Go service drops down to an optimized RSA token minter.
// Sanitized RSA RS512 JWT Minting in Go
claims := RegisteredClaims{
ExpiresAt: NewNumericDate(now.Add(expiryDuration)),
IssuedAt: NewNumericDate(now),
Issuer: jwtIssuer,
Audience: jwtAudience,
}
token := NewWithClaims(SigningMethodRS512, claims)
signedToken := token.SignedString(jwtSigningKey)
By enforcing SigningMethodRS512 and validating claims strictly at the edge, any request passing downstream to Python is cryptographically guaranteed to be authentic.
3. The Core: Stateful Onboarding Orchestration (Python)
While Go handles lightning-fast edge traffic, the actual business logic of banking—aggregating customer accounts, managing KYC onboarding states, and syncing core ledgers—is deeply stateful. For this layer, we chose Python (Django) for its rapid data modeling and ecosystem strength.
Managing Complex Onboarding State Machines
Banking onboarding isn’t a simple true/false status. A customer transitions through a series of strict state machines: Initiated → Document Verified → Limited Access → Full Account. Handling these transitions requires multi-step validation, OTP verification, and third-party eKYC lookups. Python makes expressing these relational workflows clean and maintainable.
Curing N+1 Database Bottlenecks with Prefetch Objects
A naive ORM implementation fetching a customer profile along with their active accounts, credit cards, and tagged preferences will easily trigger 50+ database queries per request. Multiply that by thousands of users, and your database CPU hits 100%.
To fix this, we bypassed default lazy loading and engineered custom Prefetch configurations in Django. This batches query execution into exactly two SQL calls, pre-populating the ORM cache in memory:
# Sanitized Django ORM Bulk Prefetching
customer = (
Customer.objects.filter(id=customer_id)
.prefetch_related(
Prefetch(
"accounttag_set",
queryset=AccountTag.objects.filter(is_untagged=False),
to_attr="accounts"
),
Prefetch(
"cardtag_set",
queryset=CardTag.objects.filter(status="active"),
to_attr="cards"
)
).first()
)
By filtering at the database layer (is_untagged=False, status="active") and binding results directly to to_attr in memory, building the customer response payload becomes a zero-I/O dictionary lookup.
4. The Inter-Service Layer: gRPC & Connection Safety (Python)
Connecting a multi-threaded, high-concurrency Go gateway to a synchronous Python backend presented a major challenge: standard REST/HTTP calls added unacceptable latency overhead.
We replaced REST with a dedicated gRPC Server inside the Python application.
However, running a multi-threaded gRPC server (grpc.server(max_workers=...)) inside a Django environment has a hidden trap: thread pool database connection leaks. If gRPC worker threads don’t explicitly clean up after finishing an RPC call, PostgreSQL connections remain open until the connection pool exhausts itself.
To solve this, we wrote a custom DBConnectionInterceptor in Python:
# Sanitized gRPC Interceptor in Python
class DBConnectionInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
def wrapper(behavior, *args, **kwargs):
try:
return behavior(*args, **kwargs)
finally:
# Forcefully release PostgreSQL connections for the worker thread
close_old_connections()
This interceptor wraps every incoming gRPC call. The moment a worker thread finishes its response, it forcefully releases any open database connections back to the pool.
By respecting the strengths of both technologies—Go for concurrency and cryptography, and Python for state machines and relational modeling—we built a CIAM infrastructure that delivers sub-50ms edge security without ever overloading the banking core.
Enjoyed this? I write about backend systems and architecture — rarely, but always substantive.