Heroku是一个平台即服务(PaaS),支持多种语言。 最初,它只支持Ruby网站,但现在支持各种语言,包括Node.js的JavaScript。 Heroku还支持Docker,因此你可以将几乎所有的东西部署到它上面。
本教程将教你如何使用Node.js的Express框架建立一个小型应用程序。 然后,你将通过将Okta OIDC中间件与你的应用程序集成,使用Okta来保护该应用程序。 最后,你将学习如何将应用程序部署到Heroku,为你的Heroku网站更新你的Okta应用程序,并在Heroku中为你的应用程序设置环境变量。
本教程使用了以下技术,但不需要任何先前的经验:
- Visual Studio Code
- Node.js
- 一个免费的Okta开发者账户(用于处理你的OAuth需求)
- Heroku账户
- 一个GitHub上的存储库
当然,如果你只想看看代码,可以随时在GitHub上查看。
创建你的Okta应用程序
安装Okta CLI并运行okta login 。然后,运行okta apps create 。选择默认的应用程序名称,或者根据你的需要改变它。 选择Web,然后按回车键。
选择节点。 然后,将重定向URI改为http://localhost:3000/authorization-code/callback ,并接受默认的注销重定向URI:http://localhost:3000 。
Okta CLI是做什么的?
Okta CLI将在您的Okta机构中创建一个OIDC网络应用。它将添加您指定的重定向URI,并授予Everyone组的访问权。当它完成后,您会看到如下输出。
Okta application configuration has been written to: /path/to/app/.okta.env
运行cat .okta.env (或Windows上的type .okta.env ),查看你的应用程序的发行者和凭证。
export OKTA_OAUTH2_ISSUER="https://dev-133337.okta.com/oauth2/default"
export OKTA_OAUTH2_CLIENT_ID="0oab8eb55Kb9jdMIr5d6"
export OKTA_OAUTH2_CLIENT_SECRET="NEVER-SHOW-SECRETS"
您的Okta域是发行方的第一部分,在/oauth2/default 。
注意:您也可以使用Okta管理控制台来创建您的应用程序。更多信息请参见创建一个节点应用程序。
创建你的Express应用程序
现在是时候把你的注意力转向编写你的应用程序了。 你将使用express application generator 来构建它。 打开你的应用程序所在的文件夹并运行以下命令。
npx express-generator
安装你的依赖项
运行下面的命令来安装expression-generator的默认依赖项。
npm install
你将需要为你的应用程序安装一些额外的依赖项。
首先,你将添加dotenv ,以存放你的敏感信息和环境特定信息。
npm i dotenv@16.0.0
接下来,你将添加Bootstrap。 你将在你的jade 模板中使用Bootstrap库来编写前端。 Bootstrap是一个简单的UI框架,有许多样本可以帮助你快速开发HTML页面。
npm i bootstrap@5.1.3
你将需要Okta的oidc-middleware ,以帮助保护你的应用程序。 这个软件包使得与Okta的整合不费吹灰之力,快速而安全。 oidc-middleware 你将能够用你的Okta应用程序的细节来配置中间件,只需几行代码就能开始保护你的路由。使用express-session ,所以你也需要安装它。
npm i express-session@1.17.2
npm i @okta/oidc-middleware@4.3.0
编写你的服务器代码
express-generator包在为一个简单的Node.js应用程序提供支架方面做得很好。 在本教程中,你可以让大部分应用程序保持原样。 不过,你需要做一些改动。
打开.okta.env ,在你的Okta配置值之后添加以下内容到文件的结尾。
export APP_BASEURL="http://localhost:3000"
注意: 确保你把.okta.env 添加到你的.gitignore 文件。
接下来,打开app.js ,用以下代码替换该文件的内容。
require('dotenv').config({ path: '.okta.env' })
var createError = require("http-errors");
var express = require("express");
var path = require("path");
var cookieParser = require("cookie-parser");
var logger = require("morgan");
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
const session = require("express-session");
const { ExpressOIDC } = require("@okta/oidc-middleware");
var app = express();
// session support is required to use ExpressOIDC
app.use(
session({
secret: "this should be secure",
resave: true,
saveUninitialized: false,
})
);
const { OKTA_OAUTH2_ISSUER, OKTA_OAUTH2_CLIENT_ID, OKTA_OAUTH2_CLIENT_SECRET, APP_BASEURL } = process.env;
const oidc = new ExpressOIDC({
issuer: OKTA_OAUTH2_ISSUER,
client_id: OKTA_OAUTH2_CLIENT_ID,
client_secret: OKTA_OAUTH2_CLIENT_SECRET,
appBaseUrl: APP_BASEURL,
redirect_uri: `${APP_BASEURL}/authorization-code/callback`,
scope: "openid profile",
});
// ExpressOIDC attaches handlers for the /login and /authorization-code/callback routes
app.use(oidc.router);
// view engine setup
app.set("views", path.join(__dirname, "views"));
app.set("view engine", "jade");
app.use(logger("dev"));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, "public")));
app.use(express.static(__dirname + "/node_modules/bootstrap/dist"));
app.use("/", indexRouter);
app.use("/users", usersRouter.userRoutes({ oidc: oidc }));
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
});
// error handler
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
module.exports = app;
这段代码大部分都是来自Express-generator的模板,值得注意的是增加了对Okta OIDC的配置。 首先,你需要在这个文件的顶部启用dotenv 。 接下来,配置express-session ,并将其添加到你的应用程序中。 最后,你可以使用.env 的变量来配置你的Okta OIDC中间件。
打开你的routes/index.js 文件,用下面的代码替换该文件的内容。
var express = require("express");
var router = express.Router();
/* GET home page. */
router.get("/", function (req, res, next) {
res.render("index", { loggedIn: req.isAuthenticated() });
});
module.exports = router;
这里你添加了一个名为loggedIn 的属性,供index.jade 模板使用。这个属性将帮助视图决定是否应该显示一个login 或logout 按钮。 主页的其他部分将是静态的。
打开你的routes/users.js 文件,用以下代码替换该文件的内容。
var express = require("express");
var router = express.Router();
function userRoutes(options) {
const oidc = options.oidc;
router.get("/", oidc.ensureAuthenticated(), function (req, res, next) {
res.render("users/index", {
loggedIn: true,
title: "Express",
user: req.userContext.userinfo,
});
});
return router;
}
module.exports.userRoutes = userRoutes;
这个文件是Okta OIDC中间件的神奇之处。 你在路由定义中传入oidc.ensureAuthenticated 中间件。 这个函数将把未经认证的用户重新路由到你在app.js 文件中定义的登录路由。 因为你没有明确设置这个参数,它将引导用户到/login ,并触发认证流程。 在这里传递给视图的模型也包含了一些关于用户的信息,当用户登陆这个页面时,你会显示这些信息。
编写你的前端代码
现在你可以写一些客户端代码了。 默认情况下,Express-generator会使用jade 来设置一些视图。 Jade是Node.js的一个模板引擎。 该语言包含了一些条件和流程控制,以使从你的模型中进行模板化的HTML更容易。 语法相当简单易学,而且有许多HTML到Jade的转换器。
打开与你的项目一起生成的layout.jade 文件,用下面的代码替换这些代码。
doctype html
html
head
title= title
link(rel='stylesheet' href='/stylesheets/style.css')
link(rel='stylesheet' href='css/bootstrap.min.css')
script(language='javascript' src='js/bootstrap.min.js')
body
.container
header.d-flex.flex-wrap.justify-content-center.py-3.mb-4.border-bottom
h5.d-flex.align-items-center.mb-3.mb-md-0.me-md-auto.text-dark.text-decoration-none
a(href="/") Home
ul.nav.nav-pills
if loggedIn
li.nav-item
a(href="users").nav-link Profile
li.nav-item
if loggedIn
form(action="logout" method="post")
button(type="submit").nav-link.active Logout
else
a(href="Users").nav-link.active Login
block content
我们使用了HTML元素;类是连锁的,它们之间有一个. 。属性以括号为界。 这个简单的布局从服务器上获取loggedIn ,并决定是否应该显示一个login 或logout 按钮。 如果用户已经登录,它还会有条件地显示一个Profile 页面的标签。
你可以通过在你的views 文件夹中创建一个名为users 的新文件夹,并添加一个名为index.jade 的新文件来添加个人资料页面。 在其中添加以下代码。
extends ../layout
block content
h1 Welcome #{user.name}
P locale: #{user.locale}
p UserName: #{user.preferred_username}
这个页面显示用户登录时从Okta检索到的用户信息。
将views/index.jade 中的代码替换为以下内容。
extends layout
block content
.container-fluid.py-5.bg-dark.text-white-50
h1.display-5.fw-bold Deploy a Node.JS Application to Heroku
p.col-md-8.fs-4
| A tutorial on how to deploy a
a(href="https://nodejs.org/en/" target="_blank") node.js
| application to
a(href="https://dashboard.heroku.com/apps" target="_blank") Heroku
| . Secured by
a(href="https://www.okta.com/" target="_blank") Okta
| . Written by
a(href="https://profile.fishbowlllc.com/" target="_blank") Nik Fisher.
br
p
| This repository shows you how to use Okta in a Node.js application and how to deploy the application to Heroku.
p Truncated for the purposes of the article.
如前所述,这是一个静态页面。 你的下一步是部署该应用程序。 但在这之前,运行命令npm run start ,并导航到http://localhost:3000 ,查看你的应用程序,确保它在本地工作。

部署到Heroku
确保你的所有代码都在运行,并检查到你的GitHub仓库。 导航到你的Heroku仪表盘。 如果这是你第一次使用Heroku,你应该还没有配置任何应用程序。 点击 "新建",然后创建新的应用程序。 将你的应用程序命名为okta-heroku-webapp-{yourUserName} 或其他你认为合适的独特名称。 按下创建应用程序。
在新创建的应用程序的页面上,找到部署标签上的部署方法部分。 点击GitHub,将你的Heroku账户连接到GitHub账户。
接下来,在连接到GitHub部分,找到你为本教程创建的仓库,按连接。

你可以在自动部署下启用自动部署。 此外,你还可以将其配置为等待CI通过后再部署应用程序。 CI可以从你的GitHub账户中进行配置。 这一步对于本教程来说并不是严格必要的,但看看它是如何融入CI管道的也不错。

最后,在Manual Deploy下选择你想部署的分支,然后点击Deploy Branch。 稍等片刻,你应该收到来自Heroku的消息,说你的应用程序已成功部署。
点击查看按钮,就可以看到你的应用程序。 在这一点上,你的应用程序不会运行,因为它还没有被正确配置。 记下你的网站的URL,然后你要做一些改变。
配置你的环境变量
在你的Heroku应用程序中,点击设置。 找到名为Config Vars 的部分,点击Reveal Config Vars。 在这里你将添加你在本地使用的.okta.env 文件中的相同的键和值对。 你可以在下面看到我的文件,其中的数值被模糊掉了。

配置您的Okta应用程序
接下来,你需要配置你的Okta应用程序,以接受来自Heroku的新URL。 在Okta管理面板上导航到您的应用程序,找到常规设置标签。 点击编辑。 在Login部分,为Sign-in redirect URIs添加一个值,与开发设置中的URI相匹配,但用Heroku应用程序的URL替换http://localhost:3000 。 例如,我的URI是https://okta-heroku-webapp-nfisher.herokuapp.com 。
为你的退出重定向URI添加一个类似的值,它应该是你的Heroku应用程序的主页。
现在返回到你的应用程序。 你应该能够登录并看到你的应用程序如期运行。
用Node和Heroku做更多事情
Heroku是一种快速、轻松地部署轻量级应用的好方法,可以滑入你现有的CI流程。 此外,它还可以免费开始使用。与Express和Okta一起,你可以快速建立安全的Web应用,并将它们部署到一个现代平台上。
在本教程中,你学会了如何使用Express-generator工具链构建一个Express应用程序。 然后你用Okta保证了它的安全。 接下来,你学会了如何在Heroku上创建一个应用程序,将其连接到GitHub仓库,并将你的应用程序从GitHub部署到Heroku。 最后,你学会了如何配置Heroku和Okta以使其相互配合。