Skip to content

Commit 90c7d53

Browse files
feat(providers): add support to Twitter OAuth 2.0 (nextauthjs#3446)
* feat(providers): add support to Twitter OAuth 2.0 * docs: add docs comment * chore: cleanup * chore: remove comments * chore: give warning for OAuth 2 for now
1 parent 0510c9b commit 90c7d53

File tree

2 files changed

+110
-7
lines changed

2 files changed

+110
-7
lines changed

app/pages/api/auth/[...nextauth].ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import NextAuth, { NextAuthOptions } from "next-auth"
33
import GitHubProvider from "next-auth/providers/github"
44
import Auth0Provider from "next-auth/providers/auth0"
55
import KeycloakProvider from "next-auth/providers/keycloak"
6-
import TwitterProvider from "next-auth/providers/twitter"
6+
import TwitterProvider, {
7+
TwitterLegacy as TwitterLegacyProvider,
8+
} from "next-auth/providers/twitter"
79
import CredentialsProvider from "next-auth/providers/credentials"
810
import IDS4Provider from "next-auth/providers/identity-server4"
911
import Twitch from "next-auth/providers/twitch"
@@ -72,11 +74,17 @@ export const authOptions: NextAuthOptions = {
7274
},
7375
}),
7476
// OAuth 1
77+
// TwitterLegacyProvider({
78+
// clientId: process.env.TWITTER_LEGACY_ID,
79+
// clientSecret: process.env.TWITTER_LEGACY_SECRET,
80+
// }),
81+
// OAuth 2 / OIDC
7582
TwitterProvider({
83+
// Opt-in to the new Twitter API for now. Should be default in the future.
84+
version: "2.0",
7685
clientId: process.env.TWITTER_ID,
7786
clientSecret: process.env.TWITTER_SECRET,
7887
}),
79-
// OAuth 2 / OIDC
8088
GitHubProvider({
8189
clientId: process.env.GITHUB_ID,
8290
clientSecret: process.env.GITHUB_SECRET,

src/providers/twitter.ts

Lines changed: 100 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { OAuthConfig, OAuthUserConfig } from "."
22

3-
export interface TwitterProfile {
3+
export interface TwitterLegacyProfile {
44
id: number
55
id_str: string
66
name: string
@@ -95,12 +95,12 @@ export interface TwitterProfile {
9595
needs_phone_verification: boolean
9696
}
9797

98-
export default function Twitter<P extends Record<string, any> = TwitterProfile>(
99-
options: OAuthUserConfig<P>
100-
): OAuthConfig<P> {
98+
export function TwitterLegacy<
99+
P extends Record<string, any> = TwitterLegacyProfile
100+
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
101101
return {
102102
id: "twitter",
103-
name: "Twitter",
103+
name: "Twitter (Legacy)",
104104
type: "oauth",
105105
version: "1.0A",
106106
authorization: "https://api.twitter.com/oauth/authenticate",
@@ -122,3 +122,98 @@ export default function Twitter<P extends Record<string, any> = TwitterProfile>(
122122
options,
123123
}
124124
}
125+
126+
/**
127+
* [Documentation](https://developer.twitter.com/en/docs/twitter-api/users/lookup/api-reference/get-users-me)
128+
*/
129+
export interface TwitterProfile {
130+
data: {
131+
id: string
132+
name: string
133+
username: string
134+
location?: string
135+
entities?: {
136+
url: {
137+
urls: Array<{
138+
start: number
139+
end: number
140+
url: string
141+
expanded_url: string
142+
display_url: string
143+
}>
144+
}
145+
description: {
146+
hashtags: Array<{
147+
start: number
148+
end: number
149+
tag: string
150+
}>
151+
}
152+
}
153+
verified?: boolean
154+
description?: string
155+
url?: string
156+
profile_image_url?: string
157+
protected?: boolean
158+
pinned_tweet_id?: string
159+
created_at?: string
160+
}
161+
includes?: {
162+
tweets?: Array<{
163+
id: string
164+
text: string
165+
}>
166+
}
167+
}
168+
169+
let warned = false
170+
export default function Twitter<
171+
P extends Record<string, any> = TwitterLegacyProfile | TwitterProfile
172+
>(options: OAuthUserConfig<P>): OAuthConfig<P> {
173+
if (!warned && options.version === "2.0") {
174+
warned = true
175+
console.warn(
176+
"Opted-in to Twitter OAuth 2.0. See the docs https://next-auth.js.org/providers/twitter#oauth-2"
177+
)
178+
return {
179+
id: "twitter",
180+
name: "Twitter",
181+
version: "2.0",
182+
type: "oauth",
183+
authorization: {
184+
url: "https://twitter.com/i/oauth2/authorize",
185+
params: { scope: "users.read tweet.read offline.access" },
186+
},
187+
token: {
188+
url: "https://api.twitter.com/2/oauth2/token",
189+
// TODO: Remove this
190+
async request({ client, params, checks, provider }) {
191+
const response = await client.oauthCallback(
192+
provider.callbackUrl,
193+
params,
194+
checks,
195+
{ exchangeBody: { client_id: options.clientId } }
196+
)
197+
return { tokens: response }
198+
},
199+
},
200+
userinfo: {
201+
url: "https://api.twitter.com/2/users/me",
202+
params: { "user.fields": "profile_image_url" },
203+
},
204+
profile({ data }) {
205+
return {
206+
id: data.id,
207+
name: data.name,
208+
// NOTE: E-mail is currently unsupported by OAuth 2 Twitter.
209+
email: null,
210+
image: data.profile_image_url,
211+
}
212+
},
213+
checks: ["pkce", "state"],
214+
options,
215+
}
216+
}
217+
218+
return TwitterLegacy(options)
219+
}

0 commit comments

Comments
 (0)