Autoryzacja i integralność

Gdy tworzysz aplikacje dostępne publicznie, koniecznie musisz chronić dane przechowywane w Twoim systemie. W przypadku LLM trzeba zadbać o to, aby model miał dostęp tylko do danych, do których powinien mieć dostęp, a wywołania narzędzi były odpowiednio ograniczone do użytkownika wywołującego ten model, a przepływ jest wywoływany tylko przez zweryfikowane aplikacje klienckie.

Firebase Genkit zapewnia 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 wyraźnie potwierdzić brak takiej zasady. W przypadku przepływów niezwiązanych z funkcjami uwierzytelnianie można też zarządzać i konfigurować uwierzytelnianie, ale wymaga nieco bardziej ręcznej integracji.

Podstawowa autoryzacja przepływu

Wszystkie przepływy mogą zdefiniować authPolicy w swojej konfiguracji. Zasada uwierzytelniania to funkcja, która sprawdza, czy określone kryteria (zdefiniowane przez Ciebie) są spełnione, i zgłasza wyjątek, jeśli któryś z testów się nie powiedzie. Jeśli to pole jest ustawione, zostanie wykonane 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 procesu 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' },
  }
);

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

Kontekst uwierzytelniania przepływu możesz też pobrać w dowolnym momencie, wywołując getFlowAuth(). Obejmuje to też funkcje wywoływane przez przepływ:

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 deweloperskich Genkit możesz określić ten obiekt uwierzytelniania w interfejsie użytkownika lub w wierszu poleceń za pomocą flagi --auth:

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

Integracja z Cloud Functions dla Firebase

Wtyczka Firebase umożliwia wygodne korzystanie z funkcji Uwierzytelnianie Firebase lub Google Cloud Identity Platform, a także z wbudowaną obsługą Sprawdzania aplikacji Firebase.

Autoryzacja

Otoka onFlow() dostarczana przez wtyczkę Firebase natywnie działa z pakietami SDK klienta Cloud Functions dla Firebase. Gdy używasz pakietu SDK, nagłówek Uwierzytelnianie Firebase będzie dołączany automatycznie, o ile klient aplikacji korzysta też z pakietu SDK uwierzytelniania Firebase. Możesz używać Uwierzytelniania Firebase do ochrony przepływów zdefiniowanych za pomocą 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, wartość user jest zwracana jako DecodedIdToken. W każdej chwili możesz pobrać ten obiekt za pomocą metody getFlowAuth(), jak opisano powyżej. Uruchamiając ten proces w fazie programowania, przekazujesz obiekt użytkownika w taki sam sposób:

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

Domyślnie wtyczka Uwierzytelnianie Firebase wymaga, aby klient wysyłał nagłówek uwierzytelniania, ale jeśli chcesz zezwolić na nieuwierzytelniony dostęp ze specjalną obsługą uwierzytelnionych użytkowników (np. przez funkcje sprzedaży dodatkowej), możesz skonfigurować zasadę w ten 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, koniecznie korzystaj z jakiegoś mechanizmu autoryzacji, która chroni dane Twoje i klientów. Mając to na uwadze, zdarza się, że musisz wdrożyć funkcję w Cloud Functions bez kontroli autoryzacji na podstawie kodu (np. funkcja nie może być wywoływana na całym świecie, tylko chroniona przez Cloud IAM). Gdy używasz onFlow(), pole authPolicy jest zawsze wymagane, ale za pomocą funkcji noAuth() możesz wskazać bibliotece, że przechodzisz testy autoryzacji:

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

Integralność klienta

Samodzielne uwierzytelnianie to duży sposób na ochronę aplikacji. Ważne jest jednak, aby upewnić się, że tylko aplikacje klienckie wywołują Twoje funkcje. Wtyczka Firebase do 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 poza Firebase

Gdy wdrażasz przepływy w kontekście serwera poza Cloud Functions dla Firebase, chcesz mieć możliwość skonfigurowania własnych kontroli autoryzacji w ramach przepływów natywnych. Dostępne są dwie opcje:

  1. Użyj dowolnej platformy serwera i przekaż kontekst uwierzytelniania przez runFlow(), jak opisano powyżej.

  2. Użyj wbudowanego komponentu startFlowsServer() i udostępnij oprogramowanie pośredniczące Express 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 Express znajdziesz w instrukcjach Cloud Run.

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