What we are protecting: - A question bank (981 questions with explanations) that represents significant editorial work - Not financial data, medical data, or personal information - Business goal: prevent competitors from bulk-downloading questions for free
Realistic threats: 1. Casual scraping — someone writes a script to download all content 2. Competitor copying — another app developer bulk-downloads the question bank 3. NOT a concern: sophisticated nation-state attackers, reverse engineers with unlimited resources
Conclusion: We need "good enough" security, not military-grade protection. A determined attacker with the app binary can always extract keys, but we want to raise the bar above casual scraping.
How it works:
- A shared secret (e.g., X-Glidr-Key: abc123...) is embedded in the app
- Server validates this header on all content endpoints
- Key is obfuscated in the binary using compile-time string splitting or encryption
Implementation: ```dart // Store as environment variable injected at build time // Never hardcode in plain text in source const apiKey = String.fromEnvironment('GLIDRAPIKEY');
// In HTTP client dio.options.headers['X-Glidr-Key'] = apiKey; ```
Server side (PHP example): ```php $key = $SERVER['HTTPXGLIDRKEY'] ?? ''; if ($key !== getenv('GLIDRAPIKEY')) { httpresponsecode(403); die('Forbidden'); } ```
Pros: Simple, zero infrastructure cost, stops casual scrapers Cons: Key is extractable from app binary by determined attacker Verdict: Sufficient for protecting a $49.99 exam app's question bank
How it works: - App validates that tekmidian.com presents the exact SSL certificate it expects - Prevents man-in-the-middle attacks even on compromised networks
Implementation in Dio: ```dart (dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate = (client) { client.badCertificateCallback = (cert, host, port) => false; SecurityContext context = SecurityContext(); context.setTrustedCertificatesBytes(pinnedCertBytes); return HttpClient(context: context); }; ```
Pros: Prevents MITM interception of API key Cons: Certificate rotation causes app to break; requires app update when cert expires Verdict: Nice-to-have, add in v2 if abuse becomes a problem
How it works: - Apple/Google cryptographically verify the app is genuine and unmodified - Backend receives attestation token, validates with Apple/Google servers - Only genuine, unmodified app instances can download content
Pros: Strongest protection available; cannot be bypassed without jailbreak Cons: - Requires backend infrastructure to validate attestation tokens - Apple App Attest has rate limits (free tier: limited attestations/day) - Adds significant implementation complexity - Overkill for a small hobby/niche app
Verdict: Not needed for v1. Consider for v2 if piracy becomes a real problem.
How it works: - App registers with backend using device ID + purchase receipt - Backend issues a JWT valid for this device - JWT is used for all content requests - Content is tied to a specific "account"
Pros: Can revoke access per device; enables future account features Cons: Requires user accounts infrastructure; adds backend complexity Verdict: Not needed for v1 one-time purchase model with no accounts
All API traffic over HTTPS. tekmidian.com must have a valid SSL certificate.
--dart-defineBase URL: https://tekmidian.com/glidr/api/v1/
All requests require header: X-Glidr-Key: {secret}
``` GET /manifest.json Response: { "version": "1.2.0", "subjects": { "air_law": "abc123hash", ... } }
GET /subjects/{subject_id}.json Response: Full subject JSON with all questions Example: /subjects/air_law.json
GET /figures/{filename} Response: Image file (PNG or SVG) Example: /figures/bazl30q08ask21speed_polar.png ```
```json { "version": "1.2.0", "updated_at": "2026-03-15T00:00:00Z", "subjects": { "airlaw": { "hash": "sha256:abc...", "questioncount": 110, "size_bytes": 45000 }, "meteorology": { "hash": "sha256:def...", "questioncount": 110, "sizebytes": 52000 } }, "figures": { "hash": "sha256:ghi...", "count": 58, "size_bytes": 4200000 } } ```
App caches subject hashes locally. On launch, compares cached hash vs remote manifest. Downloads only changed subjects.
Minimal PHP implementation:
``` /glidr/ api/ v1/ .htaccess # auth check + routing auth.php # API key validation manifest.json # pre-generated static file subjects/ air_law.json meteorology.json ... (9 files) figures/ *.png *.svg ```
.htaccess:
```apache
RewriteEngine On
RewriteCond %{HTTP:X-Glidr-Key} !={GLIDRAPIKEY}
RewriteRule ^ - [F,L]
```
This is the simplest possible implementation — Apache validates the key before serving any file.