关于前端:构建一个带身份验证的-Deno-应用

34次阅读

共计 7761 个字符,预计需要花费 20 分钟才能阅读完成。

作者:Lee Brandt

翻译:疯狂的技术宅

原文:https://scotch.io/tutorials/b…

未经容许严禁转载

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="/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><strong><%= p %>: </strong><%= 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 的属性显示在列表中。


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

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

欢送持续浏览本专栏其它高赞文章:

  • 深刻了解 Shadow DOM v1
  • 一步步教你用 WebVR 实现虚拟现实游戏
  • 13 个帮你进步开发效率的古代 CSS 框架
  • 疾速上手 BootstrapVue
  • JavaScript 引擎是如何工作的?从调用栈到 Promise 你须要晓得的所有
  • WebSocket 实战:在 Node 和 React 之间进行实时通信
  • 对于 Git 的 20 个面试题
  • 深刻解析 Node.js 的 console.log
  • Node.js 到底是什么?
  • 30 分钟用 Node.js 构建一个 API 服务器
  • Javascript 的对象拷贝
  • 程序员 30 岁前月薪达不到 30K,该何去何从
  • 14 个最好的 JavaScript 数据可视化库
  • 8 个给前端的顶级 VS Code 扩大插件
  • Node.js 多线程齐全指南
  • 把 HTML 转成 PDF 的 4 个计划及实现

  • 更多文章 …

正文完
 0