How it works - Gurgant/azurebank-v2 GitHub Wiki
The shape of the system, in short. The full description is one document, complete on its own: How AzureBank works.
flowchart TB
Browser["Browser<br/>React 19 SPA"]
BFF["BFF<br/>ASP.NET Core + YARP"]
API["API<br/>ASP.NET Core"]
DB[("SQL Server")]
Browser -->|"HttpOnly session cookie"| BFF
BFF -->|"service key + bearer, added server-side"| API
API -->|"EF Core"| DB
- The browser runs the React app. It holds a cookie, never the JWT.
- The BFF serves the built app, holds the session and is the API's only client.
- The API owns the money rules.
- SQL Server is the database, reached through EF Core.
- The README's diagram has a fifth box, the notice relay, which is outside the path of a request.
The app in the browser never receives, stores or sends a JWT. Its requests are authenticated by an HttpOnly cookie. The BFF keeps the tokens on the server and adds the bearer header itself.
- There is no CORS anywhere, because everything is same-origin.
- The API refuses a request that does not carry the BFF's service credential, with two exemptions: its health probes and, in Development only, the pages of the API's documentation.
| Guarantee | In short |
|---|---|
|
A payment is applied at most once per key Read: Moving money once · the proof |
Every money move carries an idempotency key, and the server claims the key before it acts. It held under 24 concurrent duplicates on a real SQL Server. |
|
The JWT never reaches the browser Read: The one decision · where each guarantee stops |
The BFF keeps it in a server-side session. |
|
One PIN entry authorises one payment Read: A second factor |
The authorisation is bound to the account, the amount and, for a transfer, the payee, is valid two minutes and is spent once. Three wrong PINs in a row lock the PIN for 15 minutes. |
|
An audit trail that can be checked Read: A tamper-evident record |
Security events go to an append-only, hash-chained table. A tool in the repository walks the chain: it is run by hand, on no schedule, and it does not detect rows cut from the end of the table. |
|
The frontend is held to the API's contract Read: Three claims about the contract |
The committed contract is what the API generates, and one contract suite runs against the mock and the real stack. |
|
One trace per request Read: Keeping the two halves honest |
A request through the BFF is one trace down to SQL, and every error carries its trace id. Traces leave the process only when a collector's address is set in its environment, and the template of the deployed demo sets none. What its containers print goes to a log workspace kept 30 days. |
- One money move is at most €100,000.
- Transfers to other people are at most €5,000 in a UTC day, by default.
- There are no optimistic updates on money: a balance comes from a refetch.
- The tools in each layer: Stack.
- The API itself: the generated contract and its guide. The contract is generated from the API and never edited by hand.
- The security posture in one place, with what is not done: SECURITY.md.
- Next page: Decisions.