跳转至

Express

Authany 让您能够轻松地为不依赖任何框架的普通 Web 应用添加用户身份验证。您可以通过将 Authany 作为 OIDC 身份提供方接入应用来实现这一点。

本文将介绍如何使用 Authany 为 Express.js 应用添加用户身份验证。

您将学到什么

  • 如何创建一个 Authany 应用。
  • 如何使用 Authany 为 Express 应用添加用户登录功能。
  • 如何从 Authany 请求用户信息。

前提条件

跟随本教程操作,您需要准备好以下内容:

  • 已安装 Node.js
  • 一个免费的 Authany 账号。可以在这里注册。

按照本指南操作,您可以在 🕐 15 分钟内为 Express 应用添加基于 Authany 的用户身份验证。

说明

查看并克隆 GitHub 上的示例项目。

在 Authany 中设置应用

登录您的 Authany 账号(如果还没有账号,可以在这里注册)以执行本节中的步骤。

在管理台中创建应用

在这一步,我们将创建 Authany 客户端应用,后续将用它把 Authany 接入 Express 应用。

登录 Authany 管理台后,选择一个项目,然后在左侧菜单栏中进入应用。

进入应用

接下来,点击顶部工具栏中的 ⊕新增应用 按钮,打开新增应用页面。

输入应用名称(例如:"My App"),并选择 OIDC/SAML Client Application 作为应用类型。点击保存以创建应用。

新增应用表单,OIDC 应用

在下一个页面中,您会看到不同框架的教程链接。点击下一步跳转到应用配置页面。

说明

请记下新应用的 Client ID 和 Client Secret,后续配置 Express 应用时会用到。

配置授权重定向 URI

授权重定向 URI 应该是您 Express 应用中的一个页面,该页面会向 Authany 令牌端点发起 HTTP(S) 请求,用授权码换取访问令牌。

在本指南的示例 Express 应用中,这个 URL 是 http://localhost:3000/auth-redirect。因此,请在 Authany 管理台的应用配置页面中滚动到 URI 部分,将 http://localhost:3000/auth-redirect 添加为授权重定向 URI。

在 Authany 中设置重定向 URI

点击保存以保留您的更改。

如何使用 Authany 为 Express.js 应用添加用户身份验证

在本节中,我们将构建一个包含以下功能的简单 Express 应用:

  • 一个带有登录链接的首页,点击后跳转到登录路由。
  • 一个 /start-login 路由,用于启动身份验证流程。
  • 用授权码从 Authany 换取访问令牌的逻辑。
  • 一个使用该访问令牌从 Authany 获取用户信息的页面。
  • 一个用于结束当前用户会话的登出按钮。
  • 刷新访问令牌。

第 1 步:创建 Express 应用

现在来创建将要连接 Authany 客户端应用的 Express 应用。运行以下命令,新建一个项目文件夹并将其设为当前工作目录:

# create project directory
mkdir my-express-app
#set project directory as current working directory
cd my-express-app

安装基础项目依赖

接下来,安装 Express npm 包以及 axios、nodemon、session、dotenv 等其他依赖包。

示例应用中会使用 axios 库发起 HTTP 请求,也会使用 nodemon 为开发环境添加热重载。请在 Express 项目根目录下运行以下命令,安装 Express 及其他依赖:

# Generate package.json
npm init -y
# Install Express 
npm install express
# Install axios
npm install axios
# Install express-session
npm install express-session
# Install dotenv
npm install dotenv
# Install Nodemon (Used to add hot road to JavaScript development)
npm install -D nodemon

创建 app.js 文件

安装好 Express 和其他依赖后,在项目根目录下新建一个 app.js 文件,并添加以下代码:

//app.js
const express = require('express');
const axios = require("axios");
const session = require("express-session");
require("dotenv").config();

const app = express();
const port = process.env.PORT || 3000;
app.use(express.urlencoded({ extended: true }));
app.use(
  session({
    secret: "your_strong_secret_key", // Replace with a strong, randomly generated secret
    cookie: {},
  })
);

app.get("/", async (req, res) => {
    res.send(`
        <div style="max-width: 650px; margin: 16px auto; background-color: #EDEDED; padding: 16px;">
          <p>Hi there!</p>
          <p>This demo app shows you how to add user authentication to your Express app using Authgear</p>
            <p>Checkout <a href="https://docs.authany.com/">docs.authgear.com</a> to learn more about adding Authgear to your apps.</p>
          <a href="/login">Login</a>
        </div>
      `);
  });

app.get("/login", async(req, res) => {
    // TODO add implementation here
});

app.get("/auth-redirect", async(req, res) => {
    // TODO add implementation here
});

app.listen(port, () => {
    console.log(`server started on port ${port}!`);
});

更新 package.json

在 Express 项目根目录的 package.json 文件的 scripts 部分添加以下代码:

"start": "node app.js",
"dev": "nodemon app.js"

检查点

现在运行 npm run dev 命令,在浏览器中访问 localhost:3000,您应该会看到如下页面:

Express 示例应用首页

第 2 步:实现登录路由

这一步我们将在 Express 应用中实现登录路由。该路由会将用户浏览器重定向到登录页面(AuthUI),从而启动身份验证流程。具体做法是将用户重定向到您 Authany 项目/客户端应用的授权 URL(https://<AUTHGEAR_ENDPOINT>/oauth2/authorize )。

将 app.get("/login", ...) 路由的代码更新为以下内容:

app.get("/login", async(req, res) => {
    const scopes = "openid offline_access";
    const authorizedUrl = new URL("/oauth2/authorize", process.env.AUTHGEAR_ENDPOINT);
    authorizedUrl.searchParams.set('client_id', process.env.AUTHGEAR_CLIENT_ID);
    authorizedUrl.searchParams.set('redirect_uri', process.env.AUTHGEAR_REDIRECT_URL);
    authorizedUrl.searchParams.set('response_type', 'code');
    authorizedUrl.searchParams.set('scope', scopes);
    res.redirect(authorizedUrl);
});

在项目根目录下创建一个 .env 文件,并使用以下字段添加您的 Authany 客户端应用配置(Client ID、Secret、端点):

AUTHGEAR_CLIENT_ID=<CLIENT_ID>
AUTHGEAR_CLIENT_SECRET=<CLIENT_SECRET>
AUTHGEAR_ENDPOINT=<AUTHGEAR_ENDPOINT>
AUTHGEAR_REDIRECT_URL=http://localhost:3000/auth-redirect

Client ID 和 Client Secret 的值可以在您之前创建客户端应用时的配置页面中获取,参见在管理台中创建应用。如果您使用的是 Authany Cloud 版本,Authany 端点的格式类似 https://project_id.authgear.cloud。

现在保存代码并重启应用,点击登录链接应该会跳转到 Authany 授权页面。

AuthUI 登录页面

登录成功后,授权码会被发送回您的 Express 应用。

第 3 步:用授权码换取访问令牌

用户登录并在登录页面授权您的应用后,会被重定向回您之前指定的重定向 URL。在重定向的同时,授权码会通过 URL 查询参数 code 一并发送过来。

在这一步,我们将用授权码换取访问令牌,供用户后续访问受保护的资源。

将 app.get("/auth-redirect", ...) 路由的代码更新为以下内容:

app.get("/auth-redirect", async(req, res) => {
        if (req.query.code != null) {
            const data = {
              client_id: process.env.AUTHGEAR_CLIENT_ID,
              client_secret: process.env.AUTHGEAR_CLIENT_SECRET,
              code: req.query.code,
              grant_type: 'authorization_code',
              response_type: 'code',
              redirect_uri: process.env.AUTHGEAR_REDIRECT_URL
            };

            try {
              const tokenUrl = new URL("/oauth2/token", process.env.AUTHGEAR_ENDPOINT);
              const getToken = await axios.post(tokenUrl, data, {
                headers: { "Content-Type": "application/x-www-form-urlencoded" }
              });

              const accessToken = getToken.data.access_token;

              req.session.access_token = accessToken;
              res.redirect("/");

            } catch (error) {
              res.send("An error occurred! Login could not complete. Error data: " + error);
            }
          } else {
            res.send("No Authorization code in URL");
          }

});

以上代码向令牌端点(https://<AUTHGEAR_ENDPOINT>/oauth2/token)发送了一个 HTTP POST 请求。上一步获取的授权码会连同其他客户端凭据一起放在 HTTP 请求体中发送。请求头应包含 "Content-Type": "application/x-www-form-urlencoded"

HTTP(S) 请求的响应中会返回一个有效的访问令牌,即 response.data.access_token。在上面的示例代码中,我们使用 express-session 临时保存了这个访问令牌,以便用户被重定向后可以在 "/" 路由中访问它。

现在我们可以使用这个访问令牌向应用中受保护的资源发起认证请求,或者调用 Authany 的用户信息端点。下一步,我们将演示如何使用访问令牌从 Authany 获取当前用户的信息。

第 4 步:获取用户信息

Authany 提供了一个端点,供您的应用请求用户信息(https://<AUTHGEAR_ENDPOINT>/oauth2/userinfo)。该端点会返回用户在 Authany 中的详细信息,例如邮箱地址、性别、全名等。

要在示例应用中获取用户信息,请更新 app.js 中的 app.get("/", ...),使用以下条件语句:当当前会话已设置有效的访问令牌时,渲染不同的代码块:

app.get("/", async (req, res) => {
  if (req.session.access_token != null) {
    const accessToken = req.session.access_token;
    //Now use access token to get user info.
  try {
        const userInfoUrl = new URL(
            "/oauth2/userinfo",
            process.env.AUTHGEAR_ENDPOINT
          );
          const getUserInfo = await axios.get(userInfoUrl, {
            headers: { Authorization: "Bearer " + accessToken },
          });
          const userInfo = getUserInfo.data;
          res.send(`
              <div style="max-width: 650px; margin: 16px auto; background-color: #EDEDED; padding: 16px;">
                <p>Welcome ${userInfo.email}</p>
                <p>User Info:</p>
                <div>
                  <pre>${JSON.stringify(userInfo, null, 2)}</pre>
                </div>
                  <p> 
                      <a href="/logout">Logout</a>
                  </p>
              </div>
          `);
    }
    catch (error) {
        res.send("Unable to get User Info: " + error);
    }
  } else {
    res.send(`
            <div style="max-width: 650px; margin: 16px auto; background-color: #EDEDED; padding: 16px;">
              <p>Hi there!</p>
              <p>This demo app shows you how to add user authentication to your Express app using Authgear</p>
                <p>Checkout <a href="https://docs.authany.com/">docs.authgear.com</a> to learn more about adding Authgear to your apps.</p>
              <a href="/login">Login</a>
            </div>
          `);
  }
});

这里我们的应用发起了另一个 HTTP(S) 请求,这次请求的是用户信息端点,请求方式为 GET。访问令牌以 Bearer 授权头的形式发送。

此时,保存所有更改并重启应用。重新完整地登录一次,如果授权头中的访问令牌有效,最后您应该会看到 "Welcome [your email address]" 以及 UserInfo 端点返回的响应内容。

Express 示例

第 5 步:登出

要让用户能够登出,请在 Express 应用中添加一个新的 /logout 路由。在该路由中,调用 express-session 的 destroy() 方法从会话中删除访问令牌,然后将用户重定向到 Authany 的登出端点,以结束用户在 Authany 上的会话。

为了让用户登出后能跳转回您的 Express 应用,请在 Authany 管理台的客户端应用中添加 http://localhost:3000 作为登出后重定向 URI。

设置登出后重定向 URI

登出路由的实现大致如下:

app.get("/logout", async (req, res) => {
  const accessToken = req.session.access_token;
  const endSessionUrl = new URL(
    "/oauth2/end_session",
    process.env.AUTHGEAR_ENDPOINT
  );
  endSessionUrl.searchParams.set("post_logout_redirect_uri", "http://localhost:3000");

  // Remove access token, and refresh token from express-session
  req.session.destroy();

  res.set("Authorization", "Bearer " + accessToken);
  res.redirect(endSessionUrl);
});

最后,在 </prev> 标签下方添加一个指向登出路由的链接,代码如下:

<a href="/logout">Logout</a>

第 6 步:刷新访问令牌

访问令牌通常只在较短的时间内有效。不过,您可以使用刷新令牌——同样包含在第 3 步令牌端点的响应中——在访问令牌过期后申请新的访问令牌。

说明

请注意,您必须在授权请求的 scope 参数中包含 offline_access,才能从 Authany 获取刷新令牌。

我们建议您在把访问令牌放入授权头去访问诸如用户信息端点这类受保护资源之前,先检查该访问令牌是否仍然有效。

要为我们的 Express 应用添加刷新过期访问令牌的能力,请在 app.js 中添加以下函数:

const refreshAccessTokenIfExpired = async (refreshToken, expiresAt, req) => {
    const currentTime = (new Date(Date.now()).getTime());
    if (expiresAt<currentTime) {
        const tokenUrl = new URL("/oauth2/token", process.env.AUTHGEAR_ENDPOINT);
        const data = {
            client_id: process.env.AUTHGEAR_CLIENT_ID,
            client_secret: process.env.AUTHGEAR_CLIENT_SECRET,
            grant_type: "refresh_token",
            refresh_token: refreshToken
          };

          try {
            const getToken = await axios.post(tokenUrl, data, {
              headers: { "Content-Type": "application/x-www-form-urlencoded" },
            });

            const accessToken = getToken.data.access_token;
            const expiresAt = new Date(Date.now()).getTime() + getToken.data.expires_in * 1000;

            req.session.access_token = accessToken;
            req.session.expire_at = expiresAt;
            return accessToken;
          } catch (error) {
            throw new Error('Failed to refresh access token: ' + error);
          }
    } else {
        return req.session.access_token;
    }
};

接下来,在 app.get("/auth-redirect", ...) 路由中找到以下这行代码:

req.session.access_token = accessToken;

在上面这行代码之后,添加以下代码:

req.session.expire_at = new Date(Date.now()).getTime() + getToken.data.expires_in * 1000;
req.session.refresh_token = getToken.data.refresh_token;

以上代码会把第 3 步响应中返回的 refresh_token 和 expires_in 的值保存到 express-session 中。为了把 expire_in 转换为未来的一个时间点,我们将它乘以 1000,再加上当前时间。

最后,在 app.get("/", ...) 路由中找到以下这行代码:

const accessToken = req.session.access_token;

将上面这行代码替换为以下代码:

const accessToken = await refreshAccessTokenIfExpired(req.session.refresh_token, req.session.expire_at, req);

现在,我们的 Express 应用会先调用新的 refreshAccessTokenIfExpired() 方法来获取访问令牌。如果当前的访问令牌已过期,该方法会向 Authany 的令牌端点发起请求以获取新的访问令牌。这个请求的 grant_type 需要是 refresh_token,并且 refresh_token 需要包含在 POST 请求体中。

关于刷新访问令牌的更多内容,请参阅这里。

后续步骤:调用 API

要访问后端应用服务器上的受限资源,HTTP 请求应在其授权头中包含访问令牌。

例如:

axios.get("https://<your_backend_url>", {
        headers: { Authorization: "Bearer " + accessToken },
      });

结语

到这里,您已经成功使用 Authany 作为 OAuth 提供方,为您的 Express 应用添加了用户身份验证。

借助全新的 Authany 身份验证,您还可以为应用添加更多功能,例如用访问码保护您自己的应用端点。您也可以使用 express-session 和 Cookie 安全地存储访问令牌,以保持用户会话。

完整代码请见我们在 GitHub 上的示例代码。