使用 OAuth 2.0 客户端凭据保护 Node API

保护服务器到服务器的API服务可能很棘手。 OAuth 2.0是将用户身份验证转移到另一个服务的绝佳方法,但是如果没有用户要身份验证怎么办? 在本文中,我将向您展示如何在用户上下文之外(也称为“客户端凭据流”)使用OAuth 2.0。

您可以使用第三方服务来管理您的授权,而不必为客户端(其他服务器)存储和管理API密钥。 这种工作方式是API客户端向OAuth服务器发送请求以获取API令牌。 该令牌随后随其请求从API客户端发送到您的API服务。 获得客户令牌后,您可以验证其有效性,而无需存储有关客户的任何信息。

客户凭证流验证的工作方式

验证您收到的API服务令牌的一种方法是将令牌转发到OAuth服务器以询问其是否有效。 此方法的缺点是,发送到服务器的每个API请求也需要发送到OAuth服务器的请求,这增加了您响应客户端的时间。 一种替代方法是使用一种称为本地验证的方法,该策略由JSON Web令牌(JWT)普及。 JWT在未加密的机器可读JSON中包含您的声明(客户端数据)。

当使用本地验证模式来验证API令牌(JWT)时,可以使用数学来验证以下内容:

您的API收到的令牌尚未被篡改您的API收到的令牌尚未过期令牌中编码的某些JSON数据片段就是您期望的

这样安全吗? 您可能想知道。 JWT包含三部分:标头、有效负载和签名。 标头和有效负载是简单的base64编码的字符串,可以轻松解密和读取。 签名使用标头中列出的算法以及私钥来创建标头和有效负载的哈希。 没有私钥就无法重新创建哈希,但是可以使用公钥进行验证。

在某种程度上,这就像驾照或护照。 这很难伪造,但是对别人来说很容易看到它,并看到您的姓名、出生日期和其他信息。 您可以扫描条形码,用黑光对其进行测试,或寻找水印以帮助验证其有效性。

尽管在概念上相似,但是有效的JWT实际上很难建立。 具有足够技能的人可以创建令人信服的驾驶执照,但是如果没有私钥,则可能需要花费现代计算机几年的时间才能强制使用有效的JWT签名。 令牌也应具有到期时间。 虽然是可配置的,但默认设置为一小时。 这意味着,如果客户端需要向您的API服务器提出新请求,则需要每60分钟请求一个新令牌。 万一您的令牌被盗,这是一个额外的安全层。 谁知道? 也许那里有一台量子计算机可以在几个小时内重新创建签名。

现在,您了解了OAuth 2.0客户端凭据流程的基本原理,让我们构建一个使用客户端凭据和Okta的Node API。

什么是Okta?

简而言之,我们使身份管理比过去更容易、更安全、更可伸缩。Okta是一个API服务,允许您创建、编辑和安全地存储用户帐户和用户帐户数据,并将它们与一个或多个应用程序连接。我们的空气污染指数可让你:

  • 验证和授权您的用户
  • 存储有关您的用户的数据
  • 执行基于密码的社交登录
  • 通过多因素身份验证保护您的应用程序
  • 以及更多! 查看我们的产品文档以获取更多信息

注册一个永久免费的开发者帐户 ,完成后,请返回以了解有关在Node中构建安全API的更多信息!

创建一个基本节点API

为了入门,我将向您展示如何在Node中创建基本API。 Node在名为package.json的文件中保留了依赖项列表以及其他元数据。

假设您已经安装了Node,请为您的API服务器创建一个新文件夹。 然后,您可以使用npm为您生成package.json 。 命令npm init将提示您输入一些信息,但是您可以继续Enter以遵循默认值。

$ mkdir client-credentials-flow
$ cd client-credentials-flow
$ git init
$ npm init

在Node中启动并运行API服务器的最快方法是使用Express。 您可以使用命令npm install [email protected] --save将Express添加为依赖npm install [email protected] --save 。 这将创建一个名为node_modules的文件夹,在该文件夹中下载express及其依赖的所有内容,然后您的应用可以使用这些文件夹。 为了使开发更快,您还可以添加一个名为nodemon的dev依赖nodemon ,只要您更改代码, nodemon将重新启动服务器。 要添加dev-dependency,请使用-D标志: npm install -D [email protected]

在构建Node应用程序时,通常需要忽略将node_modules文件夹存储在git node_modules中。 您可以通过将node_modules添加到.gitignore文件中来实现。

echo node_modules >> .gitignore

软件包管理器还将包含一个文件(例如package-lock.jsonyarn.lock ),以便在另一台计算机上(使用npm installyarn )下载node_modules时,将下载相同版本的文件。 这有助于防止服务器之间的任何不一致,并使您不知道为什么某些东西可以在您的计算机上正常工作,而在生产环境中却无法正常工作。 确保也将该文件提交到您的git repo:

$ git add .
$ git commit -m "Adding package files."

您还可以将脚本添加到package.json文件夹中以运行这些命令。 使用命令node .创建一个start脚本node ..告诉它以main身份运行package.json列出的脚本,默认情况下为index.js 。您还希望使用nodemon *.js node .命令创建dev脚本。。命令行依赖项,像nodemon 、在道路节点脚本中运行时,您现在可以运行这些命令。 npm startnpm run dev你。 package.json文件现在应该是这个样子:

package.json

{
  "name": "client-credentials-flow",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon *.js node .",
    "start": "node .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3"
  },
  "devDependencies": {
    "nodemon": "^1.17.5"
  }
}

现在,对于最基本的“ Hello World”快递应用程序:

index.js

const express = require('express')
const app = express()

app.get('/', (req, res) => res.send('Hello World!'))

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`Listening on port ${port}`))

仅此而已! 要启动它,请在终端窗口中键入npm run dev 。 您可以在进行更改时保持此运行状态,它将自动重新启动以反映新的更改。 现在,在浏览器中转到http://localhost:3000 (或在带有curl http://localhost:3000的命令行上),您应该看到Hello World! 回响。

向您的Node API的OAuth 2.0提供程序注册

现在保护应用程序。 这是您需要设置OAuth 2.0服务的地方。 Okta是一项基于云的服务,可让开发人员轻松安全地存储OAuth 2.0令牌,用户帐户和用户数据,然后将它们与一个或多个应用程序连接。 Okta还提供了适用于多种语言(包括Node)的库,以使它们的API非常易于开发人员集成到各种应用程序中。

您可以使用Okta快速轻松地设置服务器到服务器的身份验证。 如果您还没有帐户,请注册一个免费的Okta Developer帐户 。 注册后,您将获得一个唯一的Okta Org URL(例如https://{yourOktaDomain} )和一封电子邮件,以激活您的新帐户。

为了使客户端到服务器的身份验证工作,您将需要两个部分:授权服务器和测试客户端/应用程序。

创建授权服务器

客户端可以在授权服务器上请求令牌以在您的API服务器上使用。 在Okta仪表板内,单击标题中的API选项卡,然后选择“ 授权服务器”选项卡。 点击添加授权服务器 ,然后为您的服务器提供有用的名称和描述。 Audience应该是将使用令牌的服务器的绝对路径。

创建授权服务器后,将需要一个范围供您的客户端访问。 单击“ 范围”选项卡并添加范围。 您可以拥有许多这样的功能,这可以帮助定义正在使用API​​的哪些部分,甚至是谁在使用它。

现在您有了作用域,还需要指定一些规则来说明谁可以访问它。 单击访问策略选项卡,然后创建一个新策略。 现在,只允许访问All clients 。 然后单击“ 添加规则”并为其命名。 由于这仅用于客户端凭据,因此请删除其他代表用户执行的Authorization Code类型(“ Authorization Code ,“ Implicit ”和“ Resource Owner Password ),因此唯一的授权类型是“ Client Credentials 。 除此之外,现在就使用默认设置。

返回“ 设置”选项卡,记下Issuer 。 这是客户端将用于请求令牌的地址,以及您的API服务器将用来验证这些令牌有效的地址。

创建一个测试客户端

在您的Okta仪表板中,单击顶部标题中的“ 应用程序 ”。 应用程序也称为客户端,因此您可以在其中创建测试客户端。 单击添加应用程序,然后选择服务 (机器对机器)。 它唯一需要的信息是名称,因此您可以使用诸如Test Client类的东西。 这将为您提供客户的凭据(在此测试用例中,就是您)。

保护您的Node API

现在,您已经完成了所有的难题,因此只有经过身份验证的用户才能收到心爱的“ Hello World”欢迎消息,其他所有人都会出错。

安全地存储您的凭据

您需要安全地存储凭据。 一种方法是在本地保存未存储在git中的文件(如果您的代码是开源的,则特别有用,但是无论如何还是一件好事)。 这也使您可以将相同的代码用于多个应用程序(例如,开发和生产环境)。

继续并创建一个.env文件,其中包含授权服务器中的颁发者和测试应用程序中的客户端凭据。 确保将其添加到您的.gitignore文件中,这样就不会将其添加到您的git repo中: echo .env >> .gitignore 。 您的.env文件应如下所示:

.env

ISSUER=https://{yourOktaDomain}/oauth2/abcdefg1234567
DEFAULT_SCOPE=such_scope
TEST_CLIENT_ID=client-id
TEST_CLIENT_SECRET=client-secret

代码读取.env文件的快速方法是使用名为dotenv的库。 您可以使用npm install dotenv安装它。 然后将require('dotenv').config()index.js的第一行。 您希望它是运行的第一件事,以便其余代码可以访问这些环境变量。

验证客户请求

Okta提供了一个Node库来帮助验证JSON Web令牌。 当它第一次看到验证令牌的请求时,它将通过您的授权服务器获取公共密钥Okta。 尽管可以配置,但默认情况下它将保留一个键一个小时。 如果输入的令牌无法验证,它将与Okta一起检查是否有新的密钥要使用。 如果仍然无法验证,则库将引发错误。 您可以使用npm install @okta/[email protected]软件包。

您需要为软件包提供JWT。 您可以告诉客户如何提供令牌,这可以通过多种方式完成。 通常的做法是在HTTP请求中使用Authorization标头,该请求通常看起来像Bearer MG9h...NhOq== 。 修改您的/端点以查找令牌,并使用Okta进行验证。

index.js

const OktaJwtVerifier = require('@okta/jwt-verifier')
const oktaJwtVerifier = new OktaJwtVerifier({
  issuer: process.env.ISSUER,
})

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

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

    await oktaJwtVerifier.verifyAccessToken(token)
    res.json('Hello World!')
  } catch (error) {
    res.json({ error: error.message })
  }
})

尝试再次访问http://localhost:3000 。 由于您未通过身份验证,这一次您会收到一条错误消息。

如果您不熟悉相对较新的async/await语法,这对您来说可能有点奇怪。 这里发生的是该函数被标记为async ,因此它将始终返回Promise 。 当它看到await关键字时,该函数的其余部分将暂停,直到响应返回。 同时,释放了主线程以供其他JavaScript代码执行。

在此示例中, verifyAccessToken如果无法立即验证令牌, verifyAccessToken就会向Okta发送请求。 如果您在代码中的其他位置具有setInterval ,则在等待Okta的响应时,该代码仍然可以执行。

verifyAccessToken完成后,如果令牌无效,则会抛出错误。 因此,如果通过该行而没有引发错误,则可以安全地假设客户端已通过验证,并且可以发送“ Hello World”消息。 如果您需要有关客户端的更多信息,则可以使用const jwt = await oktaJwtVerifier.verifyAccessToken(token)从验证程序获得响应。

测试您的安全API

现在,您可以看到在没有正确身份验证的情况下浏览器出现错误,但是我没有向您显示仍然可以正确验证自己。 为了从授权服务器获取令牌,您可以编写一个简单的Node脚本。 使用本机节点request有点繁琐,因此您可以使用request-promise库,该库将允许您继续使用promise和不错的async/await语法。 您还将需要btoa ,它将字符串转换为base64。

npm install [email protected] [email protected]

test.js

require('dotenv').config()
const request = require('request-promise')
const btoa = require('btoa')
const { ISSUER, TEST_CLIENT_ID, TEST_CLIENT_SECRET, DEFAULT_SCOPE } = process.env

const test = async () => {
  const token = btoa(`${TEST_CLIENT_ID}:${TEST_CLIENT_SECRET}`)
  try {
    const { token_type, access_token } = await request({
      uri: `${ISSUER}/v1/token`,
      json: true,
      method: 'POST',
      headers: {
        authorization: `Basic ${token}`,
      },
      form: {
        grant_type: 'client_credentials',
        scope: DEFAULT_SCOPE,
      },
    })

    const response = await request({
      uri: 'http://localhost:3000',
      json: true,
      headers: {
        authorization: [token_type, access_token].join(' '),
      },
    })

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

test()

现在,在您的应用仍在端口3000上运行的情况下,使用node test.js运行测试。 这将向Okta发送请求以获取令牌,然后将该令牌转发到您的API服务器并打印结果。 您应该得到一个不错的“ Hello World”问候!

快速注册客户

您有一个测试客户端,但在现实世界中,您可能希望让人们注册您的API,而无需您登录Okta并手动为其创建客户端。 您也可能不希望每个人都共享相同的客户端ID和机密,以便例如跟踪谁在发出什么请求。

Okta提供了一个API,可让您自动执行各种任务。 其中之一是创建新的应用程序。 Okta还具有一个Node库,使其非常简单。 为了使您的应用程序可以通过Okta进行身份验证,您需要一个API令牌。 登录到您的信息中心,然后从标题的API下拉列表中选择令牌 。 单击创建令牌,并为其赋予一个有意义的名称。 然后,这一次将为您提供令牌-如果丢失,则需要创建另一个令牌。 继续,并将其作为TOKEN添加到您的.env文件中。

使用npm install @okta/[email protected]安装Okta Node SDK。 它需要您的组织URL,因此您也应该将其添加到.env文件中。 然后在index.js创建一个新路由来注册新客户端。

index.js

const okta = require('@okta/okta-sdk-nodejs')
const oktaClient = new okta.Client({
  orgUrl: process.env.ORG_URL,
  token: process.env.TOKEN,
})

app.get('/register/:label', async (req, res) => {
  try {
    const application = await oktaClient.createApplication({
      name: 'oidc_client',
      label: req.params.label,
      signOnMode: 'OPENID_CONNECT',
      credentials: {
        oauthClient: {},
      },
      settings: {
        oauthClient: {
          grant_types: ['client_credentials'],
          application_type: 'service',
        },
      },
    })

    const { client_id, client_secret } = application.credentials.oauthClient

    res.json({
      client_id,
      client_secret,
      request_token_url: `${process.env.ISSUER}/v1/token`,
    })
  } catch (error) {
    res.json({ error: error.message })
  }
})

现在,您可以转到http://localhost:3000/register/Awesome+App+Name (在您的浏览器中可以使用)来创建新客户端。 第一次去那里时,它应该为您提供客户ID和密码,并提醒您在哪里申请令牌。 您可以使用.env新客户端ID替换以前的客户端ID和密码,然后重新运行test.js以查看该客户端现在也可以使用。

如果您重新登录Okta Developer Console,您将看到“ Awesome App Name”已添加为应用程序。

请记住, 这是一个测试……这只是一个测试。 您可能不想让任何人未经任何类型的验证就公开注册API密钥。 这可能是实施某种CAPTCHA或要求用户身份验证以获取API密钥的好地方。 一旦拥有API密钥,他们便可以在其应用程序中使用该密钥,而无需进一步的用户身份验证。

了解有关Okta的节点和OAuth 2.0客户端凭据的更多信息

希望我已经向您展示了,为您的Node API提供出色的安全性确实很容易。 Okta在保护您的应用程序方面提供了更多的选择。 如果您想了解有关Node、Okta和OAuth 2.0客户端凭据的更多信息,请查看其他一些资源,或浏览Okta开发人员博客的其余部分:

  • Okta Node SDK
  • 实施客户凭证流程
  • 验证访问令牌
  • 使用Spring Boot和OAuth 2.0进行安全的服务器到服务器通信
  • 如何使用令牌身份验证保护.NET Web API的安全

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

原文链接: https://www.sitepoint.com/secure-a-node-api-with-oauth-2-0-client-credentials/

你可能感兴趣的:(使用 OAuth 2.0 客户端凭据保护 Node API)