@mantlejs/auth-twitter
Sign in with X (Twitter) OAuth 2.0 strategy for Mantle JS. Implements the authorization code flow with PKCE — no Passport.js dependency. Registers GET /auth/twitter and GET /auth/twitter/callback on the HTTP transport, then finds or creates a user record and returns a Mantle JWT pair.
Installation
Section titled “Installation”npm install @mantlejs/auth-twitterConcepts
Section titled “Concepts”Unlike auth-github/auth-facebook/auth-linkedin, X’s OAuth 2.0 implementation requires PKCE (Proof Key for Code Exchange) — same posture as auth-google/auth-microsoft. On each redirect request the plugin generates a fresh code_verifier (via Arctic, which also derives the SHA-256 / base64url code_challenge — see ADR-002). The verifier is stored server-side against a random state token and passed to the token exchange on callback.
Defaults to ["users.read", "tweet.read"]. X’s own API reference lists users.read and tweet.read as alternative scopes for GET /2/users/me, but X’s developer community has repeatedly reported 403 Forbidden from that endpoint with users.read alone — both are requested by default to match what’s actually been confirmed working, not just what the reference documents.
No email
Section titled “No email”X’s /2/users/me endpoint only returns an email address via the confirmed_email field, gated behind an app-level email access grant that most apps don’t have, and never includes it by default. This strategy doesn’t request user.fields=confirmed_email (an unapproved field risks the whole call failing rather than being silently omitted), so a user record created via twitterStrategy never has an email — plan your users schema accordingly if X is one of several strategies you support.
Find-or-create
Section titled “Find-or-create”On callback the plugin searches the configured user service for a record where twitterId (configurable) matches the id field from X’s /2/users/me response. If no record is found, it creates one with { twitterId, name } from the profile (no email — see above). The same user is returned on every subsequent sign-in.
Quick start
Section titled “Quick start”import { mantle } from "@mantlejs/mantle";import { express } from "@mantlejs/express";import { auth } from "@mantlejs/auth";import { twitterStrategy } from "@mantlejs/auth-twitter";
const app = mantle() .configure(express()) .configure(auth({ secret: process.env.JWT_SECRET! })) .configure( twitterStrategy({ clientId: process.env.TWITTER_CLIENT_ID!, clientSecret: process.env.TWITTER_CLIENT_SECRET!, }), );
app.listen(3030);Sign-in flow:
- Redirect the browser to
GET /auth/twitter - User authenticates on X and consents
- X redirects to
GET /auth/twitter/callback?code=...&state=... - The plugin exchanges the code, fetches the profile, finds or creates the user, and responds:
{ "accessToken": "eyJhbGciOiJIUzI1NiJ9...", "refreshToken": "eyJhbGciOiJIUzI1NiJ9...", "user": { "id": "1", "twitterId": "1234567890", "name": "Alice" }}twitterStrategy(config)
Section titled “twitterStrategy(config)”function twitterStrategy(config: TwitterStrategyConfig): MantlePlugin;
type TwitterStrategyConfig = OAuthPluginConfig;| Field | Type | Default | Description |
|---|---|---|---|
clientId |
string |
— | X app’s OAuth 2.0 Client ID (required) |
clientSecret |
string |
— | X app’s OAuth 2.0 Client Secret (required) |
callbackUrl |
string |
'/auth/twitter/callback' |
Callback path — must match a redirect URI registered in the X app’s settings |
scope |
string[] |
['users.read', 'tweet.read'] |
X OAuth 2.0 scopes |
entity |
string |
'users' |
Mantle service used to find or create users |
entityIdField |
string |
'twitterId' |
Field on the user record matched against X’s id field |
redirectUrl |
string |
none — returns JSON | Frontend URL to redirect to on completion, with tokens (or an error) in the URL fragment — see @mantlejs/auth-oauth |
Routes registered:
| Method | Path | Description |
|---|---|---|
GET |
/auth/twitter |
Redirect to X sign-in |
GET |
/auth/twitter/callback |
Handle callback, issue Mantle JWT pair |
Must be configured after the transport (e.g. express()) and auth().
import type { TwitterStrategyConfig } from "@mantlejs/auth-twitter";TwitterStrategyConfig is a plain alias of OAuthPluginConfig from @mantlejs/auth-oauth — X needs no strategy-specific fields (unlike auth-microsoft’s tenant or auth-apple’s Sign in with Apple key fields).
X Developer Portal setup
Section titled “X Developer Portal setup”- Create a project and app at developer.x.com → Developer Portal
- Under your app’s User authentication settings, enable OAuth 2.0 and set the app type to Web App
- Add a callback URI:
https://your-domain.com/auth/twitter/callback - Under App permissions, request at least Read access
- Copy the OAuth 2.0 Client ID and generate a Client Secret into your environment
Development
Section titled “Development”npx nx build auth-twitter # compilenpx nx test auth-twitter # run testsnpx nx lint auth-twitter # lintPublishing
Section titled “Publishing”npx nx build auth-twittercd packages/auth-twitternpm publish --access public