grahql极简入门教程(基于react+graphql-yoga+urql)⑧

130 阅读4分钟

本文是graphql极简入门教程的第八篇,内容主要讲述前后端接入github的Oauth功能,进一步完善用户系统。

graphql极简入门教程目录:

👉🏻点击进入本教程github仓库

接入三方登录

目前最方便申请的是github的OAuth登录,因此本文主要介绍使用github接入用户系统。对于OAuth不熟悉的同学,这里推荐阮一峰老师的文章👉🏻理解OAuth 2.0,笔者就不再造轮子讲述了。

在github申请OAuth

请先打开这个页面,申请接入github的oauth:github.com/settings/ap…

需要填写下面三个内容:

  • 名称:随意填写
  • 首页URL:请填写目前前端的地址:http://localhost:3000
  • 授权回调地址:请填写:http://localhost:3000/oauth/redirect(在后面的内容中会实现这个页面)

申请oauth

在申请成功后会看到下面的页面,请注意此时你需要你点击Generate a new client secret按钮,生成一个Client secrets,到此前期准备工作就已经完成。

请注意保存已经生成的密钥!!!

image-20230211225643994

前端添加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,也就是这里的内容: client id

完成上述操作后,如果你点击👉🏻login with github这个按钮:

登录github

就会跳转到下面的页面👇🏻

image-20230211213502419

点击授权按钮后,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_idgithub申请的id
  • client_secretgithub申请的密钥
  • 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 天,点击查看活动详情