markbates / markbates/goth

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

Open
#635 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
6.6k
Forks
631
PR merge metrics
No merged PRs in 30d

Description

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
  • #440 — Original request for Twitter OAuth 2.0 support (closed by #460)
  • #460 — Added twitterv2 provider (API v2 endpoints, but auth flow remained OAuth 1.0a)
Environment
  • goth v1.82.0
  • Go 1.26
  • X Developer Portal: Free plan

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in goth/providers/twitterv2 and trace its current mrjones/oauth authentication flow and /2/users/me request. Compare the provider with X's documented OAuth 2.0 + PKCE endpoints and the listed golang.org/x/oauth2 APIs. Done means the provider authenticates on X's Free plan and still returns the existing internal user data.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.