oauth2.0 node_使用Node和OAuth 2.0构建简单的REST API

oauth2.0 node

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

JavaScript在网络上无处不在–几乎每个网页都至少包含一些JavaScript,即使没有,JavaScript也可能具有某种扩展名,无论如何都将JavaScript代码注入到页面中。 在2018年很难避免。

JavaScript也可以在浏览器的上下文之外使用,从托管Web服务器到控制RC汽车或运行完整的操作系统,无所不包。 有时,无论是在本地网络还是在Internet上,您都希望几个服务器相互通信。

今天,我将向您展示如何使用Node.js创建REST API,并使用OAuth 2.0保护它,以防止不必要的请求。 REST API遍布整个Web,但是如果没有适当的工具,则需要大量的样板代码。 我将向您展示如何使用几个令人称奇的工具,使它们变得轻而易举,其中包括Okta来实现客户端凭据流,该客户端凭据流将两台计算机安全地连接在一起而无需用户上下文。

构建您的节点服务器

使用Express JavaScript库在Node中设置Web服务器非常简单。 新建一个包含服务器的文件夹。

$ mkdir rest-api

Node使用package.json来管理依赖项并定义您的项目。 要创建一个,请使用npm init ,它将询问您一些问题以帮助您初始化项目。 现在,您可以使用标准JS来实施编码标准,并将其用作测试。

$ cd rest-api

$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

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 ,因此您应该使用该名称创建一个新文件。 以下代码将为您提供一个真正的基本服务器,该服务器实际上不执行任何操作,但默认情况下在端口3000上进行侦听。

index.js

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语法,并使我们的代码看起来更漂亮。

为了使这一工作,你需要安装的依赖关系,你require在文件的顶部。 使用npm install添加它们。 这将自动将一些元数据保存到您的package.json文件中,并将其本地安装在node_modules文件夹中。

注意 :永远不要将node_modules提交给源代码管理,因为它趋于Swift膨胀,而且package-lock.json文件将跟踪您以前使用的确切版本,如果将其安装在另一台计算机上,它们将获得相同的代码。

$ npm install express@4.16.3 util@0.11.0

为了快速起步,请将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 .运行服务器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 。 我将在下面更详细地解释每个部分。

免费学习PHP!

全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。

原价$ 11.95 您的完全免费

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响应(我使用json CLI ,使用npm install --global 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的表,其中包含一些有用的字段以跟踪零件。 默认情况下,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动词的端点,这些端点通常是自动的。

要实际创建数据库,您需要运行database.sync() ,它返回一个Promise。 您将要等到完成后再启动服务器。

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保护节点+ Express REST API

现在您已经建立并运行了REST API,想象一下您想要一个特定的应用程序从远程位置使用它。 如果您将其原样托管在Internet上,则任何人都可以随意添加,修改或删除部件。

为避免这种情况,您可以使用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 ,然后导航到“ 授权服务器”选项卡。 单击指向您的default服务器的链接。

在此设置选项卡中,复制Issuer字段。 您需要将其保存在Node应用程序可以读取的位置。 在您的项目中,创建一个名为.env的文件,如下所示:

.env

ISSUER=https://{yourOktaDomain}/oauth2/default

ISSUER的值应为“设置”页面的“ Issuer URI字段中的值。

提示发行者URL。

注意 :通常,您不应将此.env文件存储在源代码管理中。 这允许多个项目使用相同的源代码,而无需单独的fork。 它还可以确保您的安全信息不是公开的(尤其是如果您将代码作为开源发布时)。

接下来,导航到“ 作用域”选项卡。 单击添加范围按钮,然后为您的REST API创建范围。 您需要给它起一个名字(例如, parts_manager ),如果您愿意,可以给它一个描述。

添加范围屏幕截图。

您还应该将范围名称添加到.env文件中,以便您的代码可以访问它。

.env

ISSUER=https://{yourOktaDomain}/oauth2/default
SCOPE=parts_manager

现在,您需要创建一个客户端。 导航到“ 应用程序” ,然后单击“ 添加应用程序” 。 选择服务 ,然后单击下一步 。 输入您的服务的名称(例如, Parts Manager ),然后单击完成

这将带您进入具有客户凭证的页面。 这些是服务器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命令以测试:

授权标头是必需的:

$ 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>

授权标头中必须包含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>

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中编写一个简单的客户端,这也将帮助您测试有效的令牌是否有效。

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的路径,以及脚本的名称( clientclient.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()

客户端在此处向授权服务器发送令牌请求。 要使用授权服务器本身进行授权,您需要使用基本身份验证。 当您获得一个内置的弹出式窗口要求输入用户名和密码时,基本身份验证与浏览器使用的功能相同。 假设您的用户名为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 .启动应用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的节点和OAuth 2.0客户端凭据的更多信息

希望您已经看到在Node中创建REST API并防止未经授权的用户对其进行保护非常容易。 您可以在GitHub上找到此示例的代码。

现在您已经有机会制作自己的示例项目,请查看有关Node,OAuth 2.0和Okta的其他一些出色资源。 您也可以浏览Okta开发人员博客,以获得其他出色的文章。

与往常一样,您可以在下面的评论中提供反馈或问题,或者在Twitter @oktadev上与我们联系 。 我们期待您的回音!

翻译自: https://www.sitepoint.com/build-a-simple-rest-api-with-node-and-oauth-2-0/

oauth2.0 node

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值