.twins.la

A digital twin of Facebook Login — OAuth 2.0 and the Graph API slice for signing users in.

What is this?

This is a high-fidelity digital twin of Facebook's Login surface: the OAuth 2.0 authorization dialog, token exchange, the Graph API /me user-profile endpoint, and /debug_token for inspecting access tokens. Code you write against this twin works against real Facebook with only hostname changes on the dialog and Graph URLs. No Facebook App registration needed to develop.

Supported scenarios

How to use it

Cloud: Point your app's Facebook SDK or HTTP client at https://facebook.twins.la for both the dialog URL (real: www.facebook.com) and the Graph URL (real: graph.facebook.com). Ask the operator to create a test app and user for you, then walk the OAuth code flow.

Local: Install with pip install twins-facebook twins-local and run a local instance on any port. Same API, same behavior, your machine.

For agents

Copy this into your agent's system prompt, tool configuration, or CLAUDE.md. Also available as plain text at /_twin/agent-instructions.

# 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: <token>   (or Authorization: Bearer <token>)
  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/<app_id>                      — 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=<id>]                — list users, optionally scoped
  PATCH  /_twin/apps/<app_id>/users/<fb_id>        — update user
  DELETE /_twin/apps/<app_id>/users/<fb_id>        — 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": "<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=<app_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=<code>&state=xyz

3. Exchange the code for an access token:

     curl "https://facebook.twins.la/v19.0/oauth/access_token?\
       client_id=<app_id>&client_secret=<app_secret>&\
       redirect_uri=https://my-app.test/cb&code=<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 <access_token>"
     # {"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>&access_token=<app_id>|<app_secret>"
     # {"data": {"app_id":"<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