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。
浙公网安备 33010602011771号