# Facebook Login Twin — facebook.twins.la A high-fidelity digital twin of Facebook Login: OAuth 2.0 authorization, token exchange, Graph /me user profile, and /debug_token inspection. Code written against this twin works against real Facebook with only hostname changes on the dialog and Graph URLs. Supported Graph API versions: v19.0, v21.0 (served concurrently under the same host). ## Authentication OAuth endpoints (no auth, driven by app_id + app_secret in the flow): /dialog/oauth, /v*/oauth/access_token, /v*/me, /v*/debug_token Twin Plane tenant ops — HTTP Basic Auth Username: app_id Password: app_secret (POST /_twin/tokens for direct token minting; GET /_twin/logs scoped to your app.) Twin Plane admin ops — Bearer token Header: X-Twin-Admin-Token: (or Authorization: Bearer ) Service-wide operations (create/delete apps, create/update/delete users, read any logs, update twin settings) require the admin token set by the deployment owner. Apps are admin-provisioned only — there is no self-bootstrap endpoint. Ask the operator to create an app for you and share the app_id and app_secret back. ## Key Endpoints Twin Plane (no auth): GET /_twin/health — status check GET /_twin/scenarios — supported scenarios + API versions GET /_twin/settings — twin settings (interactive_dialog) GET /_twin/references — authoritative sources used to build this twin Twin Plane (Basic Auth with app_id:app_secret): POST /_twin/tokens — mint a user access token for one of your app's users (body: {"fb_id", "scopes"}) GET /_twin/logs — your app's operation logs Twin Plane (Admin Bearer): POST /_twin/apps — create an app GET /_twin/apps — list apps DELETE /_twin/apps/ — delete an app (cascades) POST /_twin/users — create a test user (body: {"app_id", "fb_id", "name", "email", "granted_scopes"}) GET /_twin/users[?app_id=] — list users, optionally scoped PATCH /_twin/apps//users/ — update user DELETE /_twin/apps//users/ — delete user PUT /_twin/settings — update twin settings GET /_twin/logs — all operation logs OAuth & Graph API (for end users of your app): GET /dialog/oauth — authorization dialog (code + implicit) GET /v19.0/dialog/oauth — versioned dialog (same semantics) GET /v21.0/dialog/oauth — versioned dialog (same semantics) GET /v{19,21}/oauth/access_token — exchange code for token (also grant_type=client_credentials for an app access token) GET /v{19,21}/me — fetch the authenticated user's profile (fields query param: id,name,email) GET /v{19,21}/debug_token — inspect an input_token ## Quick Start (OAuth code flow) 1. Operator creates a test app and test user for you via Twin Plane: curl -X POST https://facebook.twins.la/_twin/apps \ -H "X-Twin-Admin-Token: $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "My App", "redirect_uris": ["https://my-app.test/cb"]}' curl -X POST https://facebook.twins.la/_twin/users \ -H "X-Twin-Admin-Token: $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"app_id": "", "name": "Alice", "email": "alice@test", "granted_scopes": ["email", "public_profile"]}' 2. Walk the dialog (this is what a browser would do): curl -I "https://facebook.twins.la/v19.0/dialog/oauth?\ client_id=&redirect_uri=https://my-app.test/cb&\ response_type=code&state=xyz&scope=email,public_profile" # 302 Location: https://my-app.test/cb?code=&state=xyz 3. Exchange the code for an access token: curl "https://facebook.twins.la/v19.0/oauth/access_token?\ client_id=&client_secret=&\ redirect_uri=https://my-app.test/cb&code=" # {"access_token":"EAA...","token_type":"bearer","expires_in":...} 4. Fetch the user's identity: curl "https://facebook.twins.la/v19.0/me?fields=id,name,email" \ -H "Authorization: Bearer " # {"id":"...","name":"Alice","email":"alice@test"} 5. Verify app attribution (detect token substitution): curl "https://facebook.twins.la/v19.0/debug_token?\ input_token=&access_token=|" # {"data": {"app_id":"","user_id":"...","is_valid":true,...}} ## Local Usage pip install twins-facebook twins-local python -c " from twins_local.storage_sqlite_facebook import SQLiteFacebookStorage from twins_facebook.app import create_app storage = SQLiteFacebookStorage('facebook.db') app = create_app(storage=storage, config={'admin_token': 'dev'}) app.run(port=8081) " Then use http://localhost:8081 instead of https://facebook.twins.la. ## Fidelity Notes - Access tokens are opaque twin values — do NOT hard-code real Facebook token shapes or length expectations. The EAA prefix is a visual courtesy, not a fidelity contract. - Error envelope matches Facebook: {"error": {"message","type","code", "fbtrace_id"}}. Well-known codes: 100 (parameter), 101 (app credentials), 190 (access token), 191 (redirect_uri), 803 (unknown path), 2500 (unknown API version). - /dialog/oauth is served at the same host as /v*/... — real Facebook splits them across www.facebook.com and graph.facebook.com; twins.la collapses both to one host. Configure dialog URL and Graph URL independently in your client if the SDK expects different hostnames. - Users are per-app on this twin — the same fb_id under different apps are different users. Tenants cannot cross-access each other's users. ## Reference Detailed docs: https://github.com/twins-la/facebook Project overview: https://twins.la All twins: https://github.com/twins-la/twins-la