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:
- Created a standalone X OAuth 2.0 handler that wraps
golang.org/x/oauth2.Config
- In the HTTP handler, routes for
"x" are dispatched to the custom OAuth 2.0 flow; all other providers continue to use goth
- State and PKCE code verifier are stored in a dedicated
gorilla/sessions cookie (separate from goth's session)
- On callback: validate state, exchange code with PKCE verifier, then call
/2/users/me to fetch user info
- 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
Summary
The
twitterv2provider does not actually use OAuth 2.0 for its authentication flow. It uses OAuth 1.0a viamrjones/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
twitterv2provider 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:
Error seen at runtime:
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
twitterv2provider 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:
golang.org/x/oauth2.Config"x"are dispatched to the custom OAuth 2.0 flow; all other providers continue to use gothgorilla/sessionscookie (separate from goth's session)/2/users/meto fetch user infoKey
golang.org/x/oauth2APIs used:oauth2.GenerateVerifier()— generate PKCE code verifieroauth2.S256ChallengeOption(verifier)— attach S256 challenge to auth URLoauth2.VerifierOption(verifier)— attach verifier to token exchangeConfig.AuthCodeURL()/Config.Exchange()/Config.Client()— standard OAuth 2.0 flowX OAuth 2.0 endpoints:
https://twitter.com/i/oauth2/authorizehttps://api.twitter.com/2/oauth2/tokenhttps://api.twitter.com/2/users/me?user.fields=id,name,username,profile_image_urlusers.read tweet.readThis works correctly with X's Free plan. Login flow completes successfully and returns user profile data.
Related issues
twitterv2provider (API v2 endpoints, but auth flow remained OAuth 1.0a)Environment