react+apollo+prisma入门demo搭建---5、身份验证

孔俊爽
2023-12-01

react+apollo+prisma入门demo搭建—5、身份验证

根据HOW TO GRAPHQL官网的例子,做了些对最新版的改动,适合最新框架的学习。

本系列文章注重前端方面的开发,对于node方面的放在下一个系列。在此过程中有任何问题,都欢迎在评论中提问,会及时反馈

系列目录:

第一章. Frontend开始
第二章. Queries组件编写(Loading Links)
第三章. Mutations组件编写(Creating Links)
第四章. 页面路由
第五章. 身份验证

为了照顾更加全面的读者,我写的会尽量详细,熟练的开发者可进行快速选择性的阅读

页面路由

本节我们来学习如何使用Apollo实现身份验证功能,以便为用户提供注册和登录功能。

准备Login组件

在components文件夹中创建Login.js文件

import React, { Component } from 'react'
import { AUTH_TOKEN } from '../constants'

class Login extends Component {
  state = {
    login: true, // switch between Login and SignUp
    email: '',
    password: '',
    name: '',
  }

  render() {
    const { login, email, password, name } = this.state
    return (
      <div>
        <h4 className="mv3">{login ? 'Login' : 'Sign Up'}</h4>
        <div className="flex flex-column">
          {!login && (
            <input
              value={name}
              onChange={e => this.setState({ name: e.target.value })}
              type="text"
              placeholder="Your name"
            />
          )}
          <input
            value={email}
            onChange={e => this.setState({ email: e.target.value })}
            type="text"
            placeholder="Your email address"
          />
          <input
            value={password}
            onChange={e => this.setState({ password: e.target.value })}
            type="password"
            placeholder="Choose a safe password"
          />
        </div>
        <div className="flex mt3">
          <div className="pointer mr2 button" onClick={() => this._confirm()}>
            {login ? 'login' : 'create account'}
          </div>
          <div
            className="pointer button"
            onClick={() => this.setState({ login: !login })}
          >
            {login
              ? 'need to create an account?'
              : 'already have an account?'}
          </div>
        </div>
      </div>
    )
  }

  _confirm = async () => {
    // 接下来需要完成的
  }

  _saveUserData = token => {
    localStorage.setItem(AUTH_TOKEN, token)
  }
}

export default Login

该组件通过this.state.login状态来实现登录和注册页面的切换,但状态为true时,页面展示只有两个输入框的登录页面,当状态变为false时,展现三个输入框的注册页面。

_confirm 函数将用于实现我们需要为登录功能发送的变更请求。
接下来,我们还需要一个constants.js文件,我们用它来定义我们存储在浏览器的localStorage中的凭据的key。

在src目录下新建constants.js文件

export const AUTH_TOKEN = 'auth-token'

然后我们将新的Login组件加入到我们的路由当中,打开App.js

<Switch>
         <Route exact path="/" component={LinkList} />
         <Route exact path="/create" component={CreateLink} />
         <Route exact path="/login" component={Login} />
</Switch>

并引入Login组件

import Login from './Login'

最后,我们再Header组件中加上可以跳转至Login的导航

render() {
  const authToken = localStorage.getItem(AUTH_TOKEN)
  return (
    <div className="flex pa1 justify-between nowrap orange">
      <div className="flex flex-fixed black">
        <div className="fw7 mr1">Hacker News</div>
        <Link to="/" className="ml1 no-underline black">
          new
        </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">
        {authToken ? (
          <div
            className="ml1 pointer black"
            onClick={() => {
              localStorage.removeItem(AUTH_TOKEN)
              this.props.history.push(`/`)
            }}
          >
            logout
          </div>
        ) : (
          <Link to="/login" className="ml1 no-underline black">
            login
          </Link>
        )}
      </div>
    </div>
  )
}

在Header的右侧,我们加入了前往Login组件的导航,并且我们会先判断用户登录与否,没登录的话不提供create页面的导航

别忘了要在Header中引入需要的依赖

import { AUTH_TOKEN } from '../constants'

使用有身份验证的变更

注册和登录是两个常规的GraphQL变更,我们可以像使用之前的createLink变更一样使用它。
打开Login.js,在前面添加两个变更定义

const SIGNUP_MUTATION = gql`
  mutation SignupMutation($email: String!, $password: String!, $name: String!) {
    signup(email: $email, password: $password, name: $name) {
      token
    }
  }
`

const LOGIN_MUTATION = gql`
  mutation LoginMutation($email: String!, $password: String!) {
    login(email: $email, password: $password) {
      token
    }
  }
`

同时在className为flex mt3的div中添加以下代码

<div className="flex mt3">
  <Mutation
    mutation={login ? LOGIN_MUTATION : SIGNUP_MUTATION}
    variables={{ email, password, name }}
    onCompleted={data => this._confirm(data)}
  >
    {mutation => (
      <div className="pointer mr2 button" onClick={mutation}>
        {login ? 'login' : 'create account'}
      </div>
    )}
  </Mutation>
  <div
    className="pointer button"
    onClick={() => this.setState({ login: !login })}
  >
    {login ? 'need to create an account?' : 'already have an account?'}
  </div>
</div>

并添加所需依赖

import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'

现在我们终于要开始实现_confirm函数了

_confirm = async data => {
  const { token } = this.state.login ? data.login : data.signup
  this._saveUserData(token)
  this.props.history.push(`/`)
}

这样我们就已经基本实现了一个带有身份功能的Login组件了,接下来就是Apollo如何配合的事情了

使用身份验证token配置Apollo

既然我们已经可以拿到token了,那么我们就需要把token添加进我们的每一个请求中。
Apollo提供了一种很好的方法,通过使用中间件的概念来验证所有请求,实现为Apollo Link。
让我们来添加该依赖

yarn add apollo-link-context

然后我们需要改造我们的index.js,在创建httpLink和ApolloClient实例之间放置以下代码:

const authLink = setContext((_, { headers }) => {
  const token = localStorage.getItem(AUTH_TOKEN)
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : ''
    }
  }
})

并且引入依赖

import { setContext } from 'apollo-link-context'
import { AUTH_TOKEN } from './constants'

每次ApolloClient向服务器发送请求时,都会调用此中间件。

Note: 如果你想了解更多关于Apollo关于身份验证方面的信息,点击这里

接下来我们要确保link被正确配置进apollo client

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache()
})

现在如果token存在的话,我们所发送的所有请求都会通过身份验证了

在服务端启用身份验证

我们需要确保只有通过身份验证的用户才能发布新链接,所以需要在服务端做一点小更改
打开/server/src/resolvers/Mutation.js,找到post函数,修改为以下

function post(parent, args, context) {
  const userId = getUserId(context)
  return context.prisma.createLink({
    url: args.url,
    description: args.description,
    postedBy: { connect: { id: userId } },
  })
}

我们从请求的Authorization header中提取userId,并作为link的一个postedBy属性传出。

现在让我们重启两个服务,然后注册登录账户,发送一个create请求试试吧,目前发送create请求回到LinkList页面时并不会展示你刚才添加的那一条,需要手动刷新,这个我们之后会解决这个问题。

本节我们成功实现了页面的身份验证配置!马上开始下一步的旅程吧。

本章项目github 分支地址

https://github.com/zust-hh/simple-hackernews/tree/Frontend-topic5

 类似资料: