<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: BB Dev Team</title>
    <description>The latest articles on DEV Community by BB Dev Team (@bbescort-team).</description>
    <link>https://dev.to/bbescort-team</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3477561%2F252c2c28-127f-492d-ab6a-e3674a59f455.png</url>
      <title>DEV Community: BB Dev Team</title>
      <link>https://dev.to/bbescort-team</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bbescort-team"/>
    <language>en</language>
    <item>
      <title>Bulletproof by Default: Security Architecture for Adult Platforms That Actually Get Attacked</title>
      <dc:creator>BB Dev Team</dc:creator>
      <pubDate>Mon, 06 Apr 2026 22:42:38 +0000</pubDate>
      <link>https://dev.to/bbescort-team/bulletproof-by-default-security-architecture-for-adult-platforms-that-actually-get-attacked-103a</link>
      <guid>https://dev.to/bbescort-team/bulletproof-by-default-security-architecture-for-adult-platforms-that-actually-get-attacked-103a</guid>
      <description>&lt;p&gt;_Why platforms in the adult industry face some of the most aggressive attack surfaces on the web — and how to build a system that holds up using NestJS, React Router, and a strict three-environment pipeline.&lt;br&gt;
_&lt;br&gt;
Running a platform in the adult industry means operating in one of the harshest security environments in commercial web development. That's not hyperbole — it's what we see in our logs every single day.&lt;/p&gt;

&lt;p&gt;The attack vectors are wider than those of a typical SaaS product. Users store highly sensitive information: personal data, payment details, photos, and private communications. A breach isn't just a PR disaster — it can destroy lives. The market is competitive and not always above-board: competitors order targeted DDoS attacks or attempt to harvest user databases. Regulatory requirements (GDPR, and soon the EU Digital Services Act) demand verifiably documented security measures.&lt;/p&gt;

&lt;p&gt;In our monitoring we see daily credential-stuffing waves, scraper botnets, SQL injection attempts, and targeted attacks on WebSocket endpoints. A "that won't happen to us" attitude is simply not an option.&lt;/p&gt;

&lt;p&gt;Security can't be an afterthought. It has to be baked into the architecture — from the first line of code to the production deployment. This is what that looks like in practice.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Why Adult Platforms Need a Different Threat Model
&lt;/h2&gt;

&lt;p&gt;Most web applications have to protect their users' data. Adult platforms have to protect their users' &lt;em&gt;identities&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's a fundamentally different bar. Consider the data that lives on a typical escort platform: real names, phone numbers, location history, banking details, profile photos that users explicitly don't want associated with their identity elsewhere, and private messages between users who trust the platform to keep those conversations confidential. A breach of this data doesn't just mean spam emails — it means real-world consequences: outing, blackmail, career damage, relationship breakdown.&lt;/p&gt;

&lt;p&gt;This shapes every architectural decision we make. We don't ask "what is the minimum security we need to comply?" — we ask "what happens to our users if this particular piece of data leaks?" That question has a way of focusing the mind.&lt;/p&gt;

&lt;p&gt;Practically speaking, the threat landscape looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Credential stuffing&lt;/strong&gt;: Automated bots testing leaked username/password combinations from other breaches against our login endpoint. This is constant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scraping&lt;/strong&gt;: Botnets systematically harvesting profile data to build competing datasets or blackmail users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Targeted attacks&lt;/strong&gt;: Competitors or bad actors directly targeting infrastructure — DDoS, attempted database exfiltration, social engineering of support staff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket exploitation&lt;/strong&gt;: Our real-time messaging system is a favourite target; attackers probe for replay vulnerabilities, missing authentication on upgrade, and insecure deserialization.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory probes&lt;/strong&gt;: Authorities and NGOs scan platforms for CSAM, age verification bypasses, and GDPR non-compliance. These aren't malicious attacks, but they demand robust audit trails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The threat model shapes the architecture. Let's get into it.&lt;/p&gt;


&lt;h2&gt;
  
  
  2. The Three-Environment Pipeline: Dev → Staging → Prod
&lt;/h2&gt;

&lt;p&gt;The foundation of our security strategy is a strict three-environment separation. No feature, no hotfix, no configuration change ever lands directly in production.&lt;/p&gt;
&lt;h3&gt;
  
  
  Development
&lt;/h3&gt;

&lt;p&gt;Local development uses mock services, a local Postgres instance, hot-reloading, full debug logging, and CORS open to &lt;code&gt;localhost&lt;/code&gt;. The payment provider is faked. Secrets are fake. The goal is developer velocity — this environment is intentionally permissive because it is completely isolated from real user data.&lt;/p&gt;
&lt;h3&gt;
  
  
  Staging
&lt;/h3&gt;

&lt;p&gt;Staging is where things get serious. It runs on production-equivalent infrastructure, with real TLS certificates, production-equivalent secrets (different values, same structure), and real data — anonymized and stripped of PII before being seeded. Every deployment to staging triggers the full security gate suite: SAST, dependency audits, container scanning, DAST. Load tests run here. Penetration tests target this environment.&lt;/p&gt;

&lt;p&gt;The single most important rule: &lt;strong&gt;staging must be indistinguishable from production in terms of configuration.&lt;/strong&gt; A security vulnerability that isn't visible in staging because the config differs will hit production without warning.&lt;/p&gt;
&lt;h3&gt;
  
  
  Production
&lt;/h3&gt;

&lt;p&gt;Production deployments happen only after staging approval. Manual SSH access to production servers is impossible — there are no keys, no bastion hosts for developers. All deployments flow through the CI/CD pipeline. Debug output is completely disabled. The WAF is active. Rate limiting is strict. An audit log captures every meaningful action, immutably, in a separate append-only datastore.&lt;/p&gt;

&lt;p&gt;We manage all three environments with Terraform, with strictly separated state files per environment. Environment-specific secrets live in HashiCorp Vault and are never shared across environments.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dev    ──────────────┐
                     ▼
staging  ──── [security gates] ──── approval ──── prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Security Gates in CI/CD
&lt;/h2&gt;

&lt;p&gt;Before any code reaches staging or production, it passes through a series of automated security checks. These aren't optional quality checks — a failed gate stops the deployment completely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push → lint/typecheck → unit tests → SAST → dep audit → container scan → staging deploy → DAST → prod deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Static Application Security Testing (SAST)
&lt;/h3&gt;

&lt;p&gt;We use &lt;code&gt;semgrep&lt;/code&gt; with our own ruleset alongside &lt;code&gt;eslint-plugin-security&lt;/code&gt; to scan every commit for known vulnerability patterns: SQL injection risks, missing input validation, hardcoded secrets, unsafe regex patterns (ReDoS), and insecure deserialization. The scan runs in under 30 seconds and blocks on any findings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/security.yml (relevant excerpt)&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SAST - Semgrep&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;returntocorp/semgrep-action@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;p/nodejs&lt;/span&gt;
      &lt;span class="s"&gt;p/secrets&lt;/span&gt;
      &lt;span class="s"&gt;p/owasp-top-ten&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dependency Auditing
&lt;/h3&gt;

&lt;p&gt;Every &lt;code&gt;npm audit&lt;/code&gt; run with &lt;code&gt;--audit-level=high&lt;/code&gt; is a pipeline blocker. On top of that, we use &lt;a href="https://socket.dev" rel="noopener noreferrer"&gt;socket.dev&lt;/a&gt; — which doesn't just check known CVEs but also detects suspicious behavioral changes in npm packages. Supply chain attacks via malicious package updates are underestimated as an attack vector, and &lt;code&gt;socket.dev&lt;/code&gt; catches them before they land in our dependency tree.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Image Scanning
&lt;/h3&gt;

&lt;p&gt;Docker images are scanned with &lt;code&gt;trivy&lt;/code&gt; before being pushed to the registry. Base images are restricted to minimal Alpine or Distroless variants. No root user in containers, ever. The CI step fails if any &lt;code&gt;HIGH&lt;/code&gt; or &lt;code&gt;CRITICAL&lt;/code&gt; CVE is found in the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image &lt;span class="nt"&gt;--exit-code&lt;/span&gt; 1 &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL myapp:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dynamic Application Security Testing (DAST)
&lt;/h3&gt;

&lt;p&gt;After every staging deployment, an OWASP ZAP baseline scan runs automatically against the staging environment. It tests for the OWASP Top 10: XSS, injection, security misconfiguration, broken access control, and more. Only when this scan returns clean does the production deployment gate open.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. NestJS: Building the Backend as a Fortress
&lt;/h2&gt;

&lt;p&gt;NestJS provides an excellent foundation — but a secure baseline doesn't emerge on its own. Here are the patterns we apply consistently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guards First: Locked by Default
&lt;/h3&gt;

&lt;p&gt;Every route is locked by default. Authentication and authorization run through Guards, never through ad-hoc checks in controllers. The critical inversion: routes must explicitly opt into being public, not opt out of being protected.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.guard.ts — global guard, no route is public by default&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;JwtAuthGuard&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AuthGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jwt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPublic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reflector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;IS_PUBLIC_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHandler&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPublic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// main.ts — registered globally&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useGlobalGuards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JwtAuthGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reflector&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Routes that should be public are explicitly decorated&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Public&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Input Validation with class-validator
&lt;/h3&gt;

&lt;p&gt;All incoming data is validated through DTOs with &lt;code&gt;class-validator&lt;/code&gt; and &lt;code&gt;class-transformer&lt;/code&gt;. Whitelist mode is active: properties not defined in the DTO are automatically stripped. &lt;code&gt;forbidNonWhitelisted&lt;/code&gt; turns unknown properties into a 400 error, making it immediately obvious when a client is sending unexpected data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main.ts&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useGlobalPipes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ValidationPipe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;whitelist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// strip unknown props&lt;/span&gt;
  &lt;span class="na"&gt;forbidNonWhitelisted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// or throw exception&lt;/span&gt;
  &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// auto-cast types&lt;/span&gt;
  &lt;span class="na"&gt;transformOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;enableImplicitConversion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;  &lt;span class="c1"&gt;// stay explicit&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// dto/create-profile.dto.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProfileDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Transform&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;sanitizeHtml&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsEnum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ProfileCategory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ProfileCategory&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsOptional&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;MaxLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Rate Limiting and Throttling
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;@nestjs/throttler&lt;/code&gt; we limit requests per IP and per endpoint. Login endpoints have particularly aggressive limits. After five failed attempts, the IP is temporarily blocked with exponential backoff. Additionally, nginx sits in front of the application with &lt;code&gt;limit_req_zone&lt;/code&gt; as a first line of defense — it blocks volumetric attacks before they ever reach NestJS.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// app.module.ts&lt;/span&gt;
&lt;span class="nx"&gt;ThrottlerModule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forRoot&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]),&lt;/span&gt;

&lt;span class="c1"&gt;// auth.controller.ts — stricter limit on login&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Throttle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;short&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ttl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;dto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security Headers with Helmet
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;helmet&lt;/code&gt; is mandatory — but configured, not just switched on. Content Security Policy is locked down to our specific domains. HSTS is set with a long max-age. X-Frame-Options prevents clickjacking.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;helmet&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;contentSecurityPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;directives&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;defaultSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'self'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;imgSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'self'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdn.our-domain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;scriptSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'self'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;styleSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'self'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'unsafe-inline'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;// only where unavoidable&lt;/span&gt;
      &lt;span class="na"&gt;connectSrc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'self'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://api.our-domain.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;frameAncestors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;'none'&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;hsts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;31536000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;includeSubDomains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;preload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;referrerPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;strict-origin-when-cross-origin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Audit Logging
&lt;/h3&gt;

&lt;p&gt;Every sensitive action — login, logout, profile update, payment, admin action — is written to an append-only audit log with user ID, IP, timestamp, and action details. This log is stored separately from the application database and is write-only from the application's perspective. Even if the main database is compromised, the audit trail remains intact.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. React Router: Security on the Frontend
&lt;/h2&gt;

&lt;p&gt;Frontend security is routinely neglected. Yet the frontend is the first attack surface the user sees — and therefore the primary vector for social engineering, XSS, and token theft.&lt;/p&gt;

&lt;h3&gt;
  
  
  Route-Level Authorization
&lt;/h3&gt;

&lt;p&gt;With React Router v7 we implement a &lt;code&gt;ProtectedRoute&lt;/code&gt; wrapper that checks authentication and authorization state before any route renders. Token refresh happens automatically and transparently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// components/ProtectedRoute.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProtectedRoute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;requiredRole&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;requiredRole&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Role&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useAuth&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;LoadingSpinner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Navigate&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/login"&lt;/span&gt; &lt;span class="na"&gt;replace&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requiredRole&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requiredRole&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Navigate&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/unauthorized"&lt;/span&gt; &lt;span class="na"&gt;replace&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Outlet&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// routes.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtectedRoute&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
    &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Dashboard&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ProtectedRoute&lt;/span&gt; &lt;span class="na"&gt;requiredRole&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ADMIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
        &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AdminPanel&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Token Storage: Never localStorage
&lt;/h3&gt;

&lt;p&gt;JWTs are never stored in &lt;code&gt;localStorage&lt;/code&gt;. That's an open XSS entry point — any XSS on the page, including from a third-party script, can steal that token. Our approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Refresh tokens&lt;/strong&gt;: stored as &lt;code&gt;httpOnly&lt;/code&gt;, &lt;code&gt;Secure&lt;/code&gt;, &lt;code&gt;SameSite=Strict&lt;/code&gt; cookies. JavaScript cannot access them at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access tokens&lt;/strong&gt;: stored only in memory (React Context / Zustand store). Short lifetime of 15 minutes. Gone on page reload, automatically refreshed via the httpOnly cookie.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.context.tsx&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AuthState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// in-memory only&lt;/span&gt;
  &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useAuthStore&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AuthState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Refresh token is sent automatically via httpOnly cookie&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/auth/refresh&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;include&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nf"&gt;setState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Common mistake:&lt;/strong&gt; &lt;code&gt;localStorage.setItem('token', jwt)&lt;/code&gt; is the most popular security error in frontend development. Any XSS anywhere on the page — including in an embedded third-party script — can exfiltrate that token.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  CSP Enforced by the Backend
&lt;/h3&gt;

&lt;p&gt;Content Security Policy is set as an HTTP header — never as a meta tag. Meta tags can be bypassed via DOM injection. Inline scripts are forbidden. External scripts may only load from explicitly allowed domains. This is set in the NestJS helmet configuration and applies to every response.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Live Messaging with End-to-End Encryption
&lt;/h2&gt;

&lt;p&gt;The messaging system is the most security-critical feature of our platform. Users expect absolute confidentiality. We implement real end-to-end encryption — the server never sees the plaintext of any message.&lt;/p&gt;

&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;p&gt;Every user generates an asymmetric key pair on their first login: X25519 for key exchange, AES-256-GCM for message encryption. The private key never leaves the user's device. Only the public key is stored on the server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// crypto.service.ts (client-side, using WebCrypto API)&lt;/span&gt;

&lt;span class="c1"&gt;// Generate key pair on first login&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateUserKeyPair&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;CryptoKeyPair&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X25519&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deriveKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Export public key for server storage&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;exportPublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CryptoKey&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exportKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;publicKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;btoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromCharCode&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Private key is encrypted with a user-derived key and stored in IndexedDB&lt;/span&gt;
&lt;span class="c1"&gt;// It never travels to the server&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;storePrivateKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CryptoKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userDerivedKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CryptoKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exported&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exportKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jwk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;privateKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;userDerivedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exported&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;privateKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending a Message: ECDH Key Exchange
&lt;/h3&gt;

&lt;p&gt;When sending a message, the sender performs an ECDH key exchange with the recipient's public key, derives a shared session key, and encrypts the message with it. The result — ciphertext plus an ephemeral public key — is sent over WebSocket and stored on the server. The server is a pure transport and storage layer for encrypted blobs. It cannot read any message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encryptMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;recipientPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CryptoKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;senderPrivateKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CryptoKey&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EncryptedMessage&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Derive shared secret via ECDH&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sharedKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deriveKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;X25519&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;public&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;recipientPublicKey&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;senderPrivateKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;256&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;encrypt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ciphertext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AES-GCM&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;sharedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bufferToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ciphertext&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;bufferToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// Include ephemeral sender public key so recipient can derive the same shared secret&lt;/span&gt;
    &lt;span class="na"&gt;senderEphemeralPublicKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;exportPublicKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recipientPublicKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  WebSocket Security
&lt;/h3&gt;

&lt;p&gt;WebSocket connections are only accepted over WSS (TLS). A valid JWT is validated during the handshake, before the upgrade completes. Every message includes a timestamp and sequence number — replay attacks are detected and rejected. NestJS WebSocket Gateway handles this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;WebSocketGateway&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;transports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;websocket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MessagingGateway&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnGatewayConnection&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;handleConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;handshake&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Forward Secrecy matters&lt;/strong&gt;: We use ephemeral keys per session. Even if a user's long-term private key is eventually compromised, past messages remain encrypted. This is a non-negotiable property for a platform handling sensitive communications.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. Data Encryption at Rest and in Transit
&lt;/h2&gt;

&lt;h3&gt;
  
  
  In Transit: TLS 1.3 Only
&lt;/h3&gt;

&lt;p&gt;All HTTP connections enforce TLS 1.3. TLS 1.0 and 1.1 are disabled. TLS 1.2 remains active as a fallback for older clients but is deprioritized in cipher negotiation. Certificates come from Let's Encrypt with automated renewal. Certificate Transparency is active — any unexpected certificate issuance for our domains triggers an alert.&lt;/p&gt;

&lt;p&gt;Our nginx TLS configuration achieves an A+ on SSL Labs. The relevant settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:...'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_session_timeout&lt;/span&gt; &lt;span class="s"&gt;1d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_session_cache&lt;/span&gt; &lt;span class="s"&gt;shared:SSL:50m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_session_tickets&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_stapling&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ssl_stapling_verify&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  At Rest: Field-Level Encryption for Sensitive Data
&lt;/h3&gt;

&lt;p&gt;Full disk encryption on the infrastructure level is a given, not a substitute for application-level encryption. Sensitive database fields — phone numbers, real names where stored, payment-related identifiers — are encrypted at the application level before being written to the database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// encryption.service.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EncryptionService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Key loaded from Vault at startup, never from env vars&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ENCRYPTION_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;EncryptedField&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;plaintext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthTag&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;decrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EncryptedField&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDecipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;algorithm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;decipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAuthTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;decipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;decipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Secret Management
&lt;/h3&gt;

&lt;p&gt;No secrets in CI/CD logs. No secrets in code repositories. No secrets in environment variables that get passed around carelessly. We use HashiCorp Vault for all secret management with automatic rotation. Application secrets are loaded at startup and never logged. Database credentials rotate every 30 days automatically. If a credential leaks, the blast radius is bounded in time.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Incident Response and Monitoring
&lt;/h2&gt;

&lt;p&gt;Security is not a set-and-forget problem. An attack will come. The question is how quickly you detect it and how coordinated your response is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Alerting Stack
&lt;/h3&gt;

&lt;p&gt;We run a layered monitoring stack: application logs go to Loki, metrics to Prometheus, dashboards in Grafana. Critical events — failed login attempts above threshold, unusual traffic spikes, 5xx rates above 0.1%, anomalous message volume — trigger immediate PagerDuty alerts with on-call escalation.&lt;/p&gt;

&lt;p&gt;Specifically, we alert on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More than 10 failed logins from a single IP in 5 minutes&lt;/li&gt;
&lt;li&gt;Any admin action outside business hours&lt;/li&gt;
&lt;li&gt;Database query time spiking above p99 baseline (potential injection causing table scans)&lt;/li&gt;
&lt;li&gt;A new IP accessing more than 100 profiles in 10 minutes (scraper pattern)&lt;/li&gt;
&lt;li&gt;Any 5xx response from the payments service&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Anomaly Detection
&lt;/h3&gt;

&lt;p&gt;ML-based anomaly detection sounds sophisticated but in practice often starts as simple pattern matching: a user generating 500 profile views in 5 minutes is not a human. An IP systematically iterating sequential numeric user IDs is a scanner. These patterns are detected and lead to automatic temporary blocks. More sophisticated behavioral analytics (session velocity, geographic impossibility) layer on top as the platform scales.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incident Response Playbook
&lt;/h3&gt;

&lt;p&gt;Every team needs a written playbook — even if you hope never to use it. Our playbook defines: first response within 15 minutes of a P1 alert, isolation of the affected system, internal communication chain, external communication obligations (GDPR: 72-hour reporting deadline to the supervisory authority in the event of a data breach), and a mandatory post-mortem process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Run tabletop exercises&lt;/strong&gt;: "What do we do if the database is compromised tomorrow morning?" A playbook that only exists on paper is worth little in a real incident. We run a 90-minute tabletop exercise every quarter.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  9. Security Checklist to Take Home
&lt;/h2&gt;

&lt;p&gt;A summary of every control described in this article, usable as a review checklist for your own platform:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure &amp;amp; Pipeline&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Three strictly separated environments (Dev / Staging / Prod) with independent secrets&lt;/li&gt;
&lt;li&gt;[ ] No manual deployment to production — CI/CD pipeline only&lt;/li&gt;
&lt;li&gt;[ ] SAST, dependency audit, and container scan as pipeline gates that block on failure&lt;/li&gt;
&lt;li&gt;[ ] DAST against staging after every deployment&lt;/li&gt;
&lt;li&gt;[ ] Infrastructure-as-Code with per-environment state files&lt;/li&gt;
&lt;li&gt;[ ] Secret rotation via Vault — no static, long-lived credentials&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend (NestJS)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Global auth guard: routes opt-in to being public, not opt-out of being protected&lt;/li&gt;
&lt;li&gt;[ ] ValidationPipe with &lt;code&gt;whitelist: true&lt;/code&gt; on all endpoints&lt;/li&gt;
&lt;li&gt;[ ] Rate limiting on both application and nginx level&lt;/li&gt;
&lt;li&gt;[ ] Helmet with restrictive CSP — no wildcard inline scripts&lt;/li&gt;
&lt;li&gt;[ ] Field-level encryption for sensitive database columns (AES-256-GCM)&lt;/li&gt;
&lt;li&gt;[ ] Append-only audit log stored separately from the main database&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Frontend (React Router)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Route-level authorization via ProtectedRoute wrappers&lt;/li&gt;
&lt;li&gt;[ ] JWT: refresh token as httpOnly cookie, access token in-memory only — never localStorage&lt;/li&gt;
&lt;li&gt;[ ] CSP enforced as HTTP header from the backend, not as meta tag&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Messaging&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] End-to-end encryption: server never sees message plaintext&lt;/li&gt;
&lt;li&gt;[ ] Ephemeral keys per session for forward secrecy&lt;/li&gt;
&lt;li&gt;[ ] WebSocket auth validated on handshake before upgrade completes&lt;/li&gt;
&lt;li&gt;[ ] Replay attack protection via timestamp + sequence number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] TLS 1.3, HSTS with preload, Certificate Transparency monitoring&lt;/li&gt;
&lt;li&gt;[ ] WAF active in production with tuned ruleset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Operations&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Real-time monitoring with defined alert thresholds&lt;/li&gt;
&lt;li&gt;[ ] Written incident response playbook&lt;/li&gt;
&lt;li&gt;[ ] GDPR breach notification process documented and tested&lt;/li&gt;
&lt;li&gt;[ ] Quarterly tabletop exercises&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing: Security as Culture, Not as Feature
&lt;/h2&gt;

&lt;p&gt;Technical measures alone aren't enough. What truly protects is a culture where security is understood as a quality trait, not a brake on velocity. No developer on the team should find the question "is this secure?" annoying — it's part of the definition of done.&lt;/p&gt;

&lt;p&gt;For platforms in the adult industry, this holds with particular force. Users entrust us with data that directly affects their lives. That trust is non-negotiable. And neither is the technical foundation that supports it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We are the engineering team behind one &lt;a href="http://bb-escort.de" rel="noopener noreferrer"&gt;bb-escort.de&lt;/a&gt;. On dev.to we share what we've learned scaling, securing, and building our platform — unvarnished and practical.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have a question about any of the patterns described here? Drop it in the comments — we read everything.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cybersecurity</category>
      <category>node</category>
      <category>security</category>
    </item>
    <item>
      <title>Why We Switched from Next.js to React Router 7 (And Don’t Regret It)</title>
      <dc:creator>BB Dev Team</dc:creator>
      <pubDate>Sun, 01 Mar 2026 16:31:12 +0000</pubDate>
      <link>https://dev.to/bbescort-team/why-we-switched-from-nextjs-to-react-router-7-and-dont-regret-it-25ah</link>
      <guid>https://dev.to/bbescort-team/why-we-switched-from-nextjs-to-react-router-7-and-dont-regret-it-25ah</guid>
      <description>&lt;p&gt;Over the past few years, our platform evolved from a fast-moving startup stack into a performance- and infrastructure-sensitive system.&lt;/p&gt;

&lt;p&gt;We started with Next.js — like many teams do. It’s powerful, mature, and incredibly productive.&lt;/p&gt;

&lt;p&gt;But as our requirements grew more specific, we increasingly felt constrained by framework assumptions around rendering, deployment, and infrastructure.&lt;/p&gt;

&lt;p&gt;Eventually, we made the switch to React Router 7.&lt;/p&gt;

&lt;p&gt;Here’s why — and what changed for us.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. We Use React Server Components — Without Traditional Page SSR
&lt;/h2&gt;

&lt;p&gt;Leaving Next.js doesn’t mean abandoning advanced rendering.&lt;/p&gt;

&lt;p&gt;With React Router 7, we use React Server Components (RSC) — but without being tied to a page-based SSR architecture.&lt;/p&gt;

&lt;p&gt;What this gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server-rendered components where it makes sense
&lt;/li&gt;
&lt;li&gt;Streaming support
&lt;/li&gt;
&lt;li&gt;Reduced client hydration overhead
&lt;/li&gt;
&lt;li&gt;More granular server/client boundaries
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional SSR often means rendering entire pages on the server and hydrating everything on the client.&lt;/p&gt;

&lt;p&gt;With RSC and React Router’s data APIs, we can be more surgical.&lt;/p&gt;

&lt;p&gt;In practice, this improved client performance because we reduced unnecessary hydration and shipped less JavaScript to the browser.&lt;/p&gt;

&lt;p&gt;Next.js gives you powerful defaults.&lt;br&gt;&lt;br&gt;
React Router gives you explicit control.&lt;/p&gt;

&lt;p&gt;At our scale, explicit control wins.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. True Middleware Patterns — Fully Integrated
&lt;/h2&gt;

&lt;p&gt;A common assumption is that Next.js has “real” middleware while React Router does not.&lt;/p&gt;

&lt;p&gt;That’s outdated.&lt;/p&gt;

&lt;p&gt;React Router 7 provides fully integrated request handling via:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;loaders&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;actions&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;standard &lt;code&gt;Request&lt;/code&gt; / &lt;code&gt;Response&lt;/code&gt; objects&lt;/li&gt;
&lt;li&gt;nested route data APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These patterns allow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication guards
&lt;/li&gt;
&lt;li&gt;Geo-based logic
&lt;/li&gt;
&lt;li&gt;Logging and observability
&lt;/li&gt;
&lt;li&gt;Access control
&lt;/li&gt;
&lt;li&gt;Request transformations
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because everything is route-scoped and explicit, it’s easier to reason about.&lt;/p&gt;

&lt;p&gt;We don’t rely on edge-only middleware or special folder conventions. We write standard request logic in predictable places.&lt;/p&gt;

&lt;p&gt;In practice, this feels cleaner and more composable.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. SPA Mode — When We Need It
&lt;/h2&gt;

&lt;p&gt;Another major advantage: React Router 7 allows us to run certain parts of our system in pure SPA mode.&lt;/p&gt;

&lt;p&gt;This is extremely useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal dashboards
&lt;/li&gt;
&lt;li&gt;Admin tools
&lt;/li&gt;
&lt;li&gt;Authenticated application areas
&lt;/li&gt;
&lt;li&gt;Feature-heavy UI sections
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Disable server rendering where it’s not needed
&lt;/li&gt;
&lt;li&gt;Ship static assets
&lt;/li&gt;
&lt;li&gt;Optimize purely for client interactivity
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Next.js, the architecture is always SSR-first (even when using client components heavily).&lt;/p&gt;

&lt;p&gt;React Router gives us freedom:&lt;/p&gt;

&lt;p&gt;We can choose SSR, RSC, streaming, or pure SPA — per application or even per route.&lt;/p&gt;

&lt;p&gt;That flexibility matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. We Recommend Using Framework Mode
&lt;/h2&gt;

&lt;p&gt;One important note: we strongly recommend using React Router 7 in &lt;strong&gt;Framework Mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;While React Router can be used as a minimal routing library, Framework Mode unlocks its full potential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built-in data loading patterns
&lt;/li&gt;
&lt;li&gt;Integrated server handling
&lt;/li&gt;
&lt;li&gt;Structured routing conventions
&lt;/li&gt;
&lt;li&gt;Production-ready architecture out of the box
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It removes a lot of boilerplate and architectural decisions you would otherwise have to reinvent.&lt;/p&gt;

&lt;p&gt;At the same time, it doesn’t lock you into rigid conventions. The system remains highly adjustable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You control hosting
&lt;/li&gt;
&lt;li&gt;You control rendering strategy
&lt;/li&gt;
&lt;li&gt;You control deployment targets
&lt;/li&gt;
&lt;li&gt;You control infrastructure
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For us, Framework Mode hits the perfect balance:&lt;/p&gt;

&lt;p&gt;Less repetitive setup work —&lt;br&gt;&lt;br&gt;
but still full architectural sovereignty.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. It’s Lightweight — In Dev and Production
&lt;/h2&gt;

&lt;p&gt;Next.js is a full-stack meta-framework.&lt;/p&gt;

&lt;p&gt;React Router is a routing and data orchestration layer.&lt;/p&gt;

&lt;p&gt;That difference shows in bundle size and runtime complexity.&lt;/p&gt;

&lt;p&gt;What we observed after migrating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Smaller production bundles
&lt;/li&gt;
&lt;li&gt;Less framework runtime logic
&lt;/li&gt;
&lt;li&gt;Cleaner dependency graph
&lt;/li&gt;
&lt;li&gt;Faster CI builds
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There’s simply less abstraction between us and React.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Faster Dev Environment
&lt;/h2&gt;

&lt;p&gt;We use Vite for development, which significantly improved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold start times
&lt;/li&gt;
&lt;li&gt;HMR speed
&lt;/li&gt;
&lt;li&gt;Debug clarity
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One honest note:&lt;/p&gt;

&lt;p&gt;Vite does not fully tree-shake in development mode.&lt;/p&gt;

&lt;p&gt;Some of our dev pages can grow large (we’ve seen ~15MB), especially when using icon libraries.&lt;/p&gt;

&lt;p&gt;However:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This only affects development
&lt;/li&gt;
&lt;li&gt;Production builds are optimized correctly
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Overall, the dev feedback loop is still faster and more transparent than before.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Faster Bugfix Turnaround
&lt;/h2&gt;

&lt;p&gt;From our experience:&lt;/p&gt;

&lt;p&gt;React Router maintainers respond quickly — often within days from issue report to fix.&lt;/p&gt;

&lt;p&gt;With larger frameworks like Next.js, turnaround can take weeks or longer. This is understandable given project size and ecosystem scope.&lt;/p&gt;

&lt;p&gt;But when your business depends on framework stability, responsiveness matters.&lt;/p&gt;

&lt;p&gt;Smaller, focused projects often move faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Self-Hosting &amp;amp; Geo Clustering
&lt;/h2&gt;

&lt;p&gt;We host and geo-cluster our infrastructure ourselves.&lt;/p&gt;

&lt;p&gt;We require:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Custom regional routing
&lt;/li&gt;
&lt;li&gt;Infrastructure-level SEO optimization
&lt;/li&gt;
&lt;li&gt;Strict data control
&lt;/li&gt;
&lt;li&gt;Full observability
&lt;/li&gt;
&lt;li&gt;Predictable scaling costs
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next.js increasingly assumes Vercel as the optimal deployment target.&lt;/p&gt;

&lt;p&gt;React Router makes zero assumptions about hosting.&lt;/p&gt;

&lt;p&gt;We can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run on Node
&lt;/li&gt;
&lt;li&gt;Deploy to edge runtimes
&lt;/li&gt;
&lt;li&gt;Use containers
&lt;/li&gt;
&lt;li&gt;Geo-cluster however we want
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No vendor lock-in.&lt;br&gt;&lt;br&gt;
No deployment pressure.&lt;br&gt;&lt;br&gt;
No hidden coupling.&lt;/p&gt;

&lt;p&gt;That independence is extremely valuable.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Clearer Mental Model
&lt;/h2&gt;

&lt;p&gt;This might be subjective — but important.&lt;/p&gt;

&lt;p&gt;With React Router:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What we write is what runs.
&lt;/li&gt;
&lt;li&gt;There’s less invisible framework behavior.
&lt;/li&gt;
&lt;li&gt;Routing and data are explicit.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In large systems, clarity beats convenience.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. What Next.js Still Does Well
&lt;/h2&gt;

&lt;p&gt;To be fair, Next.js is excellent at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quick startup productivity
&lt;/li&gt;
&lt;li&gt;Batteries-included defaults
&lt;/li&gt;
&lt;li&gt;Strong ecosystem adoption
&lt;/li&gt;
&lt;li&gt;Opinionated structure for smaller teams
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many projects, it’s absolutely the right choice.&lt;/p&gt;

&lt;p&gt;But once you need architectural sovereignty, the trade-offs become more noticeable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Switched
&lt;/h2&gt;

&lt;p&gt;Our platform operates in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SEO-sensitive environments
&lt;/li&gt;
&lt;li&gt;Performance-critical markets
&lt;/li&gt;
&lt;li&gt;Privacy-sensitive domains
&lt;/li&gt;
&lt;li&gt;Regionally distributed infrastructure
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rendering flexibility (RSC + SPA)
&lt;/li&gt;
&lt;li&gt;Integrated middleware control
&lt;/li&gt;
&lt;li&gt;Hosting independence
&lt;/li&gt;
&lt;li&gt;Lightweight runtime
&lt;/li&gt;
&lt;li&gt;Faster iteration cycles
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;React Router 7 gave us exactly that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Switching frameworks is never trivial.&lt;/p&gt;

&lt;p&gt;But sometimes growth forces you to reconsider architectural constraints.&lt;/p&gt;

&lt;p&gt;For us, moving from Next.js to React Router 7 wasn’t about hype.&lt;/p&gt;

&lt;p&gt;It was about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Control.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Clarity.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Independence.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And so far, we’re very happy with the decision.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>reactrouter</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a Payment Provider: The Hidden Technical Complexity</title>
      <dc:creator>BB Dev Team</dc:creator>
      <pubDate>Wed, 03 Sep 2025 18:18:28 +0000</pubDate>
      <link>https://dev.to/bbescort-team/-building-a-payment-provider-the-hidden-technical-complexity-28o0</link>
      <guid>https://dev.to/bbescort-team/-building-a-payment-provider-the-hidden-technical-complexity-28o0</guid>
      <description>&lt;p&gt;Most companies integrate with existing payment providers (Stripe, Adyen, PayPal).&lt;br&gt;&lt;br&gt;
But what if you need to build your &lt;strong&gt;own payment provider&lt;/strong&gt;?  &lt;/p&gt;

&lt;p&gt;We went down this path, and it turned out to be one of the hardest technical challenges we’ve faced.  &lt;/p&gt;

&lt;p&gt;Here’s a breakdown of the complexity that comes with building a system that moves money reliably and securely at scale.  &lt;/p&gt;

&lt;p&gt;What you can expect: no standardized, old interface, many special regulations and lots and lots of testing ;)&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Multi-Layered Interfaces
&lt;/h2&gt;

&lt;p&gt;A payment provider isn’t one API – it’s many:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Card Networks (Visa, Mastercard, Amex):&lt;/strong&gt; Integration with different protocols, settlement, and reconciliation.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alternative Payments:&lt;/strong&gt; Bank transfers, wallets, crypto, vouchers.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Merchant Interfaces:&lt;/strong&gt; APIs and dashboards for external clients.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regulatory APIs:&lt;/strong&gt; KYC, AML, fraud monitoring.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every integration has different &lt;strong&gt;standards, formats, and SLAs&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Your system becomes the glue between dozens of protocols.  &lt;/p&gt;




&lt;h2&gt;
  
  
  2. Subscription &amp;amp; Billing Management
&lt;/h2&gt;

&lt;p&gt;Recurring payments are much harder than one-time transactions.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subscriptions:&lt;/strong&gt; Track cycles, retries, upgrades, downgrades, cancellations.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invoices:&lt;/strong&gt; Generate legal documents in multiple jurisdictions.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prorations:&lt;/strong&gt; Handle when a user changes a plan mid-cycle.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dunning Logic:&lt;/strong&gt; Retry failed payments intelligently without spamming banks.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple Currencies:&lt;/strong&gt; Exchange rates, rounding rules, settlement differences.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We ended up building a dedicated &lt;strong&gt;billing microservice&lt;/strong&gt; to handle this logic.  &lt;/p&gt;




&lt;h2&gt;
  
  
  3. Data Storage &amp;amp; Security
&lt;/h2&gt;

&lt;p&gt;Payment data is the most sensitive data you can hold.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PCI DSS Compliance:&lt;/strong&gt; Storage of cardholder data requires tokenization and encryption.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vaults:&lt;/strong&gt; Sensitive card data must never touch the core DB – it’s stored in isolated vault services.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption-at-Rest + In-Transit:&lt;/strong&gt; All data is encrypted multiple times.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Retention:&lt;/strong&gt; Store as little as legally possible.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit Trails:&lt;/strong&gt; Every read/write must be logged and immutable.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We designed a &lt;strong&gt;segmented database architecture&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encrypted vault DB for card data.
&lt;/li&gt;
&lt;li&gt;Transaction DB for operational data.
&lt;/li&gt;
&lt;li&gt;Analytics DB for reporting.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Fraud Detection &amp;amp; Risk Management
&lt;/h2&gt;

&lt;p&gt;A payment provider must detect and prevent fraud in real time.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Velocity Checks:&lt;/strong&gt; Too many transactions in short time.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral Analysis:&lt;/strong&gt; Unusual patterns compared to history.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device Fingerprinting:&lt;/strong&gt; Identify suspicious devices.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Machine Learning Models:&lt;/strong&gt; Risk scoring on each transaction.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every millisecond counts – fraud detection must run &lt;strong&gt;inline with transactions&lt;/strong&gt;.  &lt;/p&gt;




&lt;h2&gt;
  
  
  5. Reliability &amp;amp; Idempotency
&lt;/h2&gt;

&lt;p&gt;Payments are &lt;strong&gt;state machines&lt;/strong&gt; – and distributed systems love to break.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency Keys:&lt;/strong&gt; Critical to prevent duplicate charges when retries happen.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ledger System:&lt;/strong&gt; Every transaction must be immutable, reversible only with compensating entries.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; Even under failure, your books must reconcile to the cent.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial Failures:&lt;/strong&gt; One bank might succeed, another fails – the system must handle rollback scenarios gracefully.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We implemented a &lt;strong&gt;double-entry ledger&lt;/strong&gt; with immutable transaction logs to guarantee reconciliation.  &lt;/p&gt;




&lt;h2&gt;
  
  
  6. Developer Experience
&lt;/h2&gt;

&lt;p&gt;Your clients (merchants) expect:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean &lt;strong&gt;REST + Webhook APIs&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDKs&lt;/strong&gt; in multiple languages.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sandbox Mode&lt;/strong&gt; for testing.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-Time Dashboard&lt;/strong&gt; for transactions and disputes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a developer-friendly product requires as much effort as the core payment logic itself.  &lt;/p&gt;




&lt;h2&gt;
  
  
  7. Scaling &amp;amp; Compliance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High Throughput:&lt;/strong&gt; Payments must clear in milliseconds, even under peak load.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global Scale:&lt;/strong&gt; Data residency laws → different storage per region.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance:&lt;/strong&gt; PSD2, PCI DSS, GDPR – each with strict requirements.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disaster Recovery:&lt;/strong&gt; Active-active datacenters, multi-region replication.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system must be designed for &lt;strong&gt;99.999% availability&lt;/strong&gt;, because downtime means lost money.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a payment provider is not “just another API.”&lt;br&gt;&lt;br&gt;
It’s a &lt;strong&gt;network of integrations, a secure vault, a billing engine, a fraud system, and a financial ledger&lt;/strong&gt; – all in one.  &lt;/p&gt;

&lt;p&gt;While integrating Stripe takes a few hours, &lt;strong&gt;building Stripe takes years&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;For us, this has been one of the most demanding engineering challenges – but also one of the most rewarding.  &lt;/p&gt;

&lt;p&gt;It forces you to think about:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security by design.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability in distributed systems.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance at financial scale.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And in the end, you realize: &lt;em&gt;Money never sleeps – and neither can your payment system.&lt;/em&gt;  &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Next.js and the Evil Web Vitals - a ranking killer?</title>
      <dc:creator>BB Dev Team</dc:creator>
      <pubDate>Wed, 03 Sep 2025 18:13:05 +0000</pubDate>
      <link>https://dev.to/bbescort-team/nextjs-and-the-evil-web-vitals-a-ranking-killer-2jc7</link>
      <guid>https://dev.to/bbescort-team/nextjs-and-the-evil-web-vitals-a-ranking-killer-2jc7</guid>
      <description>&lt;p&gt;Building a large-scale platform in a highly competitive industry means two things:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Visibility is survival&lt;/strong&gt; (SEO, SSR, structured data).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance is ranking&lt;/strong&gt; (Core Web Vitals decide who’s on top).
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In this article, we’ll share why we chose &lt;strong&gt;Next.js 15 (with Turbo &amp;amp; App Router)&lt;/strong&gt; as the foundation of our frontend, how we leverage it for &lt;strong&gt;SEO, SSR, and PWA&lt;/strong&gt;, and how we overcame performance challenges like &lt;strong&gt;INP&lt;/strong&gt; (Interaction to Next Paint) and &lt;strong&gt;FCP&lt;/strong&gt; (First Contentful Paint).  &lt;/p&gt;




&lt;h2&gt;
  
  
  Why Next.js for Our Platform?
&lt;/h2&gt;

&lt;p&gt;Our requirements are very specific:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Server-Side Rendering (SSR):&lt;/strong&gt; Critical for SEO-heavy pages that need to be indexed fast.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Generation + Revalidation:&lt;/strong&gt; Balance between speed and freshness of content.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressive Web App (PWA):&lt;/strong&gt; We use &lt;a href="https://serwist.pages.dev/" rel="noopener noreferrer"&gt;&lt;strong&gt;Serwist&lt;/strong&gt;&lt;/a&gt; to add offline caching, push notifications, and app-like behavior.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internationalization (i18n):&lt;/strong&gt; Our audience is global – Next.js handles routing and language detection elegantly.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Routes:&lt;/strong&gt; Simplify integration with backend services.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App Router + Turbo:&lt;/strong&gt; Cleaner routing, faster builds, and improved scalability.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next.js gives us the best of both worlds: SSR for crawlers and static caching for humans.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Challenges in Our Industry
&lt;/h2&gt;

&lt;p&gt;We operate in a very competitive market where &lt;strong&gt;SEO is extremely hard-fought&lt;/strong&gt;. Every millisecond and every snippet of metadata counts.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Load Times:&lt;/strong&gt; Bounce rates skyrocket if users wait.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crawling Efficiency:&lt;/strong&gt; Google punishes slow sites with poor Web Vitals.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta Data Wars:&lt;/strong&gt; Correct OpenGraph tags decide whether a page gets shared.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured Data:&lt;/strong&gt; JSON-LD is essential for rich snippets in search results.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s why we implemented a full &lt;strong&gt;SEO toolkit&lt;/strong&gt; in our platform:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic &lt;strong&gt;meta tags&lt;/strong&gt; and &lt;strong&gt;OpenGraph&lt;/strong&gt; tags.
&lt;/li&gt;
&lt;li&gt;Structured data with &lt;strong&gt;JSON-LD&lt;/strong&gt; (&lt;code&gt;Article&lt;/code&gt;, &lt;code&gt;BreadcrumbList&lt;/code&gt;, &lt;code&gt;VideoObject&lt;/code&gt;, etc.).
&lt;/li&gt;
&lt;li&gt;Automated &lt;strong&gt;sitemap.xml&lt;/strong&gt; and &lt;strong&gt;RSS feeds&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Canonical tags to avoid duplicate indexing.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Performance Problems with Next.js
&lt;/h2&gt;

&lt;p&gt;Next.js is powerful, but it comes with challenges:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;INP (Interaction to Next Paint):&lt;/strong&gt; Hydration can delay interaction readiness.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FCP (First Contentful Paint):&lt;/strong&gt; JavaScript-heavy pages delay first paint.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundle Size:&lt;/strong&gt; Next.js includes a lot of code by default. If you don’t monitor carefully, it explodes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;next/image Issues:&lt;/strong&gt; While useful, it’s often too slow for our needs and adds extra runtime overhead.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At scale, these issues directly impact &lt;strong&gt;rankings and conversions&lt;/strong&gt;.  &lt;/p&gt;




&lt;h2&gt;
  
  
  How We Tamed the Web Vitals
&lt;/h2&gt;

&lt;p&gt;We approached performance systematically:  &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Aggressive Caching
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CDN Caching&lt;/strong&gt; for static assets.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental Static Regeneration (ISR):&lt;/strong&gt; Pages revalidate every X seconds → always fresh, never slow.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getStaticProps&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; 
    &lt;span class="na"&gt;revalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// page is regenerated every 60 seconds&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Partial Caching with Revalidate Keys
&lt;/h3&gt;

&lt;p&gt;Since we are a platform with highly dynamic content, &lt;strong&gt;full caching is not possible&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Our solution: &lt;strong&gt;partial caching&lt;/strong&gt;.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static sections of a page (layout, headers, footers, metadata) are cached aggressively.
&lt;/li&gt;
&lt;li&gt;Dynamic sections (recommendations, user feeds, trending data) are updated frequently.
&lt;/li&gt;
&lt;li&gt;We rely heavily on &lt;strong&gt;revalidate keys&lt;/strong&gt; to refresh only the parts that change.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Example with revalidateTag in Next.js 15&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;revalidateTag&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next/cache&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nf"&gt;revalidateTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`user-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// revalidate only specific user cache&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;revalidated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach allows us to serve &lt;strong&gt;fast cached pages&lt;/strong&gt; while keeping dynamic data fresh.  &lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Golden Strategy: Async Loading
&lt;/h3&gt;

&lt;p&gt;Our principle is simple: &lt;strong&gt;load everything asynchronously except what’s visible above-the-fold on first render&lt;/strong&gt;.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Critical UI (headers, navigation, hero content) loads instantly.
&lt;/li&gt;
&lt;li&gt;Everything else (widgets, tracking, secondary UI) is loaded asynchronously.
&lt;/li&gt;
&lt;li&gt;This ensures fast FCP/LCP while still reducing bundle size and execution time.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Image Optimization in the Backend
&lt;/h3&gt;

&lt;p&gt;Instead of relying only on &lt;code&gt;next/image&lt;/code&gt;, our &lt;strong&gt;backend pre-generates images in multiple formats&lt;/strong&gt; (WebP, AVIF, JPEG).&lt;br&gt;&lt;br&gt;
This reduces runtime overhead and ensures the best format is delivered instantly via CDN.  &lt;/p&gt;

&lt;h3&gt;
  
  
  5. Library &amp;amp; UI Optimization
&lt;/h3&gt;

&lt;p&gt;We spend many hours profiling and optimizing &lt;strong&gt;third-party libraries&lt;/strong&gt;, especially UI components.&lt;br&gt;&lt;br&gt;
Unnecessary dependencies are removed, and only the minimal code required is bundled.  &lt;/p&gt;

&lt;h3&gt;
  
  
  6. Bundle Size Control
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We use &lt;code&gt;next-bundle-analyzer&lt;/code&gt; to keep bundle size under control.
&lt;/li&gt;
&lt;li&gt;Strict commit guidelines ensure no debug or unused code slips in.
&lt;/li&gt;
&lt;li&gt;Every KB matters, especially since Next.js ships with a heavy base.
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Fonts: Loaded Correctly via NestJS + Next.js
&lt;/h3&gt;

&lt;p&gt;Fonts can drastically impact FCP. Our strategy combines &lt;strong&gt;NestJS static serving&lt;/strong&gt; with Next.js optimization:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fonts are &lt;strong&gt;self-hosted&lt;/strong&gt; and served via NestJS static middleware.
&lt;/li&gt;
&lt;li&gt;Correct &lt;code&gt;Cache-Control&lt;/code&gt; headers ensure long-term caching.
&lt;/li&gt;
&lt;li&gt;Next.js 15’s &lt;code&gt;@next/font&lt;/code&gt; is used for automatic font-subsetting and preload links.
&lt;/li&gt;
&lt;li&gt;Above-the-fold text fonts are preloaded, while secondary fonts are loaded async.
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Results: Best Possible Web Vitals
&lt;/h2&gt;

&lt;p&gt;After multiple iterations we reached:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;INP:&lt;/strong&gt; consistently under Google’s “good” threshold.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FCP:&lt;/strong&gt; improved by caching, async loading, and font optimization.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LCP (Largest Contentful Paint):&lt;/strong&gt; optimized via backend image pre-generation, partial caching, and CDN.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our Web Vitals are now among the best in our industry, directly improving SEO and user retention.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In a competitive industry, &lt;strong&gt;SEO + Performance = Survival&lt;/strong&gt;.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15 (with Turbo and App Router) gives us &lt;strong&gt;SSR, SEO tools, PWA support (via Serwist), and scalability&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;The ecosystem around metadata (meta tags, OpenGraph, JSON-LD, sitemaps, RSS) makes it the best choice for visibility.
&lt;/li&gt;
&lt;li&gt;Performance challenges (INP, FCP, hydration, bundle size, next/image) are real, but solvable with &lt;strong&gt;caching, async loading, backend image optimization, and careful bundle management&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial caching with revalidate keys&lt;/strong&gt; is the secret weapon for dynamic platforms like ours.
&lt;/li&gt;
&lt;li&gt;Fonts must be handled carefully – preloaded for above-the-fold, async for the rest.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next.js may bring “evil” Web Vitals problems out of the box, but with the right strategy, you can &lt;strong&gt;tame them and even turn them into a competitive advantage&lt;/strong&gt;.  &lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>seo</category>
      <category>ssr</category>
      <category>pwa</category>
    </item>
    <item>
      <title>Security by Design with NestJS, zod, ts-rest, and Custom In-House Frameworks</title>
      <dc:creator>BB Dev Team</dc:creator>
      <pubDate>Wed, 03 Sep 2025 18:01:11 +0000</pubDate>
      <link>https://dev.to/bbescort-team/security-by-design-with-nestjs-zod-ts-rest-and-custom-in-house-frameworks-488n</link>
      <guid>https://dev.to/bbescort-team/security-by-design-with-nestjs-zod-ts-rest-and-custom-in-house-frameworks-488n</guid>
      <description>&lt;p&gt;When you build a platform that handles millions of users and sensitive data, security and reliability are not just features – they’re survival requirements.&lt;/p&gt;

&lt;p&gt;In our engineering team, we rely on &lt;strong&gt;NestJS&lt;/strong&gt; (backend) and &lt;strong&gt;Next.js&lt;/strong&gt; (frontend), but we don’t stop there. To ensure maximum security and resilience, we combine battle-tested open-source tools with &lt;strong&gt;custom in-house frameworks&lt;/strong&gt; designed specifically for our threat model and performance needs.&lt;/p&gt;

&lt;p&gt;This post gives an overview of how we approach &lt;strong&gt;data protection, authentication, validation, system reliability&lt;/strong&gt;, &lt;strong&gt;secure real-time communication&lt;/strong&gt;, and &lt;strong&gt;code security&lt;/strong&gt; at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Core Security Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Minimal Data Collection
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Every extra field increases risk.&lt;/li&gt;
&lt;li&gt;For KYC, we persist only the legally required attributes and discard everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2) Encryption Everywhere
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;At Rest&lt;/strong&gt;: All DB fields with sensitive data are encrypted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Transit&lt;/strong&gt;: TLS 1.3 + HSTS with automatic certificate rotation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Application Layer&lt;/strong&gt;: Some values (like ID scans) are encrypted before they ever reach the database.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createCipheriv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;aes-256-gcm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;encrypted&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;final&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authTag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cipher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAuthTag&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;iv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authTag&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3) Strong Authentication (2FA)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mandatory 2FA&lt;/strong&gt; for users &lt;em&gt;and&lt;/em&gt; admins.&lt;/li&gt;
&lt;li&gt;NestJS Guards make it straightforward to enforce OTP/WebAuthn or second-factor checks on sensitive routes.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CanActivate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TwoFactorGuard&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;CanActivate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;canActivate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switchToHttp&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;getRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;is2FAAuthenticated&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4) Role-Based Access Control
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Implemented via NestJS Guards and decorators.&lt;/li&gt;
&lt;li&gt;Principle: &lt;strong&gt;Least Privilege&lt;/strong&gt; – narrow, task-focused scopes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5) Auditing &amp;amp; Monitoring
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Centralized logging (e.g., ELK / Datadog) with anomaly detection.&lt;/li&gt;
&lt;li&gt;Alerts on suspicious patterns (login abuse, unusual data exports).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Request &amp;amp; Response Validation with ts-rest + Zod
&lt;/h2&gt;

&lt;p&gt;We use &lt;a href="https://github.com/ts-rest/ts-rest" rel="noopener noreferrer"&gt;&lt;strong&gt;ts-rest&lt;/strong&gt;&lt;/a&gt; combined with &lt;a href="https://github.com/colinhacks/zod" rel="noopener noreferrer"&gt;&lt;strong&gt;Zod&lt;/strong&gt;&lt;/a&gt; to strictly validate both &lt;strong&gt;requests and responses&lt;/strong&gt;. This enforces contracts between backend and frontend and prevents accidental leaks or schema mismatches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;initContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@ts-rest/core&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;initContract&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authContract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;router&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/auth/login&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;responses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="na"&gt;refreshToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reliability: Microservices &amp;amp; Proxies (API Path)
&lt;/h2&gt;

&lt;p&gt;Security without availability is meaningless. Our API services are designed for &lt;strong&gt;fault tolerance&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microservices&lt;/strong&gt;: Isolated domain services reduce blast radius and allow independent scaling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxies &amp;amp; API Gateway&lt;/strong&gt;: Centralized entry for routing, rate limiting, schema validation, and WAF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful Degradation&lt;/strong&gt;: If one service fails, proxies reroute/fallback without exposing raw errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-Downtime Deployments&lt;/strong&gt; with blue-green strategies and rolling updates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit Breakers &amp;amp; Retries&lt;/strong&gt; with idempotency keys to avoid duplicate side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: The real-time stack (Socket.IO) is &lt;strong&gt;separate&lt;/strong&gt; from the microservices runtime. See the next section.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Secure Real-Time Communication (Socket.IO Path)
&lt;/h2&gt;

&lt;p&gt;We use a &lt;strong&gt;heavily adapted version of Socket.IO&lt;/strong&gt; for &lt;strong&gt;customer chat&lt;/strong&gt; and &lt;strong&gt;live website updates&lt;/strong&gt;. This channel is &lt;strong&gt;independent from the microservices&lt;/strong&gt; and optimized for reliability and security:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All socket connections are encrypted end-to-end.&lt;/li&gt;
&lt;li&gt;Custom handshake validation prevents session hijacking.&lt;/li&gt;
&lt;li&gt;Events are schema-validated before leaving or entering the server.&lt;/li&gt;
&lt;li&gt;Proxies apply throttling and IP-based abuse detection.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why We Use In-House Frameworks
&lt;/h2&gt;

&lt;p&gt;While we like NestJS and Next.js, &lt;strong&gt;we intentionally reduce reliance on third-party open-source libraries&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Many open-source packages are under-maintained and accumulate vulnerabilities.&lt;/li&gt;
&lt;li&gt;In-house frameworks allow us to:

&lt;ul&gt;
&lt;li&gt;Control the attack surface.&lt;/li&gt;
&lt;li&gt;Patch vulnerabilities instantly.&lt;/li&gt;
&lt;li&gt;Simplify dependency management.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;We selectively integrate OSS only when the maintenance and security posture is strong – and even then, often behind our own abstraction layers.&lt;/p&gt;




&lt;h2&gt;
  
  
  UI: A Hardened Version of shadcn
&lt;/h2&gt;

&lt;p&gt;For the frontend UI, we rely on a heavily customized version of &lt;a href="https://ui.shadcn.com/" rel="noopener noreferrer"&gt;&lt;strong&gt;shadcn/ui&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It gives us a consistent, accessible component system.&lt;/li&gt;
&lt;li&gt;We stripped unnecessary dependencies and hardened components against XSS and injection risks.&lt;/li&gt;
&lt;li&gt;Our version is tightly integrated with our own design system and security checks (e.g., CSP compliance).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Code Security: Obfuscation, Commit Rules &amp;amp; Tooling
&lt;/h2&gt;

&lt;p&gt;We treat &lt;strong&gt;code as a security boundary&lt;/strong&gt; itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Obfuscators&lt;/strong&gt;: We run multiple stages of JS/TS obfuscation on build artifacts to hinder reverse engineering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit Guidelines&lt;/strong&gt;: Hard rules enforced via Git hooks – no secrets, no TODOs, no debug code in commits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Toolchain&lt;/strong&gt;: Before deployment, every commit passes through a suite of security tools:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;eslint&lt;/code&gt; with strict security configs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ts-prune&lt;/code&gt; to detect unused/forgotten code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;depcheck&lt;/code&gt; and &lt;code&gt;npm audit&lt;/code&gt; for dependency issues&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;semgrep&lt;/code&gt; for static code vulnerability scanning&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;zap-cli&lt;/code&gt; (OWASP ZAP) for lightweight API security scans in CI/CD&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-bench-security&lt;/code&gt; for container hardening checks&lt;/li&gt;
&lt;li&gt;Custom &lt;strong&gt;secret scanners&lt;/strong&gt; to detect keys, tokens, or credentials in code&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This ensures that only &lt;strong&gt;secure, hardened, and obfuscated&lt;/strong&gt; code ever reaches production.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   [ User ] 
      │
      ▼
 [ Proxy / WAF ]
      │
 ┌────┴─────────┐
 ▼              ▼
[ Auth ]     [ API Gateway ]
   │              │
   ▼              ▼
[ Microservices ]       &amp;lt;-- encrypted APIs
   │
   ▼
[ Encrypted Database ]

   (Parallel Stack)
   [ User ]
      │
      ▼
 [ Secure Socket.IO ]  &amp;lt;-- chats &amp;amp; live updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Collect less, protect more&lt;/strong&gt; – store the minimum data possible.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypt at every layer&lt;/strong&gt; – DB, transit, application.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2FA everywhere&lt;/strong&gt; – users and admins alike.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate requests &amp;amp; responses&lt;/strong&gt; with ts-rest + Zod.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design for failure&lt;/strong&gt; – microservices + proxies keep the system resilient.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harden real-time communication&lt;/strong&gt; – encrypted and validated Socket.IO.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit external dependencies&lt;/strong&gt; – in-house frameworks reduce attack surface.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI is also security&lt;/strong&gt; – a hardened design system prevents front-end exploits.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure the code itself&lt;/strong&gt; – obfuscation, commit rules, and automated security tooling.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security is never “done.” But by combining strong principles, carefully chosen tools, and a defense-in-depth strategy, you can significantly increase both &lt;strong&gt;trust&lt;/strong&gt; and &lt;strong&gt;resilience&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We are the engineering team behind &lt;a href="https://bb-escort.de" rel="noopener noreferrer"&gt;bb-escort.de&lt;/a&gt;. On dev.to we share lessons learned about scaling, security, and developer experience.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>nextjs</category>
      <category>tsrest</category>
      <category>zod</category>
    </item>
  </channel>
</rss>
