本文是graphql极简入门教程的第八篇,内容主要讲述前后端接入github的Oauth功能,进一步完善用户系统。
graphql极简入门教程目录:
- 第一篇:基于react和graphql-yoga搭建前后端,并实现一个hello world
- 第二篇:基于prisma及sqlite,通过playground创建及查询数据
- 第三篇:在react中执行graphql的新增和查询操作
- 第四篇:react添加路由导航、前后端搜索功能
- 第五篇:添加分页及排序功能
- 第六篇:后端编写用户登录及注册功能
- 第七篇:前端对接用户系统
- 第八篇:前后端接入github的Oauth系统
- 第九篇:graphql实时订阅
接入三方登录
目前最方便申请的是github的OAuth登录,因此本文主要介绍使用github接入用户系统。对于OAuth不熟悉的同学,这里推荐阮一峰老师的文章👉🏻理解OAuth 2.0,笔者就不再造轮子讲述了。
在github申请OAuth
请先打开这个页面,申请接入github的oauth:github.com/settings/ap…
需要填写下面三个内容:
- 名称:随意填写
- 首页URL:请填写目前前端的地址:
http://localhost:3000 - 授权回调地址:请填写:
http://localhost:3000/oauth/redirect(在后面的内容中会实现这个页面)
在申请成功后会看到下面的页面,请注意此时你需要你点击Generate a new client secret按钮,生成一个Client secrets,到此前期准备工作就已经完成。
请注意保存已经生成的密钥!!!
前端添加github登录按钮
在顶部导航中添加在用户未登录时,展现github登录按钮,打开src/components/Header.js文件:
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { AUTH_TOKEN } from '../constants';
const Header = () => {
const navigate = useNavigate();
const authToken = localStorage.getItem(AUTH_TOKEN);
return (
<div className="flex pa1 justify-between nowrap orange">
<div className="flex flex-fixed black">
<Link to="/" className="no-underline black">
<div className="fw7 mr1">Hacker News</div>
</Link>
<Link to="/" className="ml1 no-underline black">
new
</Link>
<div className="ml1">|</div>
<Link
to="/search"
className="ml1 no-underline black"
>
search
</Link>
{authToken && (
<div className="flex">
<div className="ml1">|</div>
<Link
to="/create"
className="ml1 no-underline black"
>
submit
</Link>
</div>
)}
</div>
<div className="flex flex-fixed black">
{authToken ? (
<div
className="ml1 pointer"
onClick={() => {
localStorage.removeItem(AUTH_TOKEN);
navigate(`/`);
}}
>
logout
</div>
) : (
+ <>
<Link
to="/login"
className="ml1 no-underline black"
>
login
</Link>
+ <div className="ml1 black">|</div>
+ <Link
+ // ①
+ to="https://github.com/login/oauth/authorize?client_id={replace_your_client_id}&redirect_uri=http://localhost:3000/oauth/redirect"
+ className="ml1 no-underline black"
+ >
+ login with github
+ </Link>
+ </>
)}
</div>
</div>
);
};
export default Header;
需要做的事情很简单,就是让用户跳转到下面的链接👇🏻
https://github.com/login/oauth/authorize?client_id={replace_your_client_id}&redirect_uri=http://localhost:3000/oauth/redirect
⚠️注意:请把
{replace_your_client_id}内容替换成你自己申请的client id,也就是这里的内容:
完成上述操作后,如果你点击👉🏻login with github这个按钮:
就会跳转到下面的页面👇🏻
点击授权按钮后,github就会把你重定向到这个地址:
http://localhost:3000/oauth/redirect?code=79268d19346f44f4e0d4
此时就需要将l链接中的code参数回传给后端了。
定义后端数据类型
既然前端需要将code传给后端,那么后端接收完code后,就需要像login或者signup接口一样,回传AuthPayload类型数据给前端。
打开src/server/schema.graphql文件,增加下面新定义的数据类型:
type Mutation {
post(url: String!, description: String!): Link!
signup(email: String!, password: String!, name: String!): AuthPayload
login(email: String!, password: String!): AuthPayload
+ signupOrLoginWithGithub(code: String!): AuthPayload
}
完善后端逻辑
打开src/server/resolvers/Mutation.js文件,添加下面的实现方法
async function signupOrLoginWithGithub(parent, args, context, info) {
// ①
const { data: tokenData } = await axios.get('https://github.com/login/oauth/access_token', {
params: {
client_id: clientID,
client_secret: clientSecret,
code: args.code
}
});
// ②
const { access_token } = tokenData.split('&').reduce((acc, item) => {
const [key, value] = item.split('=');
acc[key] = value;
return acc;
}, {});
// ③
const { data: userData } = await axios({
method: 'get',
url: `https://api.github.com/user`,
headers: {
accept: 'application/json',
Authorization: `token ${access_token}`
}
});
// ④
let user = await context.prisma.user.findUnique({ where: { email: `id-${userData.id}@github.com` } })
if (!user) {
// ⑤
const password = await bcrypt.hash(generateRandomPassword(), 10)
user = await context.prisma.user.create({
data: {
email: `id-${userData.id}@github.com`,
name: userData.name,
password
}
})
}
// ⑥
const token = jwt.sign({ userId: user.id }, APP_SECRET)
// ⑦
return {
token,
user,
}
}
①:在前端把code回传给后端后,后端需要请求https://github.com/login/oauth/access_token接口,并将下面的数据传递给github👇🏻,来获取access_token数据。
client_id:github申请的idclient_secret:github申请的密钥code:前端从github获取的code
②:由于github会回传类似于下面的内容:
access_token=gho_04gHhoG1lqqbnVJ2lzUA0FPrRZPaV01ylgBI&scope=&token_type=bearer
因此需要额外的代码进行处理,拿到access_token字段。
③:请求https://api.github.com/user接口,将github回传的access_token放到headers中请求用户数据。
由于有些用户可能会设置email不可见,获取不到邮箱,因此笔者在这里会将拿到的用户github id按照下面的格式拼接成一个email:
id-${userData.id}@github.com
所以在④中拿到用户信息后,需要先查库,看看当前用户是否已经使用github注册过
⑤:如果没有注册,系统会自动帮用户进行注册:
- 用户:名采用
github的用户名: - 密码:随机生成一个
在
src/server/utils.js中添加下面的工具代码,随机生成密码:function generateRandomPassword() { return Math.random().toString(36).slice(-8); }
并将生成的用户信息挂载在user变量上。
⑥:使用注册后或者查询到的用户id,生成token
⑦:返回用户数据
完成前端github登录重定向页面
由于之前填写的重定向页面是:http://localhost:3000/oauth/redirect。因此就需要添加相关页面完成下面的功能:
- 上报
github返回的code给后端 - 处理登录成功后存储
token及重定向的页面到首页
因此需要在src/components文件夹下,添加Oauth.js文件:
import {useSearchParams} from "react-router-dom";
import {useMutation} from "urql";
import {useEffect} from "react";
export const signupOrLoginWithGithubMutation = `
mutation SignupOrLoginWithGithubMutation(
$code: String!
) {
signupOrLoginWithGithub(code: $code) {
token
}
}
`;
const Oauth = () => {
const [searchParams] = useSearchParams();
const [,login] = useMutation(signupOrLoginWithGithubMutation);
useEffect(() => {
// ①
const code = searchParams.get('code');
if (code) {
// ②
login({code}).then(({data}) => {
localStorage.setItem('token', data.signupOrLoginWithGithub.token);
window.location.href = '/';
});
}
}, [])
return (
<div>
Login form github....please wait for a moment
</div>
)
}
export default Oauth;
在①中,前端会获取链接中的code参数
②中上报给后端,并在请求成功后,将返回的token存储在localstorage中,并重定向到首页。
至此我们就完成了一个较为完整的用户系统,下一章将会是本系列的最后一篇文章,主要介绍graphql的实时订阅功能。
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 8 天,点击查看活动详情