[譯] 使用 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前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索