构建一个带身份验证的 Deno 应用

Build Your First Deno App with Authentication

Node.js 的创建者 Ryan Dahl 创建了一个用于设计 Web 应用程序的新框架。他回过头来,利用在最初编写 Node 时还不可用的新技术,纠正了事后发现的一些错误。这就是 Deno(发音为 DEH-no),一个用 TypeScript 编写的 “类似 Node 的” Web 应用的框架。在本文中,我将引导你创建一个带有身份验证的基本 Web 应用。

要点

  • 创建你的 Deno 应用
  • 用 Deno 构建真实的 Web 应用
  • 为你的 Deno 应用添加功能
  • 用 Okta 添加身份验证
  • 运行 Deno 程序

你几乎可以在 Deno 网站上找到所需的所有信息,以及有关当前可用于 Deno 的所有第三方库的信息。当前框架最大的缺点应该是:它只是在 2020 年 5 月 13 日发行了1.0版,因此即使有很多基本库,也没有 Node 的库那么多。不过对于那些精通 Node 的人,向 Deno 过渡应该很容易。

你可以在 https://deno.land/#installation 中找到安装说明。

创建你的 Deno 应用

我找不到任何基本的脚手架库,所以只能从一个空文件夹开始。在程序的根文件夹中,创建一个名为 index.ts 的文件,这将作为你 Deno 程序的起点。我们将会使用 Opine,它是 Deno 的 Express 克隆版本,可简化构建和路由。

与 Deno 不同的是,没有用于引入第三方库的包管理器。你可以通过使用库的完整 URL 来完成此操作。在 index.ts 文件顶部执行此操作,然后设置一个基本的 Web 应用程序。

import { opine } from 'https://deno.land/x/opine@0.12.0/mod.ts';

const app = opine();

app.get('/', (req, res) => {

res.send('Deno Sample');

});

app.listen(3000);

console.log('running on port 3000');

然后,在终端下切换到程序文件夹中,并输入以下内容来运行这个非常基本的程序:

deno run -A index.ts

-A 是用于开发目的的快捷选项。在默认情况下,Deno 完全处于锁定状态,所以需要把参数传递给 run 命令以允许访问,例如 --allow-net 允许联网, --allow-read 允许程序从文件系统读取。这里的 -A 允许所有内容,从而有效地禁用了所有安全性。当你运行这个程序然后转到 http://localhost:3000 时,空白页上将会出现 Deno Sample 字样。

用 Deno 构建真实的 Web 应用

虽然这是一个良好的开端,但并没有太大用处。你还需要添加一些“真实”的功能,这些功能比“真实世界”要多一些,接下来修改 index.ts 文件,使其内容为:

import { opine, serveStatic } from 'https://deno.land/x/opine@0.12.0/mod.ts';

import { renderFileToString } from 'https://deno.land/x/dejs@0.7.0/mod.ts';

import { join, dirname } from 'https://deno.land/x/opine@main/deps.ts';

import { ensureAuthenticated } from './middleware/authmiddleware.ts';

import users from './controllers/usercontroller.ts';

import auth from './controllers/authcontroller.ts';

const app = opine();

const __dirname = dirname(import.meta.url);

app.engine('.html', renderFileToString);

app.use(serveStatic(join(__dirname, 'public')));

app.set('view engine', 'html');

app.get('/', (req, res) => {

res.render('index', { title: 'Deno Sample' });

});

app.use('/users', ensureAuthenticated, users);

app.use('/auth', auth)

app.listen(3000);

console.log('running on port 3000');

你会注意到很多的 import 语句,这些语句引入了一些第三方库。在这里,我用的是 dejs,这是 Deno 的 EJS 端口。我还引入了 Opine 库中的一些用于处理目录名称的类。我在后面将会介绍本地导入的这三个文件。现在你只需要知道导入了它们。

opine() 实例化下面的代码行创建对本地目录的引用。下面的三行代码将视图引擎设置为 DEJS,用来处理类似 HTML 的文件,这很像 EJS 对 Node 的处理方式。下一部分已稍作更改以渲染这些 HTML 模板文件,并且最后两行代码引入了一些外部路由。需要注意的一件事是 /users 路由具有 ensureAuthenticated() 中间件功能。这将迫使用户先登录,然后才能访问该页面。

为你的 Deno 应用添加功能

接下来创建一些在上面代码所缺失的部分。从路由开始。在程序的根目录中创建一个名为 controllers 的文件夹。然后在该文件夹内添加一个 usercontroller.ts 文件,内容如下:

import { Router } from 'https://deno.land/x/opine@0.12.0/mod.ts';

const users = new Router();

// users routes

users.get('/me', (req, res) => {

res.render('users/me', { title: 'My Profile', user: res.app.locals.user });

});

export default users;

这是一个简单的路由文件。它从 Opine 获取路由,并创建一个新实例来挂起路由。然后有代码为 /me 添加路由以在 users/me 中渲染 HTML 视图。render() 调用还将标题和登录用户传递到页面。该页面将受到保护,以便始终有用户可以访问。

接下来,创建一些点击路由时能够显示的视图。在根文件夹中,添加一个 views 文件夹。在其中创建一个 shared 文件夹和一个 users 文件夹。在 shared 文件夹中,创建一个 header.htmlfooter.html 文件。在 users 文件夹中添加 me.html 文件。最后,在 views 文件夹本身中创建一个 index.html 文件。

这些是非常简单的方法,但是它演示了如何创建可被其他视图重用的视图。在 shared/header.html 文件中添加以下内容:

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width,initial-scale=1">

<title><%= title %></title>

</head>

<body>

这将输出 HTML 页面的顶部,并将标题注入页面。接下来,将以下内容添加到 shared/footer.html 文件中:

</body>

</html>

现在你可以在 index.html 文件中使用这些局部变量:

<%- await include('views/shared/header.html', { title }); %>

<a href="https://segmentfault.com/users/me">My Profile</a>

<%- await include('views/shared/footer.html'); %>

这包括页脚和页眉部分的内容,并向个人资料页面添加了链接。 users/me.html 文件的内容是:

<%- await include('views/shared/header.html', { title }); %>

<h1>My Profile</h1>

<ul>

<% for(var p in user){ %>

<li><span><%= p %>: </span><%= user[p] %></li>

<% } %>

</ul>

<%- await include('views/shared/footer.html'); %>

同样,此页面包含页眉和页脚,并循环遍历 user 对象的属性。当然这不是一个漂亮的个人资料页面,但是它能够使你知道身份验证步骤是否全部有效。

用 Okta 添加身份验证

如果你还没有Okta帐户,可以在此处获得免费的开发人员帐户。登录 Okta 后进入仪表板。你需要创建一个 Okta 应用,以利用 Okta 作为项目的身份提供者。

单击菜单中的 Applications,然后单击 Add Application。这将带你进入应用程序向导。选择 Web 作为你的平台,然后单击 Next。下一页是 Application Settings 页面。为你的应用程序命名(我命名为 DenoExample)。将所有 URL 更改为使用端口 3000 而不是 8080,然后将 Login Redirect URIs 更改为 http://localhost:3000/auth/callback。最后,单击 Done 在 Okta 中创建应用程序。

进入新创建的应用程序页面后,确保你位于 General Settings 选项卡上并滚动到底部,直到看到 Client Credentials 部分。我们先暂时使用这些值,所以不要关闭这个窗口。

回到你的应用程序中,在程序的根目录中创建一个名为 .env 的新文件。该文件的内容将是:

issuer=https://{yourOktaOrgUrl}/oauth2/default

clientId={yourClientID}

clientSecret={yourClientSecret}

redirectUrl=http://localhost:3000/auth/callback

state=SuPeR-lOnG-sEcReT

从 Okta 应用程序的 Client Credentials 部分复制客户端 ID 和客户端密钥。然后返回到信息中心,从菜单下方的右侧复制你的 Okta org URL。

现在你可以开始用 Okta 进行身份验证了。不幸的是你必须手动创建它。不过这是一个很棒的练习,可以帮助你了解 OAuth 和 OIDC 的工作方式。在程序的根文件夹中,创建一个名为 middleware 的新文件夹,并添加一个名为 authmiddleware.ts 的文件。然后添加以下内容:

import { config } from 'https://deno.land/x/dotenv/mod.ts';

export const ensureAuthenticated = async (req:any, res:any, next:any) => {

const user = req.app.locals.user;

if(!user){

const reqUrl = req.originalUrl;

const {issuer, clientId, redirectUrl, state} = config();

const authUrl = `${issuer}/v1/authorize?client_id=${clientId}&response_type=code&scope=openid%20email%20profile&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}:${reqUrl}`;

res.location(authUrl).sendStatus(302);

}

next();

}

首先,导入一个用于读取 .env 文件的库 dotenv。然后实现 ensureAuthenticated() 中间件,该中间件将启动身份验证过程的第一步。它首先检用户是否登录。如果已登录,则它只调用 next(),因为无事可做。

如果没有当前登录的用户,它将从 .env 文件构建一个由 Issuer,clientId,redirectUrl 和 state 属性组成的 URL。它调用发行者 URL 的 /v1/authorize 端点。然后重定向到该 URL。这是 Okta 托管的登录页面。有点像当你重定向到 Google 并用其作为身份提供者登录的机制。登录完成后将要调用的 URL 是 .env 文件中的 URL http://localhost:3000/auth/callback 。我还标记了用户重定向到 state 查询参数时要使用的原始 URL。一旦他们登录,这将会很容易把他们直接引导回去。

接下来,你将需要实现 auth/callback 路由来处理登录页面的结果,并交换将从 Okta 收到的授权代码。在 controllers 文件夹中创建一个名为 authcontroller.ts 的文件,其内容如下:

import { Router } from 'https://deno.land/x/opine@0.12.0/mod.ts';

import { config } from "https://deno.land/x/dotenv/mod.ts";

const auth = new Router();

// users routes

auth.get('/callback', async (req, res) => {

const { issuer, clientId, clientSecret, redirectUrl, state } = config();

if (req.query.state.split(':')[0] !== state) {

res.send('State code does not match.').sendStatus(400);

}

const tokenUrl: string = `${issuer}/v1/token`;

const code: string = req.query.code;

const headers = new Headers();

headers.append('Accept', 'application/json');

headers.append('Authorization', `Basic ${btoa(clientId + ':' + clientSecret)}`);

headers.append('Content-Type', 'application/x-www-form-urlencoded');

const response = await fetch(tokenUrl, {

method: 'POST',

headers: headers,

body: `grant_type=authorization_code&redirect_uri=${encodeURIComponent(redirectUrl)}&code=${code}`

});

const data = await response.json();

if (response.status !== 200) {

res.send(data);

}

const user = parseJwt(data.id_token);

req.app.locals.user = user;

req.app.locals.isAuthenticated = true;

res.location(req.query.state.split(':')_[_1] || '/').sendStatus(302);

});

function parseJwt (token:string) {

const base64Url = token.split('.')_[_1];

const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');

const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {

return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);

}).join(''));

return JSON.parse(jsonPayload);

};

export default auth;

实际上,这里发生的事比你想象的要少得多。首先从 Opine 引入 Router,然后再次读取 .env 文件。接着他们像在 usercontroller.ts 文件中一样实例化路由器。接下来是解构 config 对象,能够更易于使用它的值。接下来,我检查了状态查询参数以确保其匹配。这有助于确保 Okta 是发送授权码的人。然后用 req.query.code 从查询字符串中提取授权码。

接下来是对 token 端点的调用。你将在 POST 请求中将授权码发送给 Okta,以交换 ID Token。因此,这里我为请求构建了一些标头。最重要的是 Authorization 标头,其值为 Basic {yourClientId}:{yourClientSecret},客户端 ID 和密码是 base64 编码的。然后,使用这些标头和带有 authorization_codegrant_type(与以前相同的重定向 URL)的主体,以及带有我刚从 Okta 收到的授权代码的 Token 端点,对 Token 端点进行 POST 调用。

fetch() 调用返回一个用 then() 函数解析的 promise。我得到 response 对象的JSON值,为了确保调用成功,用下面的 parseJwt() 函数解析 id_token 值并将其粘贴到名为 user 的局部变量中。最后在重定向到身份验证之前,将用户发送到他们最初请求的 URL。

运行 Deno 程序

现在用以下命令从终端再次运行该程序:

deno run -A index.ts

一旦运行,你将能够单击主页上的配置文件链接,并将其重定向到 Okta 的托管登录页面。登录后,将会直接回到个人资料页面,你会看到 ID Token 的属性显示在列表中。

173382ede7319973.gif


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章

以上是 构建一个带身份验证的 Deno 应用 的全部内容, 来源链接: utcz.com/a/38490.html

回到顶部