Uỷ quyền và tính toàn vẹn

Khi xây dựng bất kỳ ứng dụng công khai nào, điều cực kỳ quan trọng là bạn phải bảo vệ dữ liệu lưu trữ trong hệ thống của mình. Khi nói đến các LLM, bạn cần phải thẩm định thêm để đảm bảo rằng mô hình này chỉ truy cập vào dữ liệu cần thiết, các lệnh gọi công cụ được xác định phạm vi phù hợp để người dùng gọi LLM và chỉ các ứng dụng khách đã xác minh mới gọi được quy trình này.

Firebase Genkit cung cấp các cơ chế để quản lý chính sách uỷ quyền và ngữ cảnh. Đối với các luồng chạy trên Cloud Functions cho Firebase, nhà phát triển phải cung cấp chính sách xác thực hoặc xác nhận rõ ràng việc thiếu chính sách này. Đối với các luồng không phải Hàm, bạn cũng có thể quản lý và thiết lập tính năng xác thực, nhưng yêu cầu tích hợp thủ công hơn một chút.

Uỷ quyền luồng cơ bản

Mọi flow đều có thể xác định authPolicy trong cấu hình. Chính sách xác thực là một chức năng giúp kiểm tra xem một số tiêu chí nhất định (do bạn xác định) có được đáp ứng hay không và gửi ra một trường hợp ngoại lệ nếu có bất kỳ kiểm thử nào không đạt. Nếu bạn đặt trường này, trường này sẽ được thực thi trước khi quy trình được gọi:

import { defineFlow, runFlow } from '@genkit-ai/flow';

export const selfSummaryFlow = defineFlow(
  {
    name: 'selfSummaryFlow',
    inputSchema: z.object({uid: z.string()}),
    outputSchema: z.string(),
    authPolicy: (auth, input) => {
      if (!auth) {
        throw new Error('Authorization required.');
      }
      if (input.uid !== auth.uid) {
        throw new Error('You may only summarize your own profile data.');
      }
    }
  },
  async (input) => { ... });

Khi thực thi luồng này, bạn phải cung cấp đối tượng xác thực bằng withLocalAuthContext, nếu không bạn sẽ gặp lỗi:

// Error: Authorization required.
await runFlow(selfSummaryFlow, { uid: 'abc-def' });

// Error: You may only summarize your own profile data.
await runFlow(
  selfSummaryFlow,
  { uid: 'abc-def' },
  {
    withLocalAuthContext: { uid: 'hij-klm' },
  }
);

// Success
await runFlow(
  selfSummaryFlow,
  { uid: 'abc-def' },
  {
    withLocalAuthContext: { uid: 'abc-def' },
  }
);

Khi chạy với giao diện người dùng Phát triển Genkit, bạn có thể chuyển đối tượng Xác thực bằng cách nhập JSON vào thẻ "Xác thực JSON": {"uid": "abc-def"}.

Bạn cũng có thể truy xuất ngữ cảnh xác thực cho flow bất cứ lúc nào trong flow bằng cách gọi getFlowAuth(), bao gồm cả các hàm do flow gọi:

import { getFlowAuth, defineFlow } from '@genkit-ai/flow';

async function readDatabase(uid: string) {
  if (getFlowAuth().admin) {
    // Do something special if the user is an admin:
    ...
  } else {
    // Otherwise, use the `uid` variable to retrieve the relevant document
    ...
  }
}

export const selfSummaryFlow = defineFlow(
  {
    name: 'selfSummaryFlow',
    inputSchema: z.object({uid: z.string()}),
    outputSchema: z.string(),
    authPolicy: ...
  },
  async (input) => {
    ...
    await readDatabase(input.uid);
  });

Khi kiểm thử quy trình bằng công cụ cho nhà phát triển Genkit, bạn có thể chỉ định đối tượng xác thực này trong giao diện người dùng hoặc trên dòng lệnh có cờ --auth:

genkit flow:run selfSummaryFlow '{"uid": "abc-def"}' --auth '{"uid": "abc-def"}'

Tích hợp Cloud Functions cho Firebase

Trình bổ trợ Firebase giúp tích hợp thuận tiện với tính năng Xác thực Firebase / Google Cloud Identity Platform cũng như tính năng hỗ trợ tích hợp sẵn của tính năng Kiểm tra ứng dụng Firebase.

Uỷ quyền

Trình bao bọc onFlow() do trình bổ trợ Firebase cung cấp hoạt động vốn có với SDK ứng dụng của Chức năng đám mây dành cho Firebase. Khi sử dụng SDK, tiêu đề Xác thực Firebase sẽ tự động được đưa vào, miễn là ứng dụng khách của bạn cũng đang sử dụng SDK xác thực Firebase. Bạn có thể sử dụng tính năng Xác thực Firebase để bảo vệ quy trình được xác định bằng onFlow():

import {firebaseAuth} from "@genkit-ai/firebase/auth";
import {onFlow} from "@genkit-ai/firebase/functions";

export const selfSummaryFlow = onFlow({
    name: "selfSummaryFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    authPolicy: firebaseAuth((user) => {
      if (!user.email_verified && !user.admin) {
        throw new Error("Email not verified");
      }
    }),
  }, (subject) => {...})

Khi sử dụng trình bổ trợ Xác thực Firebase, user sẽ được trả về dưới dạng DecodedIdToken. Bạn luôn có thể truy xuất đối tượng này bất cứ lúc nào thông qua getFlowAuth() như đã nêu ở trên. Khi chạy luồng này trong quá trình phát triển, bạn sẽ truyền đối tượng người dùng theo cách tương tự:

genkit flow:run selfSummaryFlow '{"uid": "abc-def"}' --auth '{"admin": true}'

Theo mặc định, trình bổ trợ Xác thực Firebase yêu cầu ứng dụng gửi tiêu đề xác thực. Tuy nhiên, trong trường hợp bạn muốn cho phép quyền truy cập chưa được xác thực với cách xử lý đặc biệt đối với người dùng đã xác thực (chẳng hạn như bán thêm các tính năng), thì bạn có thể định cấu hình chính sách như sau:

authPolicy: firebaseAuth((user) => {
  if (user && !user.email_verified) {
    throw new Error("Logged in users must have verified emails");
  }
}, {required: false}),

Bất cứ khi nào bạn cung cấp một Chức năng đám mây trên Internet, điều tối quan trọng là bạn phải sử dụng một loại cơ chế uỷ quyền nào đó để bảo vệ dữ liệu của mình và dữ liệu của khách hàng. Như vậy, đôi khi bạn cần triển khai Hàm đám mây mà không cần kiểm tra uỷ quyền dựa trên mã (ví dụ: Hàm của bạn không thể gọi trên toàn cầu mà được bảo vệ bằng Cloud IAM). Trường authPolicy luôn là trường bắt buộc khi sử dụng onFlow(), nhưng bạn có thể cho thư viện biết rằng bạn đang bỏ qua quá trình kiểm tra uỷ quyền bằng cách sử dụng hàm noAuth():

import {onFlow, noAuth} from "@genkit-ai/firebase/functions";

export const selfSummaryFlow = onFlow({
    name: "selfSummaryFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),
    // WARNING: Only do this if you have some other gatekeeping in place, like
    // Cloud IAM!
    authPolicy: noAuth(),
  }, (subject) => {...})

Tính toàn vẹn của ứng dụng

Việc tự xác thực sẽ giúp bảo vệ ứng dụng của bạn rất hiệu quả. Tuy nhiên, điều quan trọng là bạn phải đảm bảo rằng chỉ các ứng dụng khách mới gọi các chức năng của bạn. Trình bổ trợ Firebase cho genkit bao gồm tính năng hỗ trợ tốt nhất cho tính năng Kiểm tra ứng dụng Firebase. Bạn chỉ cần thêm các tuỳ chọn cấu hình sau vào onFlow():

import {onFlow} from "@genkit-ai/firebase/functions";

export const selfSummaryFlow = onFlow({
    name: "selfSummaryFlow",
    inputSchema: z.string(),
    outputSchema: z.string(),

    // These two fields for app check. The consumeAppCheckToken option is for
    // replay protection, and requires additional client configuration. See the
    // App Check docs.
    enforceAppCheck: true,
    consumeAppCheckToken: true,

    authPolicy: ...,
  }, (subject) => {...})

Uỷ quyền HTTP không phải Firebase

Khi triển khai luồng cho ngữ cảnh máy chủ bên ngoài Cloud Functions cho Firebase, bạn nên có một cách để thiết lập quy trình kiểm tra uỷ quyền của riêng mình cùng với các luồng gốc. Bạn có hai tuỳ chọn:

  1. Sử dụng bất kỳ khung máy chủ nào bạn muốn và chuyển ngữ cảnh xác thực thông qua runFlow() như đã nêu ở trên.

  2. Sử dụng startFlowsServer() tích hợp sẵn và cung cấp phần mềm trung gian Express trong cấu hình flow:

    export const selfSummaryFlow = defineFlow(
    {
      name: 'selfSummaryFlow',
      inputSchema: z.object({uid: z.string()}),
      outputSchema: z.string(),
      middleware: [
        (req, res, next) => {
          const token = req.headers['authorization'];
          const user = yourVerificationLibrary(token);
    
          // This is what will get passed to your authPolicy
          req.auth = user;
          next();
        }
      ],
      authPolicy: (auth, input) => {
        if (!auth) {
          throw new Error('Authorization required.');
        }
        if (input.uid !== auth.uid) {
          throw new Error('You may only summarize your own profile data.');
        }
      }
    },
    async (input) => { ... });
    
    startFlowsServer();  // This will register the middleware
    

    Để biết thêm thông tin về cách sử dụng Express, hãy xem hướng dẫn về Cloud Run.

Xin lưu ý rằng nếu bạn sử dụng phương thức (1), tuỳ chọn cấu hình middleware sẽ bị runFlow() bỏ qua.