Skip to content

feat: add OAuth 2.0 user-context auth#11

Open
mrap wants to merge 1 commit intoxdevplatform:masterfrom
mrap:feat/oauth2-user-context
Open

feat: add OAuth 2.0 user-context auth#11
mrap wants to merge 1 commit intoxdevplatform:masterfrom
mrap:feat/oauth2-user-context

Conversation

@mrap
Copy link
Copy Markdown

@mrap mrap commented Apr 17, 2026

Summary

Add an OAuth 2.0 User Context auth path so endpoints that require it (bookmarks, searchNews, and others that currently return 403 Unsupported Authentication) actually work, and ship the generate_authtoken.py-style helper that the README already advertises but never existed in the repo.

Problem

Two separate but linked gaps:

  1. sign_oauth1_request signs every outbound request with OAuth 1.0a. Several X API endpoints reject OAuth 1.0a entirely — for example getUsersBookmarks, createUsersBookmark, deleteUsersBookmark, getUsersBookmarkFolders, getUsersBookmarksByFolderId, searchNews. They return HTTP 403 {'title': 'Unsupported Authentication', 'detail': '...OAuth 1.0a User Context is forbidden for this endpoint. Supported authentication types are [OAuth 2.0 User Context]'}. This was reported in some tools do not work - Unsupported Authentication error #7.

  2. The README's "Generate an OAuth2 user token" section instructs users to python generate_authtoken.py, but that file was never committed. Users who hit (1) and follow the docs land on a missing file.

Changes

  • server.py: sign_oauth1_request reads X_OAUTH2_ACCESS_TOKEN at request time. When set, it writes Authorization: Bearer <token> and returns before OAuth 1.0a signing. OAuth 2.0 User Context covers every endpoint OAuth 1.0a covers plus the OAuth2-only ones, so one env var flips the whole server over. When unset, OAuth 1.0a signing runs unchanged — no behavior change for existing users.

  • generate_oauth2_token.py (new): runs the X OAuth 2.0 authorization code + PKCE flow locally. Spins up a one-shot callback server on X_OAUTH_CALLBACK_HOST:PORT, opens the browser for consent, exchanges the code for tokens, and writes X_OAUTH2_ACCESS_TOKEN + X_OAUTH2_REFRESH_TOKEN to .env. Supports both confidential (CLIENT_SECRET) and public (PKCE-only) clients. Requests all user-facing scopes (tweet.*, users.read, follows.*, bookmark.*, like.*, list.*, offline.access). Optional TLS via TLS_CERT_FILE / TLS_KEY_FILE for non-localhost callback hosts (e.g. a tailnet / LAN hostname so a browser on a different device can complete the redirect — X requires HTTPS for any non-localhost callback).

  • env.example: documents the new vars. Separates OAuth 1.0a pre-provisioned tokens (X_OAUTH_ACCESS_TOKEN*) from the new OAuth 2.0 user tokens (X_OAUTH2_*), and adds the optional TLS vars.

  • README.md: replaces the aspirational "Generate an OAuth2 user token" section with a real walkthrough (Developer Portal config, required scopes, running the script, when the server switches to OAuth 2.0). Distinguishes the two credential sets in the Setup checklist.

Testing

Verified end-to-end against my own X Developer app:

  • X_OAUTH2_ACCESS_TOKEN unset → OAuth 1.0a signing path runs unchanged. getUsersMe returns 200 with the authenticated user's profile.
  • X_OAUTH2_ACCESS_TOKEN set → Authorization: Bearer <token> header on every outbound request. getUsersMe, searchPostsRecent, and getUsersBookmarks all return 200. Without this PR, getUsersBookmarks returns the 403 from some tools do not work - Unsupported Authentication error #7.
  • generate_oauth2_token.py completes the full PKCE flow, receives the callback, exchanges the code, and writes both access and refresh tokens to .env.

Closes #7.

This contribution was developed with AI assistance (Claude Code).

Several X API endpoints — bookmarks (getUsersBookmarks,
createUsersBookmark, deleteUsersBookmark, getUsersBookmarkFolders,
getUsersBookmarksByFolderId), searchNews, and a handful of others —
reject OAuth 1.0a with HTTP 403 "Unsupported Authentication: ...
Supported authentication types are [OAuth 2.0 User Context]". The
server currently only signs with OAuth 1.0a, so those tools fail at
runtime once allowlisted. This was reported in xdevplatform#7.

The README also advertises an "OAuth2 user token" flow via
generate_authtoken.py, but that file was never committed to the repo.

This PR delivers both sides:

1. server.py: sign_oauth1_request now checks X_OAUTH2_ACCESS_TOKEN
   at request time. When set, it writes Authorization: Bearer <token>
   and skips OAuth 1.0a signing entirely. OAuth 2.0 User Context
   supports every endpoint OAuth 1.0a supports and more, so one env
   var flips the whole server over. When the var is empty, OAuth 1.0a
   signing runs unchanged.

2. generate_oauth2_token.py: new script. Runs the X OAuth 2.0
   authorization code + PKCE flow locally — spins up a one-shot HTTP
   (or HTTPS, if TLS_CERT_FILE / TLS_KEY_FILE are set) server on
   X_OAUTH_CALLBACK_*, opens the browser for consent, exchanges the
   code for tokens, and writes X_OAUTH2_ACCESS_TOKEN and
   X_OAUTH2_REFRESH_TOKEN to .env. Requests all user-facing scopes
   including bookmark.read/write and offline.access. Supports both
   confidential (CLIENT_SECRET) and public (PKCE-only) clients.

3. env.example + README.md: documents the new env vars, distinguishes
   OAuth 1.0a pre-provisioned tokens (X_OAUTH_ACCESS_TOKEN*) from the
   OAuth 2.0 user-context tokens (X_OAUTH2_*), and replaces the
   aspirational "Generate an OAuth2 user token" section with a real,
   working walkthrough.

Closes xdevplatform#7.

This contribution was developed with AI assistance (Claude Code).
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 17, 2026

CLA assistant check
All committers have signed the CLA.

@chrisbalt
Copy link
Copy Markdown

Hit this exact gap today evaluating xmcp for a bookmark-reading workflow — the getUsersBookmarks 403 is what pushed me to a community MCP alternative instead of xmcp. The OAuth 2.0 env-var gate + separate X_OAUTH2_* naming looks right, and the generate_oauth2_token.py script is the natural companion (we ended up hand-rolling the same PKCE flow externally). Happy to test the branch end-to-end against bookmark + search endpoints if that helps.

One question: any plan to add token refresh in this PR or a follow-up? The 7200s access-token TTL is the next operational pain point once bearer mode works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

some tools do not work - Unsupported Authentication error

3 participants