Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(tiktok): Update TikTok provider configuration with custom Fetch #12168

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 97 additions & 47 deletions packages/core/src/providers/tiktok.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
*
* @module providers/tiktok
*/
import { TokenSet } from "../types.js"
import { customFetch } from "../lib/symbols.js"
import type { OAuthConfig, OAuthUserConfig } from "./index.js"

/**
* [More info](https://developers.tiktok.com/doc/tiktok-api-v2-get-user-info/)
*/
export interface TiktokProfile extends Record<string, any> {
export interface TiktokProfile {
data: {
user: {
/**
Expand Down Expand Up @@ -137,14 +137,16 @@ export interface TiktokProfile extends Record<string, any> {
* ```
*
* #### Configuration
* You can omit the client and secret if you have set the `AUTH_TIKTOK_ID` and `AUTH_TIKTOK_SECRET` environment variables.
* Remeber that the AUTH_TIKTOK_ID is the Client Key in the TikTok Application
*```ts
* import { Auth } from "@auth/core"
* import TikTok from "@auth/core/providers/tiktok"
*
* const request = new Request(origin)
* const response = await Auth(request, {
* providers: [
* TikTok({ clientId: TIKTOK_CLIENT_KEY, clientSecret: TIKTOK_CLIENT_SECRET }),
* TikTok({ clientId: AUTH_TIKTOK_ID, clientSecret: AUTH_TIKTOK_SECRET }),
* ],
* })
* ```
Expand All @@ -153,13 +155,15 @@ export interface TiktokProfile extends Record<string, any> {
* - [TikTok app console](https://developers.tiktok.com/)
* - [TikTok login kit documentation](https://developers.tiktok.com/doc/login-kit-web/)
* - [Avaliable Scopes](https://developers.tiktok.com/doc/tiktok-api-scopes/)
* - [Sandbox for testing](https://developers.tiktok.com/blog/introducing-sandbox)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is huge! 🤯 Finally!

*
*
* ### Notes
*
* :::tip
*
* Production applications cannot use localhost URLs to sign in with TikTok. You need add the domain and Callback/Redirect url's to your TikTok app and have them review and approved by the TikTok Team.
* If you need to test your implementation, you can use the sandbox feature and ngrok for testing in localhost.
*
* :::
*
Expand All @@ -171,7 +175,7 @@ export interface TiktokProfile extends Record<string, any> {
*
* :::tip
*
* Client_ID will be the Client Key in the TikTok Application
* AUTH_TIKTOK_ID will be the Client Key in the TikTok Application
*
* :::
*
Expand All @@ -183,6 +187,64 @@ export interface TiktokProfile extends Record<string, any> {
* The TikTok provider comes with a [default configuration](https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/tiktok.ts).
* To override the defaults for your use case, check out [customizing a built-in OAuth provider](https://authjs.dev/guides/configuring-oauth-providers).
*
* If You Need to Customize the TikTok Provider, You Can Use the Following Configuration as a custom provider
*
* ```ts
* {
* async [customFetch](...args) {
* const url = new URL(args[0] instanceof Request ? args[0].url : args[0]);
* if (url.pathname.endsWith("/token/")) {
* const [url, request] = args;
* const customHeaders = {
* ...request?.headers,
* "content-type": "application/x-www-form-urlencoded",
* };
*
* const customBody = new URLSearchParams(request?.body as string);
* customBody.append("client_key", process.env.AUTH_TIKTOK_ID!);
*
* const response = await fetch(url, {
* ...request,
* headers: customHeaders,
* body: customBody.toString(),
* });
* const json = await response.json();
* return Response.json({ ...json });
* }
* return fetch(...args);
* },
*
* id: "tiktok",
* name: "TikTok",
* type: "oauth",
* client: {
* token_endpoint_auth_method: "client_secret_post",
* },
*
* authorization: {
* url: "https://www.tiktok.com/v2/auth/authorize",
* params: {
* client_key: options.clientId,
* scope: "user.info.profile", //Add scopes you need eg(user.info.profile,user.info.stats,video.list)
* },
* },
*
* token: "https://open.tiktokapis.com/v2/oauth/token/",
*
* userinfo: "https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,username", //Add fields you need eg(open_id,avatar_url,display_name,username)
*
* profile(profile) {
* return {
* id: profile.data.user.open_id,
* name: profile.data.user.display_name,
* image: profile.data.user.avatar_url,
* email: profile.data.user.email || profile.data.user.username || null,
* };
* },
* }
*
* ```
*
* :::
*
* :::info **Disclaimer**
Expand All @@ -195,62 +257,50 @@ export interface TiktokProfile extends Record<string, any> {
*
* :::
*/
export default function TikTok<P extends TiktokProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
export default function TikTok(
options: OAuthUserConfig<TiktokProfile>
): OAuthConfig<TiktokProfile> {
return {
async [customFetch](...args) {
const url = new URL(args[0] instanceof Request ? args[0].url : args[0])
if (url.pathname.endsWith("/token/")) {
const [url, request] = args

const customHeaders = {
...request?.headers,
"content-type": "application/x-www-form-urlencoded",
}

const customBody = new URLSearchParams(request?.body as string)
customBody.append("client_key", options.clientId!)
const response = await fetch(url, {
...request,
headers: customHeaders,
body: customBody.toString(),
})
const json = await response.json()
return Response.json({ ...json })
}
return fetch(...args)
},
id: "tiktok",
name: "TikTok",
type: "oauth",
client: {
token_endpoint_auth_method: "client_secret_post",
},
authorization: {
url: "https://www.tiktok.com/v2/auth/authorize",
params: {
client_key: options.clientId,
scope: "user.info.profile",
response_type: "code",
},
},

token: {
url: "https://open.tiktokapis.com/v2/oauth/token/",
async request({ params, provider }) {
const res = await fetch(provider.token?.url as unknown as string, {
method: "POST",
headers: {
"Cache-Control": "no-cache",
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
client_key: provider.clientId!,
client_secret: provider.clientSecret!,
code: params.code!,
grant_type: "authorization_code",
redirect_uri: provider.callbackUrl!,
}),
}).then((res) => res.json())
token: "https://open.tiktokapis.com/v2/oauth/token/",
userinfo:
"https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,username",

const tokens: TokenSet = {
access_token: res.access_token,
expires_at: res.expires_in,
refresh_token: res.refresh_token,
scope: res.scope,
id_token: res.open_id,
token_type: res.token_type,
session_state: res.open_id,
}
return {
tokens,
}
},
},
userinfo: {
url: "https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_url,display_name,username",
async request({ tokens, provider }) {
return await fetch(provider.userinfo?.url as URL, {
headers: { Authorization: `Bearer ${tokens.access_token}` },
}).then(async (res) => await res.json())
},
},
profile(profile) {
return {
id: profile.data.user.open_id,
Expand Down
Loading