JavaScript >> Javascript 文檔 >  >> Node.js

如何使用 Express 在 Node.js 中實現安全的 HTTPOnly Cookie

使用 Express.js,了解如何在瀏覽器中實現安全的 cookie,以避免 XSS(跨站點腳本)攻擊、中間人攻擊和 XST(跨站點跟踪)攻擊。

Cookie 是一種在用戶瀏覽器和您的服務器之間共享數據的巧妙技術。 cookie 中包含的數據可以是您想要的任何數據:登錄令牌、一些個人資料數據,甚至是一些解釋用戶如何使用您的應用程序的行為數據。從開發人員的角度來看,這很好,但如果您不了解常見的安全問題,使用 cookie 可能意味著意外地將數據洩露給攻擊者。

好消息:如果您了解在應用程序中保護 cookie 所需的技術,那麼您需要做的工作並不太難。我們需要防範三種類型的攻擊:

  1. 跨站腳本攻擊 (XSS) - 這些攻擊依賴於將客戶端 JavaScript 注入應用程序的前端,然後通過瀏覽器的 JavaScript cookie API 訪問 cookie。
  2. 中間人攻擊 - 這些攻擊發生在請求進行中(從瀏覽器傳送到服務器)並且服務器沒有 有 HTTPS 連接(無 SSL)。
  3. 跨站追踪攻擊 (XST) - 在 HTTP 協議中,一個名為 TRACE 的 HTTP 方法 存在允許攻擊者在繞過任何安全性的同時向服務器發送請求(並獲取其 cookie)。雖然現代瀏覽器通常會因為禁用 TRACE 而使這無關緊要 方法,注意並防範以增加安全性仍然很好。

首先,我們將查看服務器設置,我們將在其中創建 cookie,然後將其傳送回瀏覽器。

創建安全 cookie

為了給我們的示例提供上下文,我們將使用 CheatCode Node.js 樣板,它為我們設置了一個已經設置好並準備好進行開發的 Express 服務器。首先,將樣板副本克隆到您的計算機:

git clone https://github.com/cheatcode/nodejs-server-boilerplate.git

接下來,確保安裝樣板的依賴項:

cd nodejs-server-boilerplate && npm install

之後,繼續啟動服務器:

npm run dev

接下來,讓我們打開/api/index.js 項目中的文件。我們將添加一個測試路由,我們將在其中設置 cookie 並驗證它們是否正常工作:

/api/index.js

import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  // Our cookie code will go here.
};

接下來,讓我們添加用於設置 cookie 的代碼,然後介紹如何以及為什麼 它正在工作:

/api/index.js

import dayjs from "dayjs";
import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use("/cookies", (req, res) => {
    const dataToSecure = {
      dataToSecure: "This is the secret data in the cookie.",
    };

    res.cookie("secureCookie", JSON.stringify(dataToSecure), {
      secure: process.env.NODE_ENV !== "development",
      httpOnly: true,
      expires: dayjs().add(30, "days").toDate(),
    });

    res.send("Hello.");
  });
};

添加了很多細節,所以讓我們逐步完成它。首先,在文件的頂部,我們為 dayjs 添加了一個導入 NPM 包。這是一個用於在 JavaScript 中創建和操作日期的庫。我們將在下面使用它來為我們的 cookie 生成過期日期,以確保它不會無限期地在瀏覽器中徘徊。

接下來,我們使用 Express app 實例(通過 /index.js 傳入此文件 項目根目錄下的文件)調用.use() 方法允許我們在 Express 應用程序中定義路由。需要明確的是,這純粹是舉例。在您自己的應用中,這可以是您想要設置 cookie 並將其返回給瀏覽器的任何路由。

/cookies 的回調內部 路線,我們開始設置我們的cookie。首先,我們定義一個例子dataToSecure 裡面有一些測試數據的對象。

接下來,我們設置我們的cookie。使用 res.cookie() Express中提供的方法,我們傳遞三個參數:

  1. 我們要在瀏覽器上設置的cookie的名稱(這裡是secureCookie , 但這可以是任何你想要的,例如,pizza )。
  2. 我們要發送的數據的字符串化版本。在這裡,我們使用我們的 dataToSecure 對象並使用 JSON.stringify() 對其進行字符串化 .請記住:如果您發送回瀏覽器的數據已經是一個字符串,那麼您不要 需要這樣做。
  3. cookie 的設置。此處設置的屬性(secure , httpOnly , 和 expires ) 是特定於 Express 的屬性,但名稱與 HTTP 規範中的實際設置 1:1 映射。

關注最後一個參數,設置,這就是我們的安全性所在。有三個設置對於保護 cookie 很重要:

一、secure 屬性採用布爾值 (true/false),指定是否只能通過 SSL 或 HTTPS 連接檢索此 cookie。在這裡,我們根據我們的應用程序運行在哪個環境來設置。只要環境是不是 開發,我們想強制它為 true .在開發過程中,這不是必需的,因為我們的應用程序沒有暴露給互聯網,只有我們自己,而且您可能沒有在本地設置 SSL 代理服務器來處理這些請求。

二、httpOnly 屬性同樣採用布爾值(真/假),此處指定是否應通過瀏覽器中的 JavaScript 訪問 cookie。此設置強制為 true ,因為它確保任何跨站點腳本攻擊 (XSS) 都是不可能的。我們不必擔心這裡的開發環境,因為這個設置沒有 依賴於 SSL 或任何其他瀏覽器功能。

第三,也是最後,expires 屬性允許我們在 cookie 上設置過期日期。這通過確保我們的 cookie 來幫助我們提高安全性 無限期地停留在用戶的瀏覽器中。根據您存儲在 cookie 中的數據(以及您的應用程序的需要),您可能希望縮短或擴展它。在這裡,我們使用 dayjs 我們之前導入的庫,告訴它“獲取當前日期,添加 30 天,然後返回一個 JavaScript Date 該日期的對象。”換句話說,此 cookie 將在創建之日起 30 天內過期。

最後,在路由回調函數的底部,我們調用 res.send() 回應我們的要求。因為我們使用的是 res.cookie() 我們會自動告訴 Express 將 cookie 作為響應的一部分發回——無需執行任何其他操作。

處理 TRACE 請求

就像我們之前提到的,在我們檢查我們的 cookie 是否按預期工作之前,我們要確保我們已經阻止了 TRACE 的可能性 要求。我們需要這樣做以確保攻擊者無法利用 TRACE 訪問我們的 httpOnly 的 HTTP 方法 餅乾(TRACE 不尊重這條規則)。為此,我們將依賴一個自定義的 Express 中間件,它會自動阻止 TRACE 來自任何客戶端(瀏覽器或其他)的請求。

/middleware/requestMethod.js

export default (req, res, next) => {
  // NOTE: Exclude TRACE and TRACK methods to avoid XST attacks.
  const allowedMethods = [
    "OPTIONS",
    "HEAD",
    "CONNECT",
    "GET",
    "POST",
    "PUT",
    "DELETE",
    "PATCH",
  ];

  if (!allowedMethods.includes(req.method)) {
    res.status(405).send(`${req.method} not allowed.`);
  }

  next();
};

方便的是,上述代碼作為 CheatCode Node.js 樣板的一部分存在,並且已經設置為在 /middleware/index.js 內部運行 .為了解釋這裡發生了什麼,我們正在做的是導出一個預期 Express req 的函數 對象,res 對象和 next 方法作為參數。

接下來,我們定義一個數組,為我們的服務器指定所有允許的 HTTP 方法。注意這個數組沒有 包括 TRACE 方法。為了使用它,我們運行檢查看看這個 allowedMethods 數組包含當前的 req uest的方法。如果是不是 ,我們想用 HTTP 405 響應碼(“HTTP method not allowed”的技術代碼)進行響應。

假設 req.method allowedMethods 數組,我們調用 next() Express 傳遞的方法,該方法向 Express 發出信號以繼續通過其他中間件向前移動請求。

如果你想看看這個中間件在使用中,從 /index.js 開始 文件查看middleware()如何 方法被導入並調用(通過 Express app 實例),然後打開 /middleware/index.js 文件以查看 /middleware/requestMethods.js 如何 文件被導入並使用。

驗證瀏覽器中的安全 cookie

現在,我們應該準備好測試我們的 cookie。因為我們在 /cookies 路徑上設置了 cookie ,我們需要在瀏覽器中訪問這條路由來驗證一切是否正常。在網絡瀏覽器中,打開 http://localhost:5001/cookies 然後打開瀏覽器的控制台(通常通過 CTRL + click 訪問 在 MacOS 上或通過右鍵單擊 Windows):

在此示例中,我們使用的是 Brave 瀏覽器,它具有與 Google Chrome 相同的開發人員檢查工具(Firefox 和 Safari 具有可比較的 UI,但可能不會使用我們在下面引用的完全相同的命名)。在這裡,我們可以看到我們的 secureCookie 以及我們在服務器上傳遞的所有數據和設置。為了清楚起見,請注意這裡,因為我們在 development 環境,Secure 未設置。

我們在此處省略的附加設置 SameSite 也被禁用(默認值為 Lax ) 在瀏覽器中。 SameSite 是另一個布爾值(真/假),它決定我們的 cookie 是否只能在同一個域上訪問。這是禁用的,因為如果您在應用程序中使用單獨的前端和後端,它會增加混亂(如果您的應用程序使用 CheatCode 的 Next.js 和 Node.js 樣板,這將是真的)。如果要啟用此功能,可以通過添加 sameSite: true 到我們傳遞給 res.cookie() 的選項對象 作為第三個參數。

在服務器上檢索 cookie

現在我們已經驗證了我們的 cookie 存在於瀏覽器中,接下來,讓我們看看檢索它們以供稍後使用。為此,我們需要確保我們的 Express 服務器正在解析 餅乾。這意味著將請求的 HTTP 標頭中發送的 cookie 字符串轉換為更易於訪問的 JavaScript 對象。

為了自動化這個,我們可以添加 cookie-parser 打包到我們的應用程序中,這使我們能夠訪問為我們解析它的 Express 中間件:

npm i cookie-parser

實現這一點很簡單。從技術上講,這已經在我們用於示例的 CheatCode Node.js 樣板中使用,在 middleware/index.js 中 應用根目錄下的文件:

/middleware/index.js

[...]
import cookieParser from "cookie-parser";
[...]

export default (app) => {
  [...]
  app.use(cookieParser());
};

在這裡,我們需要做的就是導入 cookieParser 來自 cookie-parser 打包然後調用 app.use() 將調用傳遞給 cookieParser() app.use(cookieParser()) 之類的方法 .為了將其與我們上面的示例聯繫起來,這裡是對我們的 /api/index.js 的更新 文件(假設您是從頭開始編寫代碼):

/api/index.js

import dayjs from "dayjs";
import cookieParser from "cookie-parser";
import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use(cookieParser());

  app.use("/cookies", (req, res) => {
    const dataToSecure = {
      dataToSecure: "This is the secret data in the cookie.",
    };

    res.cookie("secureCookie", JSON.stringify(dataToSecure), {
      secure: process.env.NODE_ENV !== "development",
      httpOnly: true,
      expires: dayjs().add(30, "days").toDate(),
    });

    res.send("Hello.");
  });
};

同樣,如果您使用的是 CheatCode Node.js Boilerplate,則不需要這樣做。

有了這個實現,現在,每當應用程序收到來自瀏覽器的請求時,它的 cookie 將被解析並放置在 reqreq.cookies 處的請求對象 作為一個 JavaScript 對象。在請求內部,我們可以這樣做:

/api/index.js

import dayjs from "dayjs";
import cookieParser from "cookie-parser";
import graphql from "./graphql/server";

export default (app) => {
  graphql(app);

  app.use(cookieParser());

  app.use("/cookies", (req, res) => {
    if (!req.cookies || !req.cookies.secureCookie) {
      const dataToSecure = {
        dataToSecure: "This is the secret data in the cookie.",
      };

      res.cookie("secureCookie", JSON.stringify(dataToSecure), {
        secure: process.env.NODE_ENV !== "development",
        httpOnly: true,
        expires: dayjs().add(30, "days").toDate(),
      });
    }

    res.send("Hello.");
  });
};

在這裡,在設置之前示例中的 cookie 之前,我們調用 req.cookies (通過 cookieParser() 自動為我們添加 中間件),檢查是否有 req.cookies 值未定義,或者,如果 req.cookies 定義,是 req.cookies.secureCookie 也定義了。如果 req.cookies.secureCookie 不是 定義,我們想繼續設置我們的cookie正常。如果已經定義好了,我們就正常響應請求,跳過設置cookie。

這裡的重點是我們可以通過 req.cookies 訪問我們的 cookie 在 Express 中的財產。除非您願意,否則您不必對自己的 cookie 進行上述檢查。

如何在 GraphQL 中管理 cookie

要結束管理 cookie 的循環,有必要了解如何在 GraphQL 服務器上執行此操作。如果您想從 GraphQL 解析器或在服務器實例化期間設置或檢索 cookie,這一點值得了解。

/api/graphql/server.js

import { ApolloServer } from "apollo-server-express";
import schema from "./schema";
import { isDevelopment } from "../../.app/environment";
import { configuration as corsConfiguration } from "../../middleware/cors";

export default (app) => {
  const server = new ApolloServer({
    ...schema,
    introspection: isDevelopment,
    playground: isDevelopment,
    context: async ({ req, res }) => {
      const context = {
        req,
        res,
        user: {},
      };

      return context;
    },
  });

  server.applyMiddleware({
    cors: corsConfiguration,
    app,
    path: "/api/graphql",
  });
};

在這裡,為了確保我們可以通過 GraphQL 查詢和變異解析器訪問和設置 cookie,我們設置了 context 服務器的屬性等於接受 req 的函數 和 res (在這裡,因為我們將它綁定到 Express app 例如,這些是 Express reqres 對象),然後將它們分配回 context 傳遞給我們所有查詢和變異解析器的對象:

import dayjs from 'dayjs';

export default {
  exampleResolver: (parent, args, context) => {
    // Accessing an existing cookie from context.req.
    const cookie = context?.req?.cookies?.secureCookie;

    // Setting a new cookie with context.res.
    if (context.res && !cookie) {
      const dataToSecure = {
        dataToSecure: "This is the secret data in the cookie.",
      };

      res.cookie("secureCookie", JSON.stringify(dataToSecure), {
        secure: process.env.NODE_ENV !== "development",
        httpOnly: true,
        expires: dayjs().add(30, "days").toDate(),
      });
    }

    // Arbitrary return value here. This would be whatever value you want to
    // resolve the query or mutation with.
    return cookie;
  },
};

在上面的示例中,我們重複了與教程前面相同的模式,但是,現在我們通過 context.req.cookies 訪問 cookie 並通過 context.res.cookie() 設置它們 .值得注意的是,這個 exampleResolver 並非旨在發揮作用——它只是一個如何從解析器中訪問和設置 cookie 的示例。您自己的 GraphQL 解析器將使用與在您的應用中讀取或寫入數據相關的更具體的代碼。

確保 Cookie 包含在您的 GraphQL 請求中

根據您選擇的 GraphQL 客戶端,來自瀏覽器(httpOnly 或其他)的 cookie 可能不會自動包含在請求中。為確保發生這種情況,您需要檢查客戶端的文檔,看看它是否具有包含憑據的選項/設置。例如,這裡是 CheatCode 的 Next.js 樣板中的 Apollo 客戶端配置:

new ApolloClient({
  credentials: "include",
  link: ApolloLink.from([
    new HttpLink({
      uri: settings.graphql.uri,
      credentials: "include",
    }),
  ]),
  cache: new InMemoryCache(),
  defaultOptions: {
    watchQuery: {
      errorPolicy: "all",
      fetchPolicy: "network-only",
    },
    query: {
      errorPolicy: "all",
      fetchPolicy: "network-only",
    },
    mutate: {
      errorPolicy: "all",
    },
  },
});

在這裡,我們確保設置 credentials 屬性為 'include' 向 Apollo 發出信號,我們希望它在每個請求中包含我們的 cookie。此外,由於我們使用的是 Apollo 的 HTTP Link 方法,因此我們設置了 credentials'include' 這裡也是。

總結

在本教程中,我們了解瞭如何使用 Express 在 Node.js 中管理安全 cookie。我們學習瞭如何使用 secure 定義 cookie , httpOnly , 和 expires 值以確保它們與攻擊者分開以及如何禁用 TRACE 請求防止後門訪問我們的 httpOnly 餅乾。

我們還學習瞭如何利用 Express cookie-parser 訪問 cookie 中間件,學習如何在 Express 路由中以及通過 GraphQL 上下文訪問 cookie。


Tutorial JavaScript 教程
  1. 在控制台上變得時髦 - 提升幽默感😅

  2. CodeGuppy.com:面向年輕程序員的 JavaScript 環境

  3. CORS 是副項目的痛苦

  4. 湯姆和傑瑞的 JavaScript 範圍基礎知識

  5. 修復在 JSX 代碼中看到“0”的問題

  6. TypeScript 中的 Mixin 類

  7. 在 React 組件中使用 TypeScript 接口

  1. 關於 Vue.js 中的環境變量你需要知道的一切

  2. 如何在引導模式上使用點擊事件

  3. 第 81/100 天變量

  4. 如果你最近被解雇了,我想提供免費的投資組合/簡歷評論/模擬面試

  5. 在本地使用 base-href 測試角度構建

  6. 你不需要 CSS-in-JS:為什麼(以及何時)我使用樣式表代替

  7. 如何第一次正確配置 Visual Studio Code

  1. Javascript中的函數式編程原則

  2. 我建立了一個網站截圖API,好奇你們的想法!

  3. 將 React 應用程序連接到 firebase

  4. 使用 Node Js 構建一個簡單的發票生成器