API Security Key Exchange#
We categorize clients into two main types: web/wap (lightweight clients) and android/ios (trusted clients).
For web/wap clients: The process starts by obtaining an RSA public key through CSRF protection, then using that RSA public key to decrypt the AES Token that the server associates with your current session. All subsequent requests are encrypted using this AES Token.
- CSRF tokens must be embedded directly in the page, not stored in cookies - otherwise, the security benefit is minimal.
- This approach helps ensure that pages are actually rendered by our servers.
- CSRF tokens need encryption and should be unique for each return (by embedding timestamps), though they map to the same backend CSRF token.
- Public key rotation is essential: We run scheduled tasks to regularly update the stored wap/web key pairs, with previous keys expiring after 1 month (our frontend session public keys expire within 2 weeks without renewal, so 1 month provides a safety buffer).
For android/ios clients: Each app version gets a unique public key embedded during build time, which the server records. This directly decrypts the AES Token for your current session, with all subsequent requests encrypted using this AES Token.
- Since mobile apps are generally trusted clients with version-specific public keys, CSRF protection isn’t typically necessary.
- This mechanism can also facilitate forced version upgrades.
Why this matters:
- HTTPS prevents WiFi hijacking and packet sniffing of HTTP headers and body content
- API encryption prevents bot scraping, request forgery, and client simulation
- Android/iOS public keys help identify specific versions and verify trusted clients, while browsers can’t provide this level of assurance - that’s why we add CSRF tokens to reasonably ensure requests originate from our rendered pages
Security Headers#
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
- Enables HTTP Strict Transport Security (HSTS), instructing browsers to only access the website via HTTPS
max-age=31536000
tells browsers to remember HTTPS-only access for 31,536,000 seconds (1 year)includeSubDomains
extends this rule to all subdomains of the current domainpreload
indicates the site wants inclusion in browsers’ built-in HSTS preload lists, enforcing HTTPS even on first visits
X-Content-Type-Options: nosniff
- A security feature preventing browsers from attempting to “sniff” or guess resource MIME types, forcing adherence to server-provided Content-Type headers
- The
nosniff
option prevents MIME type confusion attacks, such as browsers interpreting non-script files as executable scripts
X-Frame-Options: SAMEORIGIN
- Prevents other sites from embedding the page via
<iframe>
,<frame>
,<embed>
, or<object>
, protecting against clickjacking attacks SAMEORIGIN
only allows pages from the same origin to embed the current page in frames
Content-Security-Policy: [extensive content omitted here]
- This blocking mechanism might cause some third-party tracking pixels, images, etc. to fail loading, requiring monitoring (by adding
report-uri /api/csp-report-endpoint?version=5
at the end). Reports include all blocked content: JS, CSS, JPG, persistent connections, video resources, etc. - This header changes frequently - for example, when search engines redirect to your site, they embed JS, and advertising channel partners do the same
- You’ll need to implement
/api/csp-report-endpoint
yourself - Include a version parameter so that after adding new whitelist entries and incrementing the version number, you can ignore reports from older versions (since this header is typically added at the CDN level like Cloudflare, which usually has long cache times)
Here’s a sample Content-Security-Policy allowing all Google and Facebook domains:
default-src 'self' data: 'unsafe-inline' blob: 'unsafe-eval' *.google-analytics.com *.googletagmanager.com *.gstatic.com *.googleapis.com *.google.co *.google.com *.google.ad *.google.ae *.google.com.af *.google.com.ag *.google.al *.google.am *.google.co.ao *.google.com.ar *.google.as *.google.at *.google.com.au *.google.az *.google.ba *.google.com.bd *.google.be *.google.bf *.google.bg *.google.com.bh *.google.bi *.google.bj *.google.com.bn *.google.com.bo *.google.com.br *.google.bs *.google.bt *.google.co.bw *.google.by *.google.com.bz *.google.ca *.google.cd *.google.cf *.google.cg *.google.ch *.google.ci *.google.co.ck *.google.cl *.google.cm *.google.cn *.google.com.co *.google.co.cr *.google.com.cu *.google.cv *.google.com.cy *.google.cz *.google.de *.google.dj *.google.dk *.google.dm *.google.com.do *.google.dz *.google.com.ec *.google.ee *.google.com.eg *.google.es *.google.com.et *.google.fi *.google.com.fj *.google.fm *.google.fr *.google.ga *.google.ge *.google.gg *.google.com.gh *.google.com.gi *.google.gl *.google.gm *.google.gr *.google.com.gt *.google.gy *.google.com.hk *.google.hn *.google.hr *.google.ht *.google.hu *.google.co.id *.google.ie *.google.co.il *.google.im *.google.co.in *.google.iq *.google.is *.google.it *.google.je *.google.com.jm *.google.jo *.google.co.jp *.google.co.ke *.google.com.kh *.google.ki *.google.kg *.google.co.kr *.google.com.kw *.google.kz *.google.la *.google.com.lb *.google.li *.google.lk *.google.co.ls *.google.lt *.google.lu *.google.lv *.google.com.ly *.google.co.ma *.google.md *.google.me *.google.mg *.google.mk *.google.ml *.google.com.mm *.google.mn *.google.com.mt *.google.mu *.google.mv *.google.mw *.google.com.mx *.google.com.my *.google.co.mz *.google.com.na *.google.com.ng *.google.com.ni *.google.ne *.google.nl *.google.no *.google.com.np *.google.nr *.google.nu *.google.co.nz *.google.com.om *.google.com.pa *.google.com.pe *.google.com.pg *.google.com.ph *.google.com.pk *.google.pl *.google.pn *.google.com.pr *.google.ps *.google.pt *.google.com.py *.google.com.qa *.google.ro *.google.ru *.google.rw *.google.com.sa *.google.com.sb *.google.sc *.google.se *.google.com.sg *.google.sh *.google.si *.google.sk *.google.com.sl *.google.sn *.google.so *.google.sm *.google.sr *.google.st *.google.com.sv *.google.td *.google.tg *.google.co.th *.google.com.tj *.google.tl *.google.tm *.google.tn *.google.to *.google.com.tr *.google.tt *.google.com.tw *.google.co.tz *.google.com.ua *.google.co.ug *.google.co.uk *.google.com.uy *.google.co.uz *.google.com.vc *.google.co.ve *.google.co.vi *.google.com.vn *.google.vu *.google.ws *.google.rs *.google.co.za *.google.co.zm *.google.co.zw *.google.cat *.googleadservices.com facebook.net *.facebook.net facebook.com *.facebook.com; report-uri /api/csp-report-endpoint?version=5
Anti-Bot Protection Mechanisms#
We primarily address two scenarios:
For unregistered or logged-out users: Implementing anti-bot measures for registration, verification codes, and similar endpoints while minimizing user friction.
For authenticated users:
- Setting necessary participation thresholds for activities (like recent transaction volume)
- After introducing MFA, restricting activity participation to users’ bound MFA devices
Scenario 2 is mainly business-focused. MFA mechanisms provide not just security assurance, but also help verify device authenticity, enabling device-based business restrictions.
For scenario 1, we can reduce verification code friction using these mechanisms:
- Implement services like Google reCAPTCHA Enterprise (reCAPTCHA v3) or hCAPTCHA Enterprise for sensitive endpoints like registration and SMS OTP. Each request includes a Google reCAPTCHA Enterprise score:
- reCAPTCHA v3 continuously evaluates user behavior while browsing, including interaction patterns (mouse movement, scrolling, clicking), device and browser information, and session-wide behavior analysis across multiple pages.
- Based on this behavioral analysis, reCAPTCHA v3 assigns each user request a score from 0.0 to 1.0. Scores closer to 1.0 indicate the system believes the behavior comes from a real human, while lower scores suggest automated scripts or bots. Here’s an example score distribution:
Your backend responds based on this score (we require verification codes for all requests scoring below 0.8). There are numerous verification code implementation options available.
The result: Most users won’t even need to enter verification codes during registration. Only users with lower scores face challenges (verification codes or other challenge methods).
Why we don’t recommend IP + device blocking or rate limiting (rate limiting means redirecting or showing verification codes, not blocking access): Compared to the above methods, this creates more user friction. Additionally, IPs and devices are easily spoofed (IPs via VPNs, devices via simulation), and current browser trends move toward unified user-agents with less exposed information:
https://developers.google.com/privacy-sandbox/blog/user-agent-reduction-android-model-and-version