How to Connect Users’ X (Twitter) Accounts Using OAuth 2.0 PKCE
Because the X API docs are unusable I've made a comprehensive tutorial for beginners complete with code snippets and live working examples.
By the end, you’ll understand OAuth conceptually and have working Node.js code.
Note: This is account linking, not primary login. Your app needs its own auth first. I used github
What We're Building
A flow where users click "Connect X Account," authorize your app, and you can tweet for them.
Prerequisites
1. X Developer Account with app created
2. Client ID (and optionally Client Secret)
3. Registered callback URL (e.g., http://localhost:3000/auth/x/callback)
The Problem
You can't ask for passwords. That's insecure, fragile, and against X's rules.
The Solution: OAuth
OAuth = hotel key card. X gives you a card that only opens specific doors (permissions), can be deactivated anytime, and you never get the master key.
The Flow
1. User clicks "Connect with X"
2. Your app redirects to X's login
3. User approves: "App wants to read/post tweets"
4. X redirects back with a temporary code
5. You exchange code for access token
6. Store token, use for API calls
Why PKCE?
The vulnerability: What if someone intercepts that code in step 4?
PKCE prevents this:
Before redirecting:
1. Generate random code_verifier (stays on your server)
2. Send hashed version (code_challenge) to X
3. When exchanging code: Send original code_verifier
4. X verifies the hash matches
Even if attackers steal the code, they can't use it without the verifier that never left your server.
The Code
File 1: pkceHelper.js
-generateCodeVerifier() - Creates a 128-character random string (96 bytes → base64url)
-generateCodeChallenge() - SHA-256 hashes the verifier into a "fingerprint"
-generatePKCEPair() - Returns both at once for convenience
File 2: oauthHandler.js
-Constructor - Stores your Client ID, Secret, callback URL, and default scopes (tweet[dot]read, tweet[dot]write, users[dot]read, offline[dot]access)
-generateAuthUrl(state) - Builds the X authorization URL with PKCE challenge. Returns the URL to redirect to the verifier you must store server-side
-exchangeCodeForTokens(code, verifier) - Trades the authorization code verifier for access/refresh tokens. -Handles Basic Auth if you have a Client Secret
-refreshAccessToken(refreshToken) - Gets a new access token when the old one expires (~2 hours). Critical for production
File 3: server.js
-Setup:
1. Express session middleware (critical for linking OAuth to users)
2. In-memory PKCE store (use Redis/database in production)
3. OAuth handler initialization
-Route 1: /auth/x (Start OAuth)
-Route 2: /auth/x/callback (Handle X's redirect)
The Critical Part: Session Linking
The trap:
You get tokens back, but which user do they belong to?
The solution:
1. Store user ID with the PKCE verifier when starting OAuth
2. Verify session matches in the callback
3. Save tokens associated with that user ID
The state parameter prevents CSRF. Your session cookie tells you which user is completing the flow.
Token Refresh
Access tokens expire in ~2 hours.
1. Handle this proactively, check before each req
2. Reactively on 401s
Real Implementation (Links in replies)
See this in action: GitLogs
Full article on medium with all kinds of bells and whistles like code snippets and diagrams 👇