[译] 使用 Node 和 OAuth 2.0 构建一个简单的 REST API

JavaScript 在 web 是随处可见 —— 几乎每一个 web 页面都会或多或少的包含一些 JavaScript,即便没有 JavaScript,你的浏览器也可能存在某种扩展类型向页面中注入一些 JavaScript 代码。直到现在,这些事情都不可避免。html

JavaScript 也能够用于浏览器的上下文以外的任何事情,从托管 web 服务器来控制 RC 汽车或运行成熟的操做系统。有时你想要几个一组不管是在本地网络仍是在互联网上均可以相互交流的服务器。前端

今天,我会向你演示如何使用 Node.js 建立一个 REST API,并使用 OAuth 2.0 保证它的安全性,以此来阻止没必要要的请求。REST API 在 web 上比比皆是,但若是没有合适的工具,就须要大量的样板代码。我会向你演示如何使用能够轻松实现客户端认证流的使人惊讶的一些工具,它能够在没有用户上下文的状况下将两台机器安全地链接。node

构建你的 Node 服务器

使用 Express JavaScript 库 在 Node 中设置 web 服务器很是简单。建立一个包含服务器的新文件夹。android

$ mkdir rest-api
复制代码

Node 使用 package.json 来管理依赖并定义你的项目。咱们使用 npm init 来新建该文件。该命令会在帮助你初始化项目时询问你一些问题。如今你可使用标准 JS 来强制执行编码标准,并将其用做测试。ios

$ cd rest-api

$ npm init
这个实用工具将引导你建立 package.json 文件。
它只涵盖最多见的项目,并试图猜想合理的默认值。

请参阅 `npm help json` 来获取关于这些字段的确切文档以及它们所作的事情。

使用 `npm install <pkg>` 命令来安装一个 npm 依赖,并将其保存在 package.json 文件中。

Press ^C at any time to quit.
package name: (rest-api)
version: (1.0.0)
description: A parts catalog
entry point: (index.js)
test command: standard
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/Braden/code/rest-api/package.json:

{
  "name": "rest-api",
  "version": "1.0.0",
  "description": "A parts catalog",
  "main": "index.js",
  "scripts": {
    "test": "standard"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)
复制代码

默认的入口端点是 index.js,所以,你应当建立一个 index.js 文件。下面的代码将为你提供一个出了默认监听 3000 端口之外什么也不作的很是基本的服务器。git

index.jsgithub

const express = require('express')
const bodyParser = require('body-parser')
const { promisify } = require('util')

const app = express()
app.use(bodyParser.json())

const startServer = async () => {
  const port = process.env.SERVER_PORT || 3000
  await promisify(app.listen).bind(app)(port)
  console.log(`Listening on port ${port}`)
}

startServer()
复制代码

utilpromisify 函数容许你接受一个指望回调的函数,而后返回一个 promise,这是处理异步代码的新标准。这还容许咱们使用相对较新的 async/await 语法,并使咱们的代码看起来漂亮得多。web

为了让它运行,你须要下载你在文件头部导入的 require 依赖。使用 npm install 来安装他们。这会将一些元数据自动保存到你的 package.json 文件中,并将它们下载到本地的 node_modules 文件中。sql

注意:你永远都不该该向源代码提交 node_modules,由于对于源代码的管理,每每会很快就变得臃肿,而 package-lock.json 文件将跟踪你使用的确切版本,若是你将其安装在另外一台计算机上,它们将获得相同的代码。数据库

$ npm install express@4.16.3 util@0.11.0
复制代码

对于一些快速 linting,请安装 standard 做为 dev 依赖,而后运行它以确保你的代码达到标准。

$ npm install --save-dev standard@11.0.1
$ npm test

> rest-api@1.0.0 test /Users/bmk/code/okta/apps/rest-api
> standard
复制代码

若是一切顺利,在 > standard 线后,你不该该看到任何输出。若是有错误,可能以下所示:

$ npm test

> rest-api@1.0.0 test /Users/bmk/code/okta/apps/rest-api
> standard

standard: Use JavaScript Standard Style (https://standardjs.com)
standard: Run `standard --fix` to automatically fix some problems.
  /Users/Braden/code/rest-api/index.js:3:7: Expected consistent spacing
  /Users/Braden/code/rest-api/index.js:3:18: Unexpected trailing comma.
  /Users/Braden/code/rest-api/index.js:3:18: A space is required after ','.
  /Users/Braden/code/rest-api/index.js:3:38: Extra semicolon.
npm ERR! Test failed.  See above for more details.
复制代码

如今,你的代码已经准备好了,也下载了所需的依赖,你能够用 node . 运行服务器了。(. 表示查看前目录,而后检查你的 package.json 文件,以肯定该目录中使用的主文件是 index.js):

$ node .

Listening on port 3000
复制代码

为了测试它的工做状态,你可使用 curl 命令。没有终结点,因此 Express 将返回一个错误:

$ curl localhost:3000 -i
HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 139
Date: Thu, 16 Aug 2018 01:34:53 GMT
Connection: keep-alive

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
复制代码

即便它报错,那也是很是好的状况。你尚未设置任何端点,所以 Express 惟一要返回的是 404 错误。若是你的服务器根本没有运行,你将获得以下错误:

$ curl localhost:3000 -i
curl: (7) Failed to connect to localhost port 3000: Connection refused
复制代码

用 Express、Sequelize 和 Epilogue 构建你的 REST API

你如今有了一台正在运行的 Express 服务器,你能够添加一个 REST API。这实际上比你想象中的简单的多。我看过的最简单的方法是使用 Sequelize 来定义数据库字段,Epilogue 建立带有接近零样板的 REST API 端点。

你须要将这些依赖加入到你的项目中。Sequelize 也须要知道如何与数据库进行通讯。如今,使用 SQLite 是由于它能帮助咱们快速地启动和运行。

npm install sequelize@4.38.0 epilogue@0.7.1 sqlite3@4.0.2
复制代码

新建一个包含如下代码的文件 database.js。我会在下面详细解释每一部分。

database.js

const Sequelize = require('sequelize')
const epilogue = require('epilogue')

const database = new Sequelize({
  dialect: 'sqlite',
  storage: './test.sqlite',
  operatorsAliases: false
})

const Part = database.define('parts', {
  partNumber: Sequelize.STRING,
  modelNumber: Sequelize.STRING,
  name: Sequelize.STRING,
  description: Sequelize.TEXT
})

const initializeDatabase = async (app) => {
  epilogue.initialize({ app, sequelize: database })

  epilogue.resource({
    model: Part,
    endpoints: ['/parts', '/parts/:id']
  })

  await database.sync()
}

module.exports = initializeDatabase
复制代码

你如今只须要将那些文件导入主应用程序并运行初始化函数便可。在你的 index.js 文件中添加如下内容。

index.js

@@ -2,10 +2,14 @@ const express = require('express')
 const bodyParser = require('body-parser')
 const { promisify } = require('util')

+const initializeDatabase = require('./database')
+
 const app = express()
 app.use(bodyParser.json())

 const startServer = async () => {
+  await initializeDatabase(app)
+
   const port = process.env.SERVER_PORT || 3000
   await promisify(app.listen).bind(app)(port)
   console.log(`Listening on port ${port}`)
复制代码

你如今能够测试语法错误,若是一切 看上去都正常了,就能够启动应用程序了:

$ npm test && node .

> rest-api@1.0.0 test /Users/bmk/code/okta/apps/rest-api
> standard

Executing (default): CREATE TABLE IF NOT EXISTS `parts` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `partNumber` VARCHAR(255), `modelNu
mber` VARCHAR(255), `name` VARCHAR(255), `description` TEXT, `createdAt` DATETIME NOT NULL, `updatedAt` DATETIME NOT NULL);
Executing (default): PRAGMA INDEX_LIST(`parts`)
Listening on port 3000
复制代码

在另外一个终端,你能够测试它是否实际上已经在工做了(我使用 json CLI 来格式化 JSON 响应,使用 npm install --global json 进行全局安装):

$ curl localhost:3000/parts
[]

$ curl localhost:3000/parts -X POST -d '{ "partNumber": "abc-123", "modelNumber": "xyz-789", "name": "Alphabet Soup", "description": "Soup with letters and numbers in it" }' -H 'content-type: application/json' -s0 | json
{
  "id": 1,
  "partNumber": "abc-123",
  "modelNumber": "xyz-789",
  "name": "Alphabet Soup",
  "description": "Soup with letters and numbers in it",
  "updatedAt": "2018-08-16T02:22:09.446Z",
  "createdAt": "2018-08-16T02:22:09.446Z"
}

$ curl localhost:3000/parts -s0 | json
[
  {
    "id": 1,
    "partNumber": "abc-123",
    "modelNumber": "xyz-789",
    "name": "Alphabet Soup",
    "description": "Soup with letters and numbers in it",
    "createdAt": "2018-08-16T02:22:09.446Z",
    "updatedAt": "2018-08-16T02:22:09.446Z"
  }
]
复制代码

这发生了什么?

若是你以前一直是按照咱们的步骤来的,那么是能够跳过这部分的,由于这部分是我以前承诺过要给出的解释。

Sequelize 函数建立了一个数据库。这是配置详细信息的地方,例如要使用 SQL 语句。如今,使用 SQLite 来快速启动和运行。

const database = new Sequelize({
  dialect: 'sqlite',
  storage: './test.sqlite',
  operatorsAliases: false
})
复制代码

一旦建立了数据库,你就能够为每一个表使用 database.define 来定义它的表。用一些有用的字段建立叫作 parts 的表来进跟踪 parts。默认状况下,Sequelize 还会在建立和更新时自动建立和更新 idcreatedAtupdatedAt 字段。

const Part = database.define('parts', {
  partNumber: Sequelize.STRING,
  modelNumber: Sequelize.STRING,
  name: Sequelize.STRING,
  description: Sequelize.TEXT
})
复制代码

结语为了添加端点会请求获取你的 Express app 访问权限。但 app 被定义在另外一个文件中。处理这个问题的一个方法就是导出一个函数,该函数接受应用程序并对其进行一些操做。当咱们在另外一个文件中导入这个脚本时,你能够像运行 initializeDatabase(app) 同样运行它。

结语须要同时使用 appdatabase 来初始化。软化定义你须要使用的 REST 端点。resource 函数会包括 GETPOSTPUTDELETE 动词的端点,这些动词大多数是自动化的。

想真正建立数据库,你须要运行返回一个 promise 的 database.sync()。在你启动服务器以前,你须要等待它执行结束。

module.exports 意思是 initializeDatabase 函数能够从另外一个函数中导入。

const initializeDatabase = async (app) => {
  epilogue.initialize({ app, sequelize: database })

  epilogue.resource({
    model: Part,
    endpoints: ['/parts', '/parts/:id']
  })

  await database.sync()
}

module.exports = initializeDatabase
复制代码

用 OAuth 2.0 保护你的 Node + Express REST API

如今你已经启动并运行了 REST API,想象你但愿一个特定的应用程序从远程位置使用这个 API。若是你把它按照原样存放在互联网上,那么任何人均可以随意添加、修改或删除部位。

为了不这个状况,你可使用 OAuth 2.0 客户端凭证。这是一种不须要上下文就可让两个服务器相互通讯的方式。这两个服务器必须事先赞成使用第三方受权服务器。假设有两个服务器,A 和 B,以及一个接权服务器。服务器 A 托管 REST API,服务器 B 但愿访问该 API。

  • 服务器 B 向受权服务器发送一个私钥来证实本身的身份,并申请一个临时令牌。
  • 服务器 B 会向往常同样使用 REST API,但会将令牌与请求一块儿发送。
  • 服务器 A 向受权服务器请求一些元数据,这些元数据可用于验证令牌。
  • 服务器 A 验证服务器 B 的请求。
    • 若是它是有效的,一个成功的响应将被发送而且服务器 B 正常运行。
    • 若是令牌无效,则将发送错误消息,而且不会泄露敏感信息。

建立受权服务器

这就是 OKta 发挥做用的地方。OKta 能够扮演容许你保护数据的服务器的角色。你可能会问本身“为何是 OKta?”好的,对于构建 REST 应用程序来讲,它很是的酷,可是构建一个安全的应用程序会更酷。为了实现这个目标,你须要添加身份验证,以便用户在查看/修改组以前必需要登陆才能够。在 Okta 中,咱们的目标是确保身份管理比你过去使用的要更容易、更安全、更可扩展。Okta 是一种云服务,它容许开发者建立、编辑和安全存储用户帐户以及用户帐户数据,并将它们与一个或多个应用程序链接。咱们的 API 容许你:

若是你尚未帐户,能够注册一个永久免费的开发者帐号,让咱们开始吧!

建立帐户后,登陆到开发者控制台,导航到 API,而后导航到 Authorization Servers 选项卡。单击 default 服务器的连接。

从这个 Settings 选项卡中,复制 Issuer 字段。你须要把它保存在你的 Node 应用程序能够阅读的地方。在你的项目中,建立一个名为 .env 的文件,以下所示:

.env

ISSUER=https://{yourOktaDomain}/oauth2/default
复制代码

ISSUER 的值应该是设置页面的 Issuer URI 字段的值。

高亮 issuer URL。

注意:通常规则是,你不该该将 .env 文件存储在源代码管理中。这容许多个项目同时使用相同的源代码,而不是须要单独的分支。它确保你的安全信息不会被公开(特备是若是你将代码做为开源发布时)。

接下来,导航到 Scopes 菜单。单击 Add Scope 按钮,而后为 REST API 建立一个做用域。你须要起一个名称(例如,parts_manager),若是你愿意,还能够给它一个描述。

添加范围的截图

你还应该将做用域添加到你的 .env 文件中,以便你的代码能够访问到它。

.env

ISSUER=https://{yourOktaDomain}/oauth2/default
SCOPE=parts_manager
复制代码

你如今须要建立一个客户端。导航到 Applications,而后单击 Add Application。选择 Service,而后单击 Next。输入服务名(例如 Parts Manager)而后单击 Done

这将带你到具体的客户凭据的页面。这些是服务器 B(将消耗 REST API 的服务器)为了进行身份验证所须要的凭据。在本例中,客户端和服务器代码位于同一存储库中,所以继续将这些数据添加到你的 .env 文件中。请确保将 {yourClientId}{yourClientSecret} 替换为此页面中的值。

CLIENT_ID={yourClientId}
CLIENT_SECRET={yourClientSecret}
复制代码

建立中间件来验证 Express 中的令牌

在 Express 中,你能够添加将在每一个端点以前运行的中间件。而后能够添加元数据,设置报头,记录一些信息,甚至能够提早取消请求并发送错误消息。在本例中,你须要建立一些中间件来验证客户端发送的令牌。若是令牌是有效的,它会被送达至 REST API 并返回适当的响应。若是令牌无效,它将使用错误消息进行响应,这样只有受权的机器才能访问。

想要验证令牌,你尅使用 OKta 的中间件。你还须要一个叫作 dotenv 的工具来加载环境变量:

npm install dotenv@6.0.0 @okta/jwt-verifier@0.0.12
复制代码

如今建立一个叫作 auth.js 的文件,它能够导出中间件:

auth.js

const OktaJwtVerifier = require('@okta/jwt-verifier')

const oktaJwtVerifier = new OktaJwtVerifier({ issuer: process.env.ISSUER })

module.exports = async (req, res, next) => {
  try {
    const { authorization } = req.headers
    if (!authorization) throw new Error('You must send an Authorization header')

    const [authType, token] = authorization.trim().split(' ')
    if (authType !== 'Bearer') throw new Error('Expected a Bearer token')

    const { claims } = await oktaJwtVerifier.verifyAccessToken(token)
    if (!claims.scp.includes(process.env.SCOPE)) {
      throw new Error('Could not verify the proper scope')
    }
    next()
  } catch (error) {
    next(error.message)
  }
}
复制代码

函数首先会检查 authorization 报头是否在该请求中,而后抛出一个错误。若是存在,它应该相似于 Bearer {token},其中 {token} 是一个 JWT 字符串。若是报头不是以 Bearer 开头,则会引起另外一个错误。而后咱们将令牌发送到 Okta 的 JWT 验证器 来验证令牌。若是令牌无效,JWT 验证器将抛出一个错误,不然,它将返回一个带有一些信息的对象。而后你能够验证要求是否包含你指望的范围。

若是一切顺利,它就会以无参的形式调用 next() 函数,这将告诉 Express 能够转到链中的下一个函数(另外一个中间件或最终端点)。若是将字符串传递给 next 函数,那么 Express 将其视为将被传回客户端的错误,而且不会在链中继续。

你仍然须要导入这个函数并将其做为中间件添加到应用程序中。你还须要在索引文件的顶部加载 dotenv,以确保 .env 中的环境变量加载到你的应用程序中。对 index.js 做如下更改:

index.js

@@ -1,11 +1,14 @@
+require('dotenv').config()
 const express = require('express')
 const bodyParser = require('body-parser')
 const { promisify } = require('util')

+const authMiddleware = require('./auth')
 const initializeDatabase = require('./database')

 const app = express()
 app.use(bodyParser.json())
+app.use(authMiddleware)

 const startServer = async () => {
   await initializeDatabase(app)
复制代码

若是测试请求是否被正确阻止,请尝试再次运行...

$ npm test && node .
复制代码

而后在另外一个终端上运行一些 curl 命令来进行检测:

  1. 受权报头是否在请求之中
$ curl localhost:3000/parts
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>You must send an Authorization header</pre>
</body>
</html>
复制代码
  1. 在受权请求的报头中是否有 Bearer 令牌
$ curl localhost:3000/parts -H 'Authorization: Basic asdf:1234'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Expected a Bearer token</pre>
</body>
</html>
复制代码
  1. Bearer 令牌是否有效
$ curl localhost:3000/parts -H 'Authorization: Bearer asdf'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Jwt cannot be parsed</pre>
</body>
</html>
复制代码

在 Node 中建立一个测试客户端

你如今已经禁止没有有效令牌的人访问应用程序,但如何获取令牌并使用它呢?我会向你演示如何在 Node 中编写一个简单的客户端,这也将帮助你测试一个有效令牌的工做。

npm install btoa@1.2.1 request-promise@4.2.2
复制代码

client.js

require('dotenv').config()
const request = require('request-promise')
const btoa = require('btoa')

const { ISSUER, CLIENT_ID, CLIENT_SECRET, SCOPE } = process.env

const [,, uri, method, body] = process.argv
if (!uri) {
  console.log('Usage: node client {url} [{method}] [{jsonData}]')
  process.exit(1)
}

const sendAPIRequest = async () => {
  const token = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)
  try {
    const auth = await request({
      uri: `${ISSUER}/v1/token`,
      json: true,
      method: 'POST',
      headers: {
        authorization: `Basic ${token}`
      },
      form: {
        grant_type: 'client_credentials',
        scope: SCOPE
      }
    })

    const response = await request({
      uri,
      method,
      body,
      headers: {
        authorization: `${auth.token_type} ${auth.access_token}`
      }
    })

    console.log(response)
  } catch (error) {
    console.log(`Error: ${error.message}`)
  }
}

sendAPIRequest()
复制代码

这里,代码将 .env 中的变量加载到环境中,而后从 Node 中获取它们。节点将环境变量存储在 process.envprocess 是一个具备大量有用变量和函数的全局变量)。

require('dotenv').config()
// ...
const { ISSUER, CLIENT_ID, CLIENT_SECRET, SCOPE } = process.env
// ...
复制代码

接下来,因为这将从命令行运行,因此你能够再次使用 process 来获取与 process.argv 一块儿传入的参数。这将为你提供一个数组,其中包含传入的全部参数。前两个逗号前面没有任何变量名称,由于在本例中前两个不重要;他们只是通向 node 的路径,以及脚本的名称(client 或者 client.js)。

URL 是必须的,它包括端点,可是方法和 JSON 数据是可选的。默认的方法是 GET,所以若是你只是获取数据,就能够忽略它。在这种状况下,你也不须要任何有效负载。若是参数看起来不正确,那么这将使用错误消息和退出代码 1 退出程序,这表示错误。

const [,, uri, method, body] = process.argv
if (!uri) {
  console.log('Usage: node client {url} [{method}] [{jsonData}]')
  process.exit(1)
}
复制代码

Node 当前不容许在主线程中使用 await,所以要使用更干净的 async/await 语法,你必须建立一个函数,而后调用它。

若是在任何一个 await 函数中发生错误,那么屏幕上就会打印出 try/catch

const sendAPIRequest = async () => {
  try {
    // ...
  } catch (error) {
    console.error(`Error: ${error.message}`)
  }
}

sendAPIRequest()
复制代码

这是客户端向受权服务器发送令牌请求的地方。对于受权服务器自己的受权,你须要使用 Basic Auth。当你获得一个内置弹出要求用户名和密码时,基本认证是浏览器使用相同的行为。假设你的用户名是 AzureDiamond 而且你的密码是 hunter2。你的浏览器就会将它们用(:)连起来,而后 base64(这就是 btoa 函数的功能)对它们进行编码,来获取 QXp1cmVEaWFtb25kOmh1bnRlcjI=。而后它发送一个受权报头 Basic QXp1cmVEaWFtb25kOmh1bnRlcjI=。服务器能够用 base64 对令牌进行解码,以获取用户名和密码。

基础受权自己并不安全,由于它很容易被破解,这就是为何 https 对于中间人攻击很重要。在这里,客户端 ID 和客户端密钥分别是用户名和密码。这也是为何必须保证 CLIENT_IDCLIENT_SECRET 是私有的缘由。

对于 OAuth 2.0,你还须要制定受权类型,在本例中为 client_credentials,由于你计划在两台机器之间进行对话。你还要指定做用域。还有须要其余选项须要在这里进行添加,但这是咱们这个示例所须要的全部选项。

const token = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)
const auth = await request({
  uri: `${ISSUER}/v1/token`,
  json: true,
  method: 'POST',
  headers: {
    authorization: `Basic ${token}`
  },
  form: {
    grant_type: 'client_credentials',
    scope: SCOPE
  }
})
复制代码

一旦你经过验证,你就会获得一个访问令牌,你能够将其发送到 REST API,改令牌应该相似于 Bearer eyJra...HboUg(实际令牌要长的多 —— 可能在 800 个字符左右)。令牌包含 REST API 须要的全部信息,能够验证令牌的失效时间以及各类其余信息,像请求做用域、发出者和用于令牌的客户端 ID。

来自 REST API 的响应就会打印在屏幕上。

const response = await request({
  uri,
  method,
  body,
  headers: {
    authorization: `${auth.token_type} ${auth.access_token}`
  }
})

console.log(response)
复制代码

如今就尝试一下。一样,用 npm test && node . 启动你的应用程序,而后尝试一些像下面的命令:

$ node client http://localhost:3000/parts | json
[
  {
    "id": 1,
    "partNumber": "abc-123",
    "modelNumber": "xyz-789",
    "name": "Alphabet Soup",
    "description": "Soup with letters and numbers in it",
    "createdAt": "2018-08-16T02:22:09.446Z",
    "updatedAt": "2018-08-16T02:22:09.446Z"
  }
]

$ node client http://localhost:3000/parts post '{ "partNumber": "ban-bd", "modelNumber": 1, "name": "Banana Bread", "description": "Bread made from bananas" }' | json
{
  "id": 2,
  "partNumber": "ban-bd",
  "modelNumber": "1",
  "name": "Banana Bread",
  "description": "Bread made from bananas",
  "updatedAt": "2018-08-17T00:23:23.341Z",
  "createdAt": "2018-08-17T00:23:23.341Z"
}

$ node client http://localhost:3000/parts | json
[
  {
    "id": 1,
    "partNumber": "abc-123",
    "modelNumber": "xyz-789",
    "name": "Alphabet Soup",
    "description": "Soup with letters and numbers in it",
    "createdAt": "2018-08-16T02:22:09.446Z",
    "updatedAt": "2018-08-16T02:22:09.446Z"
  },
  {
    "id": 2,
    "partNumber": "ban-bd",
    "modelNumber": "1",
    "name": "Banana Bread",
    "description": "Bread made from bananas",
    "createdAt": "2018-08-17T00:23:23.341Z",
    "updatedAt": "2018-08-17T00:23:23.341Z"
  }
]

$ node client http://localhost:3000/parts/1 delete | json
{}

$ node client http://localhost:3000/parts | json
[
  {
    "id": 2,
    "partNumber": "ban-bd",
    "modelNumber": "1",
    "name": "Banana Bread",
    "description": "Bread made from bananas",
    "createdAt": "2018-08-17T00:23:23.341Z",
    "updatedAt": "2018-08-17T00:23:23.341Z"
  }
]
复制代码

了解更多关于 Okta 的 Node 和 OAuth 2.0 客户端凭据的更多信息

但愿你已经看到了在 Node 中建立 REST API 并对未经受权的用户进行安全保护是多么容易的。如今,你已经有机会建立本身的示例项目了,请查看有关 Node、OAuth 2.0 和 Okta 的其余一些优秀资源。你还能够浏览 Okta 开发者博客,以获取其余优秀文章。

和之前同样,你能够在下面的评论中或在 Twitter @oktadev 给咱们提供反馈或者提问,咱们期待收到你的来信!

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏