Skip to content

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

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

Activity

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

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