From b2a1642806def1eff8a499f020bb6917395f58cf Mon Sep 17 00:00:00 2001 From: DewinU Date: Fri, 1 Nov 2024 13:54:47 -0600 Subject: [PATCH 1/2] refactor(tiktok): Update TikTok provider configuration - Added custom fetch --- packages/core/src/providers/tiktok.ts | 144 +++++++++++++++++--------- 1 file changed, 97 insertions(+), 47 deletions(-) diff --git a/packages/core/src/providers/tiktok.ts b/packages/core/src/providers/tiktok.ts index 73f29c24d0..bb60110b13 100644 --- a/packages/core/src/providers/tiktok.ts +++ b/packages/core/src/providers/tiktok.ts @@ -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 { +export interface TiktokProfile { data: { user: { /** @@ -137,6 +137,8 @@ export interface TiktokProfile extends Record { * ``` * * #### 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" @@ -144,7 +146,7 @@ export interface TiktokProfile extends Record { * 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 }), * ], * }) * ``` @@ -153,6 +155,7 @@ export interface TiktokProfile extends Record { * - [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) * * * ### Notes @@ -160,6 +163,7 @@ export interface TiktokProfile extends Record { * :::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. * * ::: * @@ -171,7 +175,7 @@ export interface TiktokProfile extends Record { * * :::tip * - * Client_ID will be the Client Key in the TikTok Application + * AUTH_TIKTOK_ID will be the Client Key in the TikTok Application * * ::: * @@ -183,6 +187,64 @@ export interface TiktokProfile extends Record { * 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** @@ -195,62 +257,50 @@ export interface TiktokProfile extends Record { * * ::: */ -export default function TikTok

( - options: OAuthUserConfig

-): OAuthConfig

{ +export default function TikTok( + options: OAuthUserConfig +): OAuthConfig { 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, From 7d2f0ba8ea8e4a176bac0f377173ad22de988d72 Mon Sep 17 00:00:00 2001 From: DewinU Date: Fri, 1 Nov 2024 14:27:48 -0600 Subject: [PATCH 2/2] fix format --- .../getting-started/authentication/credentials.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/pages/getting-started/authentication/credentials.mdx b/docs/pages/getting-started/authentication/credentials.mdx index 526cea9e37..e7af253de6 100644 --- a/docs/pages/getting-started/authentication/credentials.mdx +++ b/docs/pages/getting-started/authentication/credentials.mdx @@ -18,17 +18,17 @@ To setup Auth.js with any external authentication mechanisms or use a traditiona Links](/getting-started/authentication/email), or [WebAuthn (Passkeys)](/getting-started/authentication/webauthn) options instead. - However, we also want to be flexible and support anything - you deem appropriate for your application and use case, - so there are no plans to remove this provider. +However, we also want to be flexible and support anything +you deem appropriate for your application and use case, +so there are no plans to remove this provider. By default, the Credentials provider does not persist data in the database. - However, you can still create and save any data in your database, - you just have to provide the necessary logic, eg. to encrypt passwords, - add rate-limiting, add password reset functionality, etc. + However, you can still create and save any data in your database, you just + have to provide the necessary logic, eg. to encrypt passwords, add + rate-limiting, add password reset functionality, etc.