Skip to content

twitterv2 provider uses OAuth 1.0a internally — fails with X Free plan (401 Unauthorized) #635

@rabitt1ove

Description

@rabitt1ove

Summary

The twitterv2 provider does not actually use OAuth 2.0 for its authentication flow. It uses OAuth 1.0a via mrjones/oauth, and only calls the Twitter API v2 endpoints (/2/users/me). This causes a 401 Unauthorized error on X's Free plan, which does not support OAuth 1.0a.

Details

The twitterv2 provider was added in #460 to address #440, but the underlying auth mechanism was not changed to OAuth 2.0 — only the API endpoints were updated to v2.

Internal dependency chain:

goth/providers/twitterv2
  → github.com/mrjones/oauth (OAuth 1.0a, HMAC-SHA1 signatures)

Error seen at runtime:

HTTP response is not 200/OK as expected.
  Response Status: '401 Unauthorized'
  Request Headers: Authorization: OAuth oauth_signature_method="HMAC-SHA1" ...

X's Free plan (the only plan available for new developer accounts) requires OAuth 2.0 with PKCE. The OAuth 1.0a request token endpoint returns 401 because it is disabled for Free-tier apps.

Expected behavior

The twitterv2 provider should use OAuth 2.0 + PKCE (code_challenge_method=S256) as documented in X's OAuth 2.0 documentation.

Workaround

I worked around this by implementing X OAuth 2.0 + PKCE outside of goth using golang.org/x/oauth2, while keeping goth for Google login.

Implementation approach:

  1. Created a standalone X OAuth 2.0 handler that wraps golang.org/x/oauth2.Config
  2. In the HTTP handler, routes for "x" are dispatched to the custom OAuth 2.0 flow; all other providers continue to use goth
  3. State and PKCE code verifier are stored in a dedicated gorilla/sessions cookie (separate from goth's session)
  4. On callback: validate state, exchange code with PKCE verifier, then call /2/users/me to fetch user info
  5. The result is mapped to the same internal user struct used by goth providers, so downstream session/account logic is unchanged

Key golang.org/x/oauth2 APIs used:

  • oauth2.GenerateVerifier() — generate PKCE code verifier
  • oauth2.S256ChallengeOption(verifier) — attach S256 challenge to auth URL
  • oauth2.VerifierOption(verifier) — attach verifier to token exchange
  • Config.AuthCodeURL() / Config.Exchange() / Config.Client() — standard OAuth 2.0 flow

X OAuth 2.0 endpoints:

  • Auth: https://twitter.com/i/oauth2/authorize
  • Token: https://api.twitter.com/2/oauth2/token
  • User info: https://api.twitter.com/2/users/me?user.fields=id,name,username,profile_image_url
  • Scopes: users.read tweet.read

This works correctly with X's Free plan. Login flow completes successfully and returns user profile data.

Related issues

Environment

  • goth v1.82.0
  • Go 1.26
  • X Developer Portal: Free plan

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions