Autoryzacja i integralność

Podczas tworzenia aplikacji dostępnych publicznie należy koniecznie chronić dane przechowywane w systemie. W przypadku modeli LLM konieczna jest dodatkowa staranność, aby model miał dostęp tylko do danych, do których powinien. Wywołania narzędzi miały odpowiedni zakres do użytkownika wywołującego ten model, a przepływ jest wywoływany tylko przez zweryfikowane aplikacje klienckie.

Firebase Genkit udostępnia mechanizmy zarządzania zasadami autoryzacji i kontekstami. W przypadku przepływów działających w Cloud Functions dla Firebase deweloperzy muszą podać zasadę uwierzytelniania lub w przeciwnym razie wyraźnie potwierdzić jej brak. W przypadku przepływów innych niż funkcje uwierzytelnianiem można też ustawiać i zarządzać nim, ale wymaga to nieco bardziej ręcznej integracji.

Podstawowa autoryzacja przepływu

Wszystkie przepływy mogą definiować authPolicy w swojej konfiguracji. Zasada uwierzytelniania to funkcja, która sprawdza, czy określone kryteria (zdefiniowane przez Ciebie) są spełnione, i w przypadku niepowodzenia testu zgłasza wyjątek. Jeśli to pole jest ustawione, jest wykonywane przed wywołaniem przepływu:

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) => { ... });

Podczas wykonywania tego przepływu musisz podać obiekt uwierzytelniania za pomocą withLocalAuthContext. W przeciwnym razie pojawi się błąd:

// 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' },
  }
);

Korzystając z interfejsu programistycznego Genkit, możesz przekazać obiekt Auth, wpisując kod JSON na karcie „Auth JSON”: {"uid": "abc-def"}.

Kontekst uwierzytelniania możesz też pobrać w dowolnym momencie procesu, wywołując funkcję getFlowAuth(), również w funkcjach wywoływanych przez ten proces:

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);
  });

Podczas testowania przepływów za pomocą narzędzi dla programistów Genkit możesz określić ten obiekt auth w interfejsie lub w wierszu poleceń za pomocą flagi --auth:

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

Integracja Cloud Functions dla Firebase

Wtyczka Firebase umożliwia wygodną integrację z Uwierzytelnianiem Firebase i Google Cloud Identity Platform, a także wbudowaną obsługą Sprawdzania aplikacji Firebase.

Autoryzacja

Kod onFlow() udostępniany przez wtyczkę Firebase działa natywnie z pakietami SDK klienta Cloud Functions dla Firebase. Jeśli używasz pakietu SDK, nagłówek Uwierzytelniania Firebase zostanie uwzględniony automatycznie, o ile klient aplikacji korzysta też z pakietu SDK Firebase Auth. Uwierzytelniania Firebase możesz używać do ochrony przepływów zdefiniowanych w 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) => {...})

Gdy używasz wtyczki Uwierzytelnianie Firebase, user będzie zwracany jako DecodedIdToken. W każdej chwili możesz pobrać ten obiekt przy użyciu getFlowAuth(), jak zaznaczono powyżej. Uruchamiając ten proces na etapie programowania, przekaż obiekt użytkownika w ten sam sposób:

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

Domyślnie wtyczka Uwierzytelnianie Firebase wymaga wysłania przez klienta nagłówka uwierzytelniania, ale w przypadku, gdy chcesz zezwolić na nieuwierzytelniony dostęp ze specjalną obsługą dla uwierzytelnionych użytkowników (np. w celu zwiększenia sprzedaży), możesz skonfigurować zasady w taki sposób:

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

Gdy udostępniasz funkcję w Cloud Functions w szerszym internecie, ważne jest użycie jakiegoś rodzaju mechanizmu autoryzacji, by chronić swoje dane i dane klientów. Mimo to czasami musisz wdrożyć funkcję w Cloud Functions bez testów autoryzacji na podstawie kodu (na przykład nie można jej wywoływać na całym świecie, ale jest chroniona przez Cloud IAM). Gdy używasz onFlow(), pole authPolicy jest zawsze wymagane, ale możesz wskazać w bibliotece, że przeprowadzasz kontrole autoryzacji, używając funkcji 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) => {...})

Uczciwość klienta

Samo uwierzytelnianie jest bardzo skuteczne w ochronie aplikacji, ale musisz też zadbać o to, aby tylko aplikacje klienckie wywoływały Twoje funkcje. Wtyczka Firebase dla genkit zapewnia najwyższej klasy obsługę Sprawdzania aplikacji Firebase. Wystarczy, że dodasz do onFlow() te opcje konfiguracji:

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) => {...})

Autoryzacja HTTP spoza Firebase

Gdy wdrażasz przepływy do kontekstu serwera poza Cloud Functions dla Firebase, przyda Ci się możliwość skonfigurowania własnych kontroli autoryzacji obok natywnych przepływów pracy. Dostępne są dwie opcje:

  1. Możesz użyć dowolnej platformy serwera i przekazać kontekst uwierzytelniania za pomocą runFlow() w sposób opisany powyżej.

  2. Użyj wbudowanego narzędzia startFlowsServer() i udostępnij ekspresowe oprogramowanie pośredniczące w konfiguracji przepływu:

    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
    

    Więcej informacji o korzystaniu z usługi Express znajdziesz w instrukcjach dotyczących Cloud Run.

Pamiętaj, że jeśli wybierzesz (1), opcja konfiguracji middleware zostanie zignorowana przez funkcję runFlow().