社交登录 - Twitter(前后端完整实现)

目标

用 Twitter 登录 - 拿到用户 Twitter 账户信息(如用户名、头像、粉丝数等)。

核心流程

  1. 前端"发起授权",跳转至 Twitter 授权页面,授权通过后重定向回前端页面,并在 URL 中携带 授权码(code)
  2. 前端将授权码发送给后端,后端通过授权码调用 Twitter API 换取 访问令牌(access_token) ,再使用 access_token 调用用户信息接口,最终返回用户数据给前端。

配置应用(Twitter Developer)

  1. 申请一个twitter的开发这账号: developer.x.com/en/portal/p...

  2. 进入 Dashboard ,创建一个新的 App

  3. 记录应用的 client_id(不是 API Key)并在 App 的 Callback URLs 添加你的 redirect_uri (前端常用的开发环境 localhost 也是可以直接使用的,但注意必须一致,哪怕是最后的 / )

前端获取授权码(code)

  1. 打开 twitter 授权弹窗
typescript 复制代码
'use client';

export const LoginButton = () => {

	const handleLogin = () => {
	  // 上面步骤中的clientId 与 url
		const client_id = '';
		const redirect_uri = '<http://localhost:3000/>';
		const scope = 'users.read tweet.read';
		const state = 'test';

		const twitterUrl = `https://twitter.com/i/oauth2/authorize?response_type=code
		&client_id=${client_id}
		&redirect_uri=${encodeURIComponent(redirect_uri)}
		&scope=${encodeURIComponent(scope)}
		&state=${state}
		&code_challenge=challenge&code_challenge_method=plain`;

		const popup = window.open(twitterUrl, 'twitter', 'width=450,height=730');
	}

	return (
		<button
			className="bg-black text-white px-4 py-2 rounded-md"
			onClick={() => { handleLogin() }}
		>
			Login with Twitter
		</button>
	)
}
  1. 获取 code

授权后,重定向回指定url的页面时,url中已经带上了对应的code, 但这个数据在弹窗中,而不在原页面,我们可以使用 PostMessage 在窗口间传递数据

现在我们成功跨窗口收到了对应的code数据(别忘了关闭弹窗)

typescript 复制代码
'use client';
import { useEffect } from "react";
import { LoginButton } from "./components/LoginButton";
import { useSearchParams } from "next/navigation";
import { getUserInfo } from "./api/twitter";

export default function Home() {
  const params = useSearchParams();

  useEffect(() => {
    // 从url中获取code
    const code = params.get('code');

    if (code) {
      // 发送消息给父窗口
      window.opener.postMessage({
        type: 'twitter_login_success',
        data: code
      }, '<http://localhost:3000/>');

      // 关闭当前窗口
      window.close();
    }
  }, []);

  useEffect(() => {
    const allowedOrigin = '<http://localhost:3000>';

    // 监听消息
    const onMessage = async (event: MessageEvent) => {
      if (event.origin !== allowedOrigin) return;

      const data = event.data;
      if (!data || typeof data !== 'object') return;
      if (data.type !== 'twitter_login_success') return;

      // 从消息中获取code
      const code = data.data;

      // 发送 token 给服务端获取用户信息
      const userInfo = await getUserInfo(code);

      console.log("this is the user info", userInfo);
    };

    window.addEventListener('message', onMessage);
    return () => window.removeEventListener('message', onMessage);
  }, []);

  return (
    <div className="p-10">
      <LoginButton />
    </div>
  );
}

服务端获取用户信息

(由于不是很会后端,所以让ai 生成了一份接口,测试部署下来是可行, 有不足之处还请各位大佬多多见谅)

用授权码换取 access_token

typescript 复制代码
	// 1. 换取 access_token
	const tokenRes = await fetch('<https://api.twitter.com/2/oauth2/token>', {
		method: 'POST',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded',
			Authorization: `Basic ${basic}`,
		},
		body: tokenParams.toString(),
	});
	if (!tokenRes.ok) return json({ error: 'Twitter token error' }, env, 400);
	const token = (await tokenRes.json()) as { access_token?: string };
	if (!token.access_token) return json({ error: 'No access token received' }, env, 400);

获取用户信息

typescript 复制代码
	// 2. 拉取用户信息
	const fields =
		'created_at,description,entities,id,location,name,pinned_tweet_id,profile_image_url,protected,public_metrics,url,username,verified,withheld';
	const meRes = await fetch(`https://api.twitter.com/2/users/me?user.fields=${fields}`, {
		headers: { Authorization: `Bearer ${token.access_token}` },
	});
	if (!meRes.ok) return json({ error: 'Twitter profile error' }, env, 400);
	const me = (await meRes.json()) as { data?: any; errors?: any[] };
	if (!me.data) return json({ error: 'No user data received' }, env, 400);

	// 3. 返回用户对象
	const user = {
		avatar: me.data.profile_image_url,
		username: me.data.username,
		name: me.data.name,
		id: me.data.id,
		verified: me.data.verified,
		followers_count: me.data.public_metrics?.followers_count ?? 0,
		following_count: me.data.public_metrics?.following_count ?? 0,
		tweet_count: me.data.public_metrics?.tweet_count ?? 0,
		created_at: me.data.created_at,
		description: me.data.description,
		location: me.data.location,
	};
相关推荐
狂炫冰美式13 分钟前
人均配了AI, 为什么公司还是没变快? 🤔 本质还是分布式系统问题
前端·后端·架构
乘风gg1 小时前
多 Agent 不是万能的!搞懂这 5 个原则,少走 1 年弯路!
前端·agent·ai编程
猩猩程序员2 小时前
Vercel 推出 Agent 框架 Eve:让 AI Agent 像写 Web 应用一样简单
前端
爱读源码的大都督2 小时前
Claude Code源码分析(三):为什么系统提示词中需要有tools呢?
前端·人工智能·后端
爱勇宝2 小时前
Claude Code 被曝暗藏“隐形检测”代码:封代理不是最可怕的,可怕的是你根本不知道它在干什么
前端·后端·程序员
小牛不牛的程序员3 小时前
我用 Claude Code 半天撸完了一个完整网站,AI 编程到底提升了多少效率?
前端
东风破_3 小时前
JavaScript 面试常考的字符串算法:从反转字符串到回文判断
前端·javascript
ITOM运维行者3 小时前
从零搭建企业级服务器监控体系:踩坑实录与架构设计
前端·后端
monologues3 小时前
深入 Vue 3 源码:响应式系统的精妙设计与编译优化
前端
hunterandroid3 小时前
Paging 3 分页:从手动分页到声明式加载
前端