apollo+react实战项目 stepbystep(五)

用户注册和认证

1、准备登陆和注册组件

注册需要name,email,password,登陆需要email和password

 创建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 () => {
    // ... you'll implement this 🔜
  }

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

export default Login

  当用户要登录login为true的时候,有两个输入框分别代表email和password,如果用户没有账号,需要注册时login为false,这时再增加一个name的输入框。

  在三个输入框下面有两个按钮:一个按钮时登录或创建账户按钮,一个是切换登录界面和注册界面的按钮。

  __confrim是点击登录或创建账户按钮时要执行的操作,这里我们应该引用Mutation组件的signup或者login解析函数,然后得到一个token。

  __saveUserData是将服务器端传递来的token存储到localStorage。注意:在实际生产环境中,千万不能把任何敏感信息存储到localStorage中。

创建一个constants.js用于存放常量,如AUTH_TOKEN。

src/constants.js

export const AUTH_TOKEN = 'auth-token'

  

2、将登陆组件放到Router内,同时在Header.js组件中增加对应的Link

App.js增加Route

render() {
  return (
    <div className="center w85">
      <Header />
      <div className="ph3 pv1 background-gray">
        <Switch>
          <Route exact path="/" component={LinkList} />
          <Route exact path="/create" component={CreateLink} />
          <Route exact path="/login" component={Login} />
        </Switch>
      </div>
    </div>
  )
}

  

在Header组件中增加一个Link,显示login或者logout

如果本地已经存储了token,则显示logout,当点击logout的时候清空localStorage内的token,并且重新导航到列表页面,如果没有token,则使用Link组件连接到‘/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>
  )
}

  首先后去本地的token,第二步根据authToken来确定显示的组件。

  同时我们设置如果用户没有登录的话,则不显示submit组件,即未注册的用户不能新建连接。当然这样并不能阻止用户访问该页面并新增连接,因为用户可以自行输入地址/create显示出创建页面。

因此,我们需要在createLink组件中也增加对authToken的判断,如果已经登录,则显示CreateLink组件,否则的话《Redirect from=‘/create’ to='/》重定向到列表页面。

CreateLink.js如下:

import React from 'react';
import {Mutation} from 'react-apollo';
import gql from 'graphql-tag';
import {Redirect} from 'react-router-dom';

import {AUTH_TOKEN} from '../constants'


export default class CreateLink extends React.Component{
  constructor(props){
    super(props);
    this.state={
      description:'',
      url:'',
    }
  }


  render(){
    const POST_MUTATION=gql`
      mutation PostMutation($description:String!,$url:String!){
        post(description:$description,url:$url){
          id
          description
          createdAt
          url
        }
      }
    `
    const {description,url} = this.state;
    const authToken = localStorage.getItem(AUTH_TOKEN)
    if(authToken){
      return(<div>
        <div className="flex flex-column mt3">
          <input
            className="mb2"
            type='text'
            value={description}
            placeholder='description'
            onChange={e=>this.setState({description:e.target.value})}
          />
          <input
            className="mb2"
            type='text'
            value={url}
            placeholder='url'
            onChange={e=>this.setState({url:e.target.value})}
          />
        </div>
        <Mutation
          mutation={POST_MUTATION}
          variables={{description,url}}
          onCompleted={()=>this.props.history.push('/')}
          >
          {
            postMutation=>(
              <button onClick={postMutation}>Submit</button>
            )
          }
        </Mutation>
      </div>);
    }else{
      return (<Redirect from='/create' to='/' />);
    }
  }
}

  3、注册验证

现在显示组件都已经添加完成了,我们需要将login和signup解析函数绑定到onClick事件上。

login.js中

第一步 使用gql转换mutaition字符串为文档

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
    }
  }
`

  第二步:添加Mutation组件,根据login的状态传递不同的mutation

<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>

  这里我们传递三个参数email,password和name.这样render props里的mutation相当于signup(email,password,name)或者login(email,password).将mutation绑定到onClick事件上。当点击事件触发mutation完成之后即注册或登陆成功之后,我们将获取的data.signup.token或者data.login.token保存到本地,并且重定向到列表页面。

__confirm函数:

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

 现在我们已经可以使用登陆和注册功能了,当登陆成功后可以看到submit按钮,并且可以新增一个连接。

4、使用apollo的验证功能

现在我们已经可以注册登陆并且从服务器获取token了,上面我们也看到了,我们仅仅是通过不显示CreateLink组件的方式来阻止未登陆的用户创建连接,事实上我们可以将token加到用户发送的请求上,以防止未验证登陆成功的用户得到任何信息。 而不仅仅是采用隐藏组件的方式。

如何将token附加到每次向服务器发送的请求中?apolloClient负责向服务端发送请求。事实上,apollo提供了一个中间件,在发送所有请求前可以通过该中间件,这就是apollo-link.

首先我们需要添加一个相关的依赖库:

yarn add apollo-link-context

  apollo-link-context是用来设置操作的上下文,可以被所有其他的links引用。

setContext是apollo-link-context中的主要方法,他接受一个函数,该函数返回一个对象或者是promise,promise的结果供下一个请求引用。setContext接受两个参数,一个是Graphql 请求,另一个是前一个context.此方法生成的链接可以轻松执行身份验证令牌等异步查找。

在index.js中:

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

  这里我们将authLink与向服务器发送的link链接在一起。

const httpLink = createHttpLink({
  uri: 'http://localhost:4000',
})

  配置ApolloClient,由于我们要自行配置ApolloClient,我们这里的ApolloClient不再是从apollo-boost引入,而是从apollo-client引入

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

  所有的httpLink都会接受到头部含有authorization: token的键值对,并将这个键值对发送给服务器端,因此所有向graphql发送请求都会携带token.

5、在服务器端增加验证功能

只有登录的用户才能创建新链接,并且在链接后绑定用户信息。

function post(parent, { url, description }, ctx, info) {
  const userId = getUserId(ctx)
  return ctx.db.mutation.createLink(
    { data: { url, description, postedBy: { connect: { id: userId } } } },
    info,
  )
}

  在解析函数post中,根据传递的token获取userId,然后绑定用户信息到postBY。

posted @ 2018-09-05 18:19  tutu_python  阅读(257)  评论(0)    收藏  举报