您的位置:首页 > Web前端 > React

erui _ eruie 003使用React,GraphQL和用户身份验证构建运行状况跟踪应用

2020-05-06 04:37 1091 查看

本文最初发表于Okta开发人员博客。 感谢您支持使SitePoint成为可能的合作伙伴。

简而言之,我们使身份管理比起以往,它变得更加轻松,安全和可扩展。 Okta是一项云服务,允许开发人员创建,编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。 我们的API使您能够:

  • 认证和授权您的用户Store data about您的用户Perform password-based和社交登录使用以下方法保护您的应用程序安全多因素认证以及更多! 看看我们的产品文件

你卖了吗注册一个永久免费的开发者帐户,当您完成操作后,请回来,以便我们进一步了解如何在React中构建安全的应用程序!

  1. 如果您饮食健康,那么您会有所收获。 否则为零。如果您运动,就会有所收获。如果您不喝酒,那么您会有所收获。

我建造了21点健康跟踪我的健康状况。 我认为重新创建该应用程序的一小部分,只是跟踪每日积分会很有趣。

类型ORM is a nifty ORM (object-relational mapper) framework that can run in most JavaScript platforms, including Node, a browser, Cordova, React Native, and Electron. It’s heavily influenced by Hibernate, Doctrine, and Entity Framework. Install 类型ORM globally to begin creating your API.

npm i -g typeorm@0.2.7
mkdir health-tracker
cd health-tracker
typeorm init --name graphql-api --database mysql
Edit
graphql-api/ormconfig.json
to customize the username, password, and database.

{
...
"username": "health",
"password": "pointstest",
"database": "healthpoints",
...
}

提示:要查看针对MySQL执行的查询,请将此文件中的“ logging”值更改为“ all”。 许多其他记录选项也可用。

Install MySQL if you don’t already have it installed. On Ubuntu, you can use

sudo apt-get install mysql-server
. On macOS, you can use Homebrew and
brew install mysql
. For Windows, you can use the MySQL Installer.

Once you’ve got MySQL installed and configured with a root password, login and create a

healthpoints
database.

mysql -u root -p
create database healthpoints;
use healthpoints;
grant all privileges on *.* to 'health'@'localhost' identified by 'points';

Navigate to your

graphql-api
project in a terminal window, install the project’s dependencies, then start it to ensure you can connect to MySQL.

cd graphql-api
npm i
npm start

Inserting a new user into the database...
Saved a new user with id: 1
Loading users from the database...
Loaded users:  [ User { id: 1, firstName: 'Timber', lastName: 'Saw', age: 25 } ]
Here you can setup and run express/koa/any other framework.

黄昏是一个集成TypeORM和GraphQL的Node框架。 要安装它,请使用ol’npm。
npm i vesper@0.1.9

Create

graphql-api/src/schema/model/Points.graphql
:

type Points {
id: Int
date: Date
exercise: Int
diet: Int
alcohol: Int
notes: String
user: User
}

Create

graphql-api/src/schema/model/User.graphql
:

type User {
id: String
firstName: String
lastName: String
points: [Points]
}

Next, create a

graphql-api/src/schema/controller/PointsController.graphql
with queries and mutations:

type Query {
points: [Points]
pointsGet(id: Int): Points
users: [User]
}

type Mutation {
pointsSave(id: Int, date: Date, exercise: Int, diet: Int, alcohol: Int, notes: String): Points
pointsDelete(id: Int): Boolean
}

Now that your data has GraphQL metadata create entities that will be managed by TypeORM. Change

src/entity/User.ts
to have the following code that allows points to be associated with a user.

import { Column, Entity, OneToMany, PrimaryColumn } from 'typeorm';
import { Points } from './Points';

@Entity()
export class User {

@PrimaryColumn()
id: string;

@Column()
firstName: string;

@Column()
lastName: string;

@OneToMany(() => Points, points => points.user)
points: Points[];
}

In the same

src/entity
directory, create a
Points.ts
class with the following code.

import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './User';

@Entity()
export class Points {

@PrimaryGeneratedColumn()
id: number;

@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP'})
date: Date;

@Column()
exercise: number;

@Column()
diet: number;

@Column()
alcohol: number;

@Column()
notes: string;

@ManyToOne(() => User, user => user.points, { cascade: ["insert"] })
user: User|null;
}

Note the

cascade: ["insert"]
option on the
@ManyToOne
annotation above. This option will automatically insert a user if it’s present on the entity. Create
src/controller/PointsController.ts
to handle converting the data from your GraphQL queries and mutations.

import { Controller, Mutation, Query } from 'vesper';
import { EntityManager } from 'typeorm';
import { Points } from '../entity/Points';

@Controller()
export class PointsController {

constructor(private entityManager: EntityManager) {
}

// serves "points: [Points]" requests
@Query()
points() {
return this.entityManager.find(Points);
}

// serves "pointsGet(id: Int): Points" requests
@Query()
pointsGet({id}) {
return this.entityManager.findOne(Points, id);
}

// serves "pointsSave(id: Int, date: Date, exercise: Int, diet: Int, alcohol: Int, notes: String): Points" requests
@Mutation()
pointsSave(args) {
const points = this.entityManager.create(Points, args);
return this.entityManager.save(Points, points);
}

// serves "pointsDelete(id: Int): Boolean" requests
@Mutation()
async pointsDelete({id}) {
await this.entityManager.remove(Points, {id: id});
return true;
}
}

Change

src/index.ts
to use Vesper’s
bootstrap()
to configure everything.

import { bootstrap } from 'vesper';
import { PointsController } from './controller/PointsController';
import { Points } from './entity/Points';
import { User } from './entity/User';

bootstrap({
port: 4000,
controllers: [
PointsController
],
entities: [
Points,
User
],
schemas: [
__dirname + '/schema/**/*.graphql'
],
cors: true
}).then(() => {
console.log('Your app is up and running on http://localhost:4000. ' +
'You can use playground in development mode on http://localhost:4000/playground');
}).catch(error => {
console.error(error.stack ? error.stack : error);
});

Start your API using

npm start
and navigate to http://localhost:4000/playground. In the left pane, enter the following mutation and press the play button. You might try typing the code below so you can experience the code completion that GraphQL provides you.

mutation {
pointsSave(exercise:1, diet:1, alcohol:1, notes:"Hello World") {
id
date
exercise
diet
alcohol
notes
}
}

You can use the following

points
query to verify that data is in your database.

query {
points {id date exercise diet notes}
}

You might notice that the date returned from

pointsSave
and the
points
query is in a format the might be difficult for a JavaScript client to understand. You can fix that, install graphql-iso-date.

npm i graphql-iso-date@3.5.0

Then, add an import in

src/index.ts
and configure custom resolvers for the various date types. This example only uses
Date
, but it’s helpful to know the other options.

import { GraphQLDate, GraphQLDateTime, GraphQLTime } from 'graphql-iso-date';

bootstrap({
...
// https://github.com/vesper-framework/vesper/issues/4
customResolvers: {
Date: GraphQLDate,
Time: GraphQLTime,
DateTime: GraphQLDateTime
},
...
});

Now running the

points
query will return a more client-friendly result.

{
"data": {
"points": [
{
"id": 1,
"date": "2018-06-04",
"exercise": 1,
"diet": 1,
"notes": "Hello World"
}
]
}
}

使用React最快的方法之一是使用创建React应用。 使用以下命令安装最新版本。

npm i -g create-react-app@1.1.4

cd health-tracker
create-react-app react-client

安装集成所需的依赖项阿波罗客户使用React以及Bootstrap和反应带。

npm i apollo-boost@0.1.7 react-apollo@2.1.4 graphql-tag@2.9.2 graphql@0.13.2

Open
react-client/src/App.js
and import
ApolloClient
from
apollo-boost
and add the endpoint to your GraphQL API.

import ApolloClient from 'apollo-boost';

const client = new ApolloClient({
uri: "http://localhost:4000/graphql"
});

That’s it! With only three lines of code, your app is ready to start fetching data. You can prove it by importing the

gql
function from
graphql-tag
. This will parse your query string and turn it into a query document.

import gql from 'graphql-tag';

class App extends Component {

componentDidMount() {
client.query({
query: gql`
{
points {
id date exercise diet alcohol notes
}
}
`
})
.then(result => console.log(result));
}
...
}

Make sure to open your browser’s developer tools so you can see the data after making this change. You could modify the

console.log()
to use
this.setState({points: results.data.points})
, but then you’d have to initialize the default state in the constructor. But there’s an easier way: you can use
ApolloProvider
and
Query
components from
react-apollo
!

Below is a modified version of

react-client/src/App.js
that uses these components.

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import ApolloClient from 'apollo-boost';
import gql from 'graphql-tag';
import { ApolloProvider, Query } from 'react-apollo';
const client = new ApolloClient({
uri: "http://localhost:4000/graphql"
});

class App extends Component {

render() {
return (
<ApolloProvider client={client}>
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
To get started, edit <code>src/App.js</code> and save to reload.
</p>
<Query query={gql`
{
points {id date exercise diet alcohol notes}
}
`}>
{({loading, error, data}) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return data.points.map(p => {
return <div key={p.id}>
<p>Date: {p.date}</p>
<p>Points: {p.exercise + p.diet + p.alcohol}</p>
<p>Notes: {p.notes}</p>
</div>
})
}}
</Query>
</div>
</ApolloProvider>
);
}
}

export default App;

Log in to your Okta Developer account (or sign up if you don’t have an account) and navigate to Applications > Add Application. Click Single-Page App, click Next, and give the app a name you’ll remember. Change all instances of

localhost:8080
to
localhost:3000
and click Done. Your settings should be similar to the screenshot below.

npm i @okta/okta-react@1.0.2 react-router-dom@4.2.2
Okta’s React SDK depends on react-router, hence the reason for installing
react-router-dom
. Configuring routing in
client/src/App.tsx
is a common practice, so replace its code with the JavaScript below that sets up authentication with Okta.

import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import { ImplicitCallback, SecureRoute, Security } from '@okta/okta-react';
import Home from './Home';
import Login from './Login';
import Points from './Points';

function onAuthRequired({history}) {
history.push('/login');
}

class App extends Component {
render() {
return (
<Router>
<Security issuer='https://{yourOktaDomain}/oauth2/default'
client_id='{clientId}'
redirect_uri={window.location.origin + '/implicit/callback'}
onAuthRequired={onAuthRequired}>
<Route path='/' exact={true} component={Home}/>
<SecureRoute path='/points' component={Points}/>
<Route path='/login' render={() => <Login baseUrl='https://{yourOktaDomain}'/>}/>
<Route path='/implicit/callback' component={ImplicitCallback}/>
</Security>
</Router>
);
}
}

export default App;

Make sure to replace

{yourOktaDomain}
and
{clientId}
in the code above. You can find both values in the Okta Developer Console.

The code in

App.js
references two components that don’t exist yet:
Home
,
Login
, and
Points
. Create
src/Home.js
with the following code. This component renders the default route, provides a Login button, and links to your points and logout after you’ve logged in.

import React, { Component } from 'react';
import { withAuth } from '@okta/okta-react';
import { Button, Container } from 'reactstrap';
import AppNavbar from './AppNavbar';
import { Link } from 'react-router-dom';

export default withAuth(class Home extends Component {
constructor(props) {
super(props);
this.state = {authenticated: null, userinfo: null, isOpen: false};
this.checkAuthentication = this.checkAuthentication.bind(this);
this.checkAuthentication();
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
}

async checkAuthentication() {
const authenticated = await this.props.auth.isAuthenticated();
if (authenticated !== this.state.authenticated) {
if (authenticated && !this.state.userinfo) {
const userinfo = await this.props.auth.getUser();
this.setState({authenticated, userinfo});
} else {
this.setState({authenticated});
}
}
}

async componentDidMount() {
this.checkAuthentication();
}

async componentDidUpdate() {
this.checkAuthentication();
}

async login() {
this.props.auth.login('/');
}

async logout() {
this.props.auth.logout('/');
this.setState({authenticated: null, userinfo: null});
}

render() {
if (this.state.authenticated === null) return null;
const button = this.state.authenticated ?
<div>
<Button color="link"><Link to="/points">Manage Points</Link></Button><br/>
<Button color="link" onClick={this.logout}>Logout</Button>
</div>:
<Button color="primary" onClick={this.login}>Login</Button>;

const message = this.state.userinfo ?
<p>Hello, {this.state.userinfo.given_name}!</p> :
<p>Please log in to manage your points.</p>;

return (
<div>
<AppNavbar/>
<Container fluid>
{message}
{button}
</Container>
</div>
);
}
});

This component uses

<Container/>
and
<Button/>
from reactstrap. Install reactstrap, so everything compiles. It depends on Bootstrap, so include it too.

npm i reactstrap@6.1.0 bootstrap@4.1.1

Add Bootstrap’s CSS file as an import in

src/index.js
.

import 'bootstrap/dist/css/bootstrap.min.css';

You might notice there’s a

<AppNavbar/>
in the
Home
component’s
render()
method. Create
src/AppNavbar.js
so you can use a common header between components.

import React, { Component } from 'react';
import { Collapse, Nav, Navbar, NavbarBrand, NavbarToggler, NavItem, NavLink } from 'reactstrap';
import { Link } from 'react-router-dom';

export default class AppNavbar extends Component {
constructor(props) {
super(props);
this.state = {isOpen: false};
this.toggle = this.toggle.bind(this);
}

toggle() {
this.setState({
isOpen: !this.state.isOpen
});
}

render() {
return <Navbar color="success" dark expand="md">
<NavbarBrand tag={Link} to="/">Home</NavbarBrand>
<NavbarToggler onClick={this.toggle}/>
<Collapse isOpen={this.state.isOpen} navbar>
<Nav className="ml-auto" navbar>
<NavItem>
<NavLink
href="https://twitter.com/oktadev">@oktadev</NavLink>
</NavItem>
<NavItem>
<NavLink href="https://github.com/oktadeveloper/okta-react-graphql-example/">GitHub</NavLink>
</NavItem>
</Nav>
</Collapse>
</Navbar>;
}
}

在这个例子中,我要嵌入Okta的登录小部件。 另一个选项是重定向到Okta并使用托管的登录页面。 使用npm安装登录小部件。

npm i @okta/okta-signin-widget@2.9.0
Create
src/Login.js
and add the following code to it.

import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import OktaSignInWidget from './OktaSignInWidget';
import { withAuth } from '@okta/okta-react';

export default withAuth(class Login extends Component {
constructor(props) {
super(props);
this.onSuccess = this.onSuccess.bind(this);
this.onError = this.onError.bind(this);
this.state = {
authenticated: null
};
this.checkAuthentication();
}

async checkAuthentication() {
const authenticated = await this.props.auth.isAuthenticated();
if (authenticated !== this.state.authenticated) {
this.setState({authenticated});
}
}

componentDidUpdate() {
this.checkAuthentication();
}

onSuccess(res) {
return this.props.auth.redirect({
sessionToken: res.session.token
});
}

onError(err) {
console.log('error logging in', err);
}

render() {
if (this.state.authenticated === null) return null;
return this.state.authenticated ?
<Redirect to={{pathname: '/'}}/> :
<OktaSignInWidget
baseUrl={this.props.baseUrl}
onSuccess={this.onSuccess}
onError={this.onError}/>;
}
});

The

Login
component has a reference to
OktaSignInWidget
. Create
src/OktaSignInWidget.js
:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import OktaSignIn from '@okta/okta-signin-widget';
import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
import '@okta/okta-signin-widget/dist/css/okta-theme.css';
import './App.css';

export default class OktaSignInWidget extends Component {
componentDidMount() {
const el = ReactDOM.findDOMNode(this);
this.widget = new OktaSignIn({
baseUrl: this.props.baseUrl
});
this.widget.renderEl({el}, this.props.onSuccess, this.props.onError);
}

componentWillUnmount() {
this.widget.remove();
}

render() {
return <div/>;
}
};

Create

src/Points.js
to render the list of points from your API.

import React, { Component } from 'react';
import { ApolloClient } from 'apollo-client';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
import gql from 'graphql-tag';
import { withAuth } from '@okta/okta-react';
import AppNavbar from './AppNavbar';
import { Alert, Button, Container, Table } from 'reactstrap';
import PointsModal from './PointsModal';

export const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql'
});

export default withAuth(class Points extends Component {
client;

constructor(props) {
super(props);
this.state = {points: [], error: null};

this.refresh = this.refresh.bind(this);
this.remove = this.remove.bind(this);
}

refresh(item) {
let existing = this.state.points.filter(p => p.id === item.id);
let points = [...this.state.points];
if (existing.length === 0) {
points.push(item);
this.setState({points});
} else {
this.state.points.forEach((p, idx) => {
if (p.id === item.id) {
points[idx] = item;
this.setState({points});
}
})
}
}

remove(item, index) {
const deletePoints = gql`mutation pointsDelete($id: Int) { pointsDelete(id: $id) }`;

this.client.mutate({
mutation: deletePoints,
variables: {id: item.id}
}).then(result => {
if (result.data.pointsDelete) {
let updatedPoints = [...this.state.points].filter(i => i.id !== item.id);
this.setState({points: updatedPoints});
}
});
}

componentDidMount() {
const authLink = setContext(async (_, {headers}) => {
const token = await this.props.auth.getAccessToken();
const user = await this.props.auth.getUser();

// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
'x-forwarded-user': user ? JSON.stringify(user) : ''
}
}
});

this.client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
connectToDevTools: true
});

this.client.query({
query: gql`
{
points {
id,
user {
id,
lastName
}
date,
alcohol,
exercise,
diet,
notes
}
}`
}).then(result => {
this.setState({points: result.data.points});
}).catch(error => {
this.setState({error: <Alert color="danger">Failure to communicate with API.</Alert>});
});
}

render() {
const {points, error} = this.state;
const pointsList = points.map(p => {
const total = p.exercise + p.diet + p.alcohol;
return <tr key={p.id}>
<td style={{whiteSpace: 'nowrap'}}><PointsModal item={p} callback={this.refresh}/></td>
<td className={total <= 1 ? 'text-danger' : 'text-success'}>{total}</td>
<td>{p.notes}</td>
<td><Button size="sm" color="danger" onClick={() => this.remove(p)}>Delete</Button></td>
</tr>
});

return (
<div>
<AppNavbar/>
<Container fluid>
{error}
<h3>Your Points</h3>
<Table>
<thead>
<tr>
<th width="10%">Date</th>
<th width="10%">Points</th>
<th>Notes</th>
<th width="10%">Actions</th>
</tr>
</thead>
<tbody>
{pointsList}
</tbody>
</Table>
<PointsModal callback={this.refresh}/>
</Container>
</div>
);
}
})

This code starts with

refresh()
and
remove()
methods, which I’ll get to in a moment. The important part happens in
componentDidMount()
, where the access token is added in an
Authorization
header, and the user’s information is stuffed in an
x-forwarded-user
header. An
ApolloClient
is created with this information, a cache is added, and the
connectToDevTools
flag is turned on. This can be useful for debugging with Apollo Client Developer Tools.

componentDidMount() {
const authLink = setContext(async (_, {headers}) => {
const token = await this.props.auth.getAccessToken();

// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
'x-forwarded-user': user ? JSON.stringify(user) : ''
}
}
});

this.client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
connectToDevTools: true
});

// this.client.query(...);
}

使用Apollo客户端进行身份验证需要一些新的依赖项。 立即安装。

npm apollo-link-context@1.0.8 apollo-link-http@1.5.4
In the JSX of the page, there is a delete button that calls the
remove()
method in
Points
. There’s also a
<PointsModal/>
component. This is referenced for each item, as well as at the bottom. You’ll notice both of these reference the
refresh()
method, which updates the list.

<PointsModal item={p} callback={this.refresh}/>
<PointsModal callback={this.refresh}/>

This component renders a link to edit a component, or an Add button when no

item
is set.

Create

src/PointsModal.js
and add the following code to it.

import React, { Component } from 'react';
import { Button, Form, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { withAuth } from '@okta/okta-react';
import { httpLink } from './Points';
import { ApolloClient } from 'apollo-client';
import { setContext } from 'apollo-link-context';
import { InMemoryCache } from 'apollo-cache-inmemory';
import gql from 'graphql-tag';
import { Link } from 'react-router-dom';

export default withAuth(class PointsModal extends Component {
client;
emptyItem = {
date: (new Date()).toISOString().split('T')[0],
exercise: 1,
diet: 1,
alcohol: 1,
notes: ''
};

constructor(props) {
super(props);
this.state = {
modal: false,
item: this.emptyItem
};

this.toggle = this.toggle.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

componentDidMount() {
if (this.props.item) {
this.setState({item: this.props.item})
}

const authLink = setContext(async (_, {headers}) => {
const token = await this.props.auth.getAccessToken();
const user = await this.props.auth.getUser();

// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
'x-forwarded-user': JSON.stringify(user)
}
}
});

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

toggle() {
if (this.state.modal && !this.state.item.id) {
this.setState({item: this.emptyItem});
}
this.setState({modal: !this.state.modal});
}

render() {
const {item} = this.state;
const opener = item.id ? <Link onClick={this.toggle} to="#">{this.props.item.date}</Link> :
<Button color="primary" onClick={this.toggle}>Add Points</Button>;

return (
<div>
{opener}
<Modal isOpen={this.state.modal} toggle={this.toggle}>
<ModalHeader toggle={this.toggle}>{(item.id ? 'Edit' : 'Add')} Points</ModalHeader>
<ModalBody>
<Form onSubmit={this.handleSubmit}>
<FormGroup>
<Label for="date">Date</Label>
<Input type="date" name="date" id="date" value={item.date}
onChange={this.handleChange}/>
</FormGroup>
<FormGroup check>
<Label check>
<Input type="checkbox" name="exercise" id="exercise" checked={item.exercise}
onChange={this.handleChange}/>{' '}
Did you exercise?
</Label>
</FormGroup>
<FormGroup check>
<Label check>
<Input type="checkbox" name="diet" id="diet" checked={item.diet}
onChange={this.handleChange}/>{' '}
Did you eat well?
</Label>
</FormGroup>
<FormGroup check>
<Label check>
<Input type="checkbox" name="alcohol" id="alcohol" checked={item.alcohol}
onChange={this.handleChange}/>{' '}
Did you drink responsibly?
</Label>
</FormGroup>
<FormGroup>
<Label for="notes">Notes</Label>
<Input type="textarea" name="notes" id="notes" value={item.notes}
onChange={this.handleChange}/>
</FormGroup>
</Form>
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.handleSubmit}>Save</Button>{' '}
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
)
};

handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? (target.checked ? 1 : 0) : target.value;
const name = target.name;
let item = {...this.state.item};
item[name] = value;
this.setState({item});
}

handleSubmit(event) {
event.preventDefault();
const {item} = this.state;

const updatePoints = gql`
mutation pointsSave($id: Int, $date: Date, $exercise: Int, $diet: Int, $alcohol: Int, $notes: String) {
pointsSave(id: $id, date: $date, exercise: $exercise, diet: $diet, alcohol: $alcohol, notes: $notes) {
id date
}
}`;

this.client.mutate({
mutation: updatePoints,
variables: {
id: item.id,
date: item.date,
exercise: item.exercise,
diet: item.diet,
alcohol: item.alcohol,
notes: item.notes
}
}).then(result => {
let newItem = {...item};
newItem.id = result.data.pointsSave.id;
this.props.callback(newItem);
this.toggle();
});
}
});

Make sure your GraphQL backend is started, then start the React frontend with

npm start
. The text squishes up against the top navbar, so add some padding by adding a rule in
src/index.css
.

.container-fluid {
padding-top: 10px;
}

You should see the

Home
component and a button to log in.

Navigate to your

graphql-api
project in a terminal window and install Okta’s JWT Verifier.

npm i @okta/jwt-verifier@0.0.12

Create

graphql-api/src/CurrentUser.ts
to hold the current user’s information.

export class CurrentUser {
constructor(public id: string, public firstName: string, public lastName: string) {}
}

Import

OktaJwtVerifier
and
CurrentUser
in
graphql-api/src/index.ts
and configure the JWT verifier to use your OIDC app’s settings.

import * as OktaJwtVerifier from '@okta/jwt-verifier';
import { CurrentUser } from './CurrentUser';

const oktaJwtVerifier = new OktaJwtVerifier({
clientId: '{clientId}',
issuer: 'https://{yourOktaDomain}/oauth2/default'
});

In the bootstrap configuration, define

setupContainer
to require an
authorization
header and set the current user from the
x-forwarded-user
header.

bootstrap({
...
cors: true,
setupContainer: async (container, action) => {
const request = action.request;
// require every request to have an authorization header
if (!request.headers.authorization) {
throw Error('Authorization header is required!');
}
let parts = request.headers.authorization.trim().split(' ');
let accessToken = parts.pop();
await oktaJwtVerifier.verifyAccessToken(accessToken)
.then(async jwt => {
const user = JSON.parse(request.headers['x-forwarded-user'].toString());
const currentUser = new CurrentUser(jwt.claims.uid, user.given_name, user.family_name);
container.set(CurrentUser, currentUser);
})
.catch(error => {
throw Error('JWT Validation failed!');
})
}
...
});

Modify

graphql-api/src/controller/PointsController.ts
to inject the
CurrentUser
as a dependency. While you’re in there, adjust the
points()
method to filter by user ID and modify
pointsSave()
to set the user when saving.

import { Controller, Mutation, Query } from 'vesper';
import { EntityManager } from 'typeorm';
import { Points } from '../entity/Points';
import { User } from '../entity/User';
import { CurrentUser } from '../CurrentUser';

@Controller()
export class PointsController {

constructor(private entityManager: EntityManager, private currentUser: CurrentUser) {
}

// serves "points: [Points]" requests
@Query()
points() {
return this.entityManager.getRepository(Points).createQueryBuilder("points")
.innerJoin("points.user", "user", "user.id = :id", { id: this.currentUser.id })
.getMany();
}

// serves "pointsGet(id: Int): Points" requests
@Query()
pointsGet({id}) {
return this.entityManager.findOne(Points, id);
}

// serves "pointsSave(id: Int, date: Date, exercise: Int, diet: Int, alcohol: Int, notes: String): Points" requests
@Mutation()
pointsSave(args) {
// add current user to points saved
if (this.currentUser) {
const user = new User();
user.id = this.currentUser.id;
user.firstName = this.currentUser.firstName;
user.lastName = this.currentUser.lastName;
args.user = user;
}

const points = this.entityManager.create(Points, args);
return this.entityManager.save(Points, points);
}

// serves "pointsDelete(id: Int): Boolean" requests
@Mutation()
async pointsDelete({id}) {
await this.entityManager.remove(Points, {id: id});
return true;
}
}

您可以找到本文的源代码这里。

  • 使用Node,React和Okta建立用户注册在15分钟内使用用户身份验证构建React应用程序构建一个React Native应用并使用OAuth 2.0进行身份验证将Okta身份验证添加到您的React应用使用Vue.js和Node构建基本的CRUD应用

希望您在使用React和GraphQL构建应用程序方面有出色的经验。 如果您有任何疑问,请在推特上打我或我的整个踢屁股队@oktadev。 我们的DM是开放的! :)

from: https://www.sitepoint.com//build-a-health-tracking-app-with-react-graphql-and-user-authentication/

dangzhuang7815 原创文章 0获赞 0访问量 316 关注 私信
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐