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

feat(core/providers): add wechat provider #10236

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

KaygNas
Copy link

@KaygNas KaygNas commented Mar 7, 2024

☕️ Reasoning

Support WeChat Login

FYI: Getting a WECHAT_APP_ID and WECHAT_APP_SCRECT is a bit difficult because audition is required for every application. However there are TEST ACCOUNT provided, follow the instruction below would help to do that:

0. Apply a WeChat Account

Download WeChat and apply an account.

1. Apply a test account

Open the WeChat Official Account Test Account and scan the QRCode with WeChat on your phone.

image

2. Copy the APP_ID and APP_SCRET to the .env.local file

Once you login the website, the APP_ID and APP_ACCOUNT would generated automatically, just copy them to the .env.local file.

image

3. Set the redirect_uri with your local IP

Make sure your redirct_uri is set correctly

image

4. Download WeChat Dev Tool

The login can be only done in the environment of WeChat Browser, therefore it would be convenient to development using the WeChat Dev Tool. Accurately, it's not really convenient 🥲.

Download WeChat Dev Tool, select the stable version.

image

5. Access Your Localhost by the IP Using the Dev Tool

Click the Official Account Tab,

image

Start the app/examples/nextjs project, and input your localhost IP and click Sign In button

image

Click Sign In With WeChat

image

6. Sign In Succeed

image

🧢 Checklist

  • Documentation
  • Tests
  • Ready to be merged

🎫 Affected issues

Fixes: #7960

📌 Resources

Copy link

vercel bot commented Mar 7, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
auth-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Mar 7, 2024 10:48am
1 Ignored Deployment
Name Status Preview Comments Updated (UTC)
next-auth-docs ⬜️ Ignored (Inspect) Visit Preview Mar 7, 2024 10:48am
Copy link

vercel bot commented Mar 7, 2024

@KaygNas is attempting to deploy a commit to the authjs Team on Vercel.

A member of the Team first needs to authorize it.

@tonyabracadabra
Copy link

Thanks this look dope

@tonyabracadabra
Copy link

Hey @balazsorban44, noticed many WeChat PRs got staled earlier on. Mind giving this one a look? Thanks for the help!

@KaygNas
Copy link
Author

KaygNas commented Mar 10, 2024

Hey @balazsorban44, noticed many WeChat PRs got staled earlier on. Mind giving this one a look? Thanks for the help!

@tonyabracadabra Hi there, you may adopt the provider into your app by yourself, have a look at this repo, and follow the steps below:

  1. Copy the wechat.ts file into your project
  2. Import the WeChatProvider into your auth.ts file
  3. Copy the .env.local.example into your project and rename to .env.local, also fill the Auth_WECHAT_APP_ID and AUTH_WECHAT_APP_SECRET.

After all there done, start your app and it should be work.😉

@Yidadaa
Copy link

Yidadaa commented Mar 12, 2024

@balazsorban44 @ndom91 @ThangHuuVu

WeChat has 1.36 billion users worldwide, and I believe integrating this provider into NextAuth would benefit a lot of people. It would also enhance the usability of NextAuth. I hope you'll consider merging this PR.

@swingislee
Copy link

Hello, I am a pure beginner. I have implemented username and email login in my project. Now I am trying to use the file you provided to complete the third-party login of WeChat, but after I put the wechat.ts file in to the root directory of my project, the following error occurred:

Object literal may only specify known properties, and 'options' does not exist in type 'OAuth2Config

'.ts(2353)
(property) options: OAuthUserConfig

& {
platformType: 'OfficialAccount' | 'WebsiteApp';
}

image

Please tell me if there is any step that I forgot to complete. I would be very grateful if I could get your guidance.

@KaygNas
Copy link
Author

KaygNas commented Mar 19, 2024

Hello, I am a pure beginner. I have implemented username and email login in my project. Now I am trying to use the file you provided to complete the third-party login of WeChat, but after I put the wechat.ts file in to the root directory of my project, the following error occurred:

Object literal may only specify known properties, and 'options' does not exist in type 'OAuth2Config

'.ts(2353) (property) options: OAuthUserConfig

& { platformType: 'OfficialAccount' | 'WebsiteApp'; }

image

Please tell me if there is any step that I forgot to complete. I would be very grateful if I could get your guidance.

@swingislee It can be a problem caused by your dependencies, please make sure next-auth@5.0.0-beta.15 is correctly installed. Otherwise, try clone this repo nextjs-auth-wechat and make comparison to figure out where the problem is?

@swingislee
Copy link

@swingislee It can be a problem caused by your dependencies, please make sure next-auth@5.0.0-beta.15 is correctly installed. Otherwise, try clone this repo nextjs-auth-wechat and make comparison to figure out where the problem is?

@KaygNas When I cloned your project and tried to run it on my local windows PC, I got the same error. I checked that my version was indeed next-auth@5.0.0-beta.15.

Being a pure beginner, I did some simple tests. If I create a new project in the @auth/core directory, an error will appear for the option value. If I copy your wechat code intact to other ts documents in the same directory, the error will disappear, so I am guessing whether it is because a new oauth policy has been added, but auth.v5 also needs to update settings in some places that I don't know to make this policy take effect?

@KaygNas
Copy link
Author

KaygNas commented Mar 20, 2024

@swingislee It turns out options is documented as @internal so that TS wouldn't compile it into the output types because the stripInternal config is true. So the simplest solution is to cheat TS by annotate the type as any, check out this commit.

@tonyabracadabra
Copy link

@swingislee It turns out options is documented as @internal so that TS wouldn't compile it into the output types because the stripInternal config is true. So the simplest solution is to cheat TS by annotate the type as any, check out this commit.

You can do as OAuthConfig<P>

@tonyabracadabra
Copy link

there was a few eslint issues in the original code and I've fixed them and compiled a new version you can try that out

import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers";
import { env } from "~/env";

export interface WeChatProfile {
  openid: string;
  nickname: string;
  sex: number;
  province: string;
  city: string;
  country: string;
  headimgurl: string;
  privilege: string[];
  unionid: string;
  [claim: string]: unknown;
}

interface WeChatTokenResponse {
  access_token: string;
  expires_in: number;
  refresh_token: string;
  openid: string;
  scope: string;
  unionid: string;
}

export default function WeChat<P extends WeChatProfile>(
  options: OAuthUserConfig<P> & {
    platformType: "OfficialAccount" | "WebsiteApp";
  },
): OAuthConfig<P> {
  const {
    clientId = env.WECHAT_APP_ID,
    clientSecret = env.WECHAT_APP_SECRET,
    platformType,
  } = options;

  if (platformType !== "OfficialAccount" && platformType !== "WebsiteApp") {
    throw new Error("Invalid plaformType");
  }

  const authorizationEndpointUrl =
    platformType === "OfficialAccount"
      ? "https://open.weixin.qq.com/connect/oauth2/authorize"
      : "https://open.weixin.qq.com/connect/qrconnect";
  const authorizationScope =
    platformType === "OfficialAccount" ? "snsapi_userinfo" : "snsapi_login";
  const authorization = {
    url: authorizationEndpointUrl,
    params: {
      appid: clientId,
      response_type: "code",
      scope: authorizationScope,
      state: Math.random().toString(),
    },
  };

  const tokenEndpointUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
  const token = {
    url: tokenEndpointUrl,
    params: {
      appid: clientId,
      secret: clientSecret,
      code: "CODE",
      grant_type: "authorization_code",
    },
    conform: async (response: Response) => {
      const data = (await response.json()) as WeChatTokenResponse;
      const conformedResponse = new Response(
        JSON.stringify({
          ...data,
          token_type: "bearer",
        }),
        response,
      );
      return conformedResponse;
    },
  };

  const userinfo = {
    url: "https://api.weixin.qq.com/sns/userinfo",
    request: async ({
      tokens,
      provider,
    }: {
      tokens: { access_token: string; openid: string };
      provider: { userinfo?: { url?: string } };
    }) => {
      const url = new URL(provider.userinfo?.url ?? "");
      url.searchParams.set("access_token", tokens.access_token);
      url.searchParams.set("openid", String(tokens.openid));
      url.searchParams.set("lang", "zh_CN");
      const response = await fetch(url);
      return response.json() as Promise<P>;
    },
  };

  const profile = (profile: WeChatProfile) => {
    return {
      id: profile.unionid,
      name: profile.nickname,
      email: null,
      image: profile.headimgurl,
    };
  };

  return {
    id: "wechat",
    name: "WeChat",
    type: "oauth",
    style: { logo: "/wechat.svg", bg: "#fff", text: "#000" },
    checks: ["pkce", "state"],
    clientId,
    clientSecret,
    authorization,
    token,
    userinfo,
    profile,
    options,
  } as OAuthConfig<P>;
}
@MarvinXu
Copy link

there was a few eslint issues in the original code and I've fixed them and compiled a new version you can try that out

import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers";
import { env } from "~/env";

export interface WeChatProfile {
  openid: string;
  nickname: string;
  sex: number;
  province: string;
  city: string;
  country: string;
  headimgurl: string;
  privilege: string[];
  unionid: string;
  [claim: string]: unknown;
}

interface WeChatTokenResponse {
  access_token: string;
  expires_in: number;
  refresh_token: string;
  openid: string;
  scope: string;
  unionid: string;
}

export default function WeChat<P extends WeChatProfile>(
  options: OAuthUserConfig<P> & {
    platformType: "OfficialAccount" | "WebsiteApp";
  },
): OAuthConfig<P> {
  const {
    clientId = env.WECHAT_APP_ID,
    clientSecret = env.WECHAT_APP_SECRET,
    platformType,
  } = options;

  if (platformType !== "OfficialAccount" && platformType !== "WebsiteApp") {
    throw new Error("Invalid plaformType");
  }

  const authorizationEndpointUrl =
    platformType === "OfficialAccount"
      ? "https://open.weixin.qq.com/connect/oauth2/authorize"
      : "https://open.weixin.qq.com/connect/qrconnect";
  const authorizationScope =
    platformType === "OfficialAccount" ? "snsapi_userinfo" : "snsapi_login";
  const authorization = {
    url: authorizationEndpointUrl,
    params: {
      appid: clientId,
      response_type: "code",
      scope: authorizationScope,
      state: Math.random().toString(),
    },
  };

  const tokenEndpointUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
  const token = {
    url: tokenEndpointUrl,
    params: {
      appid: clientId,
      secret: clientSecret,
      code: "CODE",
      grant_type: "authorization_code",
    },
    conform: async (response: Response) => {
      const data = (await response.json()) as WeChatTokenResponse;
      const conformedResponse = new Response(
        JSON.stringify({
          ...data,
          token_type: "bearer",
        }),
        response,
      );
      return conformedResponse;
    },
  };

  const userinfo = {
    url: "https://api.weixin.qq.com/sns/userinfo",
    request: async ({
      tokens,
      provider,
    }: {
      tokens: { access_token: string; openid: string };
      provider: { userinfo?: { url?: string } };
    }) => {
      const url = new URL(provider.userinfo?.url ?? "");
      url.searchParams.set("access_token", tokens.access_token);
      url.searchParams.set("openid", String(tokens.openid));
      url.searchParams.set("lang", "zh_CN");
      const response = await fetch(url);
      return response.json() as Promise<P>;
    },
  };

  const profile = (profile: WeChatProfile) => {
    return {
      id: profile.unionid,
      name: profile.nickname,
      email: null,
      image: profile.headimgurl,
    };
  };

  return {
    id: "wechat",
    name: "WeChat",
    type: "oauth",
    style: { logo: "/wechat.svg", bg: "#fff", text: "#000" },
    checks: ["pkce", "state"],
    clientId,
    clientSecret,
    authorization,
    token,
    userinfo,
    profile,
    options,
  } as OAuthConfig<P>;
}

I still have type errors. Do you have a working repo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core Refers to `@auth/core` examples providers
5 participants