erui _ eruie 003使用React,GraphQL和用户身份验证构建运行状况跟踪应用
本文最初发表于Okta开发人员博客。 感谢您支持使SitePoint成为可能的合作伙伴。
简而言之,我们使身份管理比起以往,它变得更加轻松,安全和可扩展。 Okta是一项云服务,允许开发人员创建,编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。 我们的API使您能够:
- 认证和授权您的用户Store data about您的用户Perform password-based和社交登录使用以下方法保护您的应用程序安全多因素认证以及更多! 看看我们的产品文件
你卖了吗注册一个永久免费的开发者帐户,当您完成操作后,请回来,以便我们进一步了解如何在React中构建安全的应用程序!
- 如果您饮食健康,那么您会有所收获。 否则为零。如果您运动,就会有所收获。如果您不喝酒,那么您会有所收获。
我建造了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 mysqlEdit
graphql-api/ormconfig.jsonto 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
healthpointsdatabase.
mysql -u root -p create database healthpoints; use healthpoints; grant all privileges on *.* to 'health'@'localhost' identified by 'points';
Navigate to your
graphql-apiproject 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.graphqlwith 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.tsto 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/entitydirectory, create a
Points.tsclass 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
@ManyToOneannotation above. This option will automatically insert a user if it’s present on the entity. Create
src/controller/PointsController.tsto 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.tsto 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 startand 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
pointsquery to verify that data is in your database.
query { points {id date exercise diet notes} }
You might notice that the date returned from
pointsSaveand the
pointsquery 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.tsand 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
pointsquery 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.2Open
react-client/src/App.jsand import
ApolloClientfrom
apollo-boostand 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
gqlfunction 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
ApolloProviderand
Querycomponents from
react-apollo!
Below is a modified version of
react-client/src/App.jsthat 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:8080to
localhost:3000and click Done. Your settings should be similar to the screenshot below.
npm i @okta/okta-react@1.0.2 react-router-dom@4.2.2Okta’s React SDK depends on react-router, hence the reason for installing
react-router-dom. Configuring routing in
client/src/App.tsxis 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.jsreferences two components that don’t exist yet:
Home,
Login, and
Points. Create
src/Home.jswith 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
Homecomponent’s
render()method. Create
src/AppNavbar.jsso 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.0Create
src/Login.jsand 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
Logincomponent 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.jsto 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
Authorizationheader, and the user’s information is stuffed in an
x-forwarded-userheader. An
ApolloClientis created with this information, a cache is added, and the
connectToDevToolsflag 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.4In 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
itemis set.
Create
src/PointsModal.jsand 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
Homecomponent and a button to log in.
Navigate to your
graphql-apiproject in a terminal window and install Okta’s JWT Verifier.
npm i @okta/jwt-verifier@0.0.12
Create
graphql-api/src/CurrentUser.tsto hold the current user’s information.
export class CurrentUser { constructor(public id: string, public firstName: string, public lastName: string) {} }
Import
OktaJwtVerifierand
CurrentUserin
graphql-api/src/index.tsand 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
setupContainerto require an
authorizationheader and set the current user from the
x-forwarded-userheader.
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.tsto inject the
CurrentUseras 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 关注 私信- 使用React,GraphQL和用户身份验证构建运行状况跟踪应用
- 使用OAuth 2.0构建React Native应用并进行身份验证
- ASP.NET MVC5+MySql使用ASP.NET 身份验证实现用户和角色功能 1 概述 目标:使用MySql数据库,建立一个使用ASP.NET 身份验证的应用,并实现角色功能,身份
- erui _ eruie 002使用Express,React和GraphQL构建一个简单的Web应用程序
- erui _ eruie 003如何使用Sencha Grid构建React 16 Web应用程序
- erui _ eruie 002使用Passport.js和OpenID Connect构建安全的节点身份验证
- 使用TKPROF工具进行跟踪ORACLE中程序运行状况
- 使用 Apache Shiro 为 web 应用程序进行用户身份验证
- erui _ eruie 002使用Angular和Node构建基本的CRUD应用
- 分享Web应用运行的细节问题:预编译提高网站性能、跟踪用户习惯和解决线程同步
- mongodb 用户身份验证,权限控制db.createUser()方法使用
- 使用 Podman 以非 root 用户身份运行 Linux 容器
- 使用mpm-itk模块让 apache 以特定的用户身份运行虚拟主机 [CentOS 5.5]
- SQLSERVER误删Windows登录用户验证方式使用Windows身份验证的解决方法
- 用户画像的构建与使用2应用
- 挨踢部落直播课堂第七期:如何使用React构建同构(isomorphic)应用
- 构建具有用户身份认证的 Ionic 应用
- PHP中使用crypt()实现用户身份验证
- 在ubuntu 12.04中使用openvswitch+kvm 构建虚拟环境(真正的主机系统,可以运行多种测试应用)
- 百分点苏海波-用户画像的构建与使用2应用