Autorização e integridade

Ao criar qualquer aplicativo voltado para o público, é extremamente importante proteger os dados armazenados em seu sistema. Quando se trata de LLMs, é necessário ter cuidado extra para garantir que o modelo acesse apenas os dados necessários, que as chamadas de ferramentas tenham o escopo correto para o usuário que invoca o LLM e que o fluxo seja invocado apenas por aplicativos clientes verificados.

O Firebase Genkit tem mecanismos para gerenciar contextos e políticas de autorização. Para fluxos em execução no Cloud Functions para Firebase, os desenvolvedores precisam fornecer uma política de autenticação ou reconhecer explicitamente a falta dela. Para fluxos não do Functions, a autenticação também pode ser gerenciada e configurada, mas requer um pouco mais de integração manual.

Autorização básica de fluxo

Todos os fluxos podem definir um authPolicy na configuração. Uma política de autenticação é uma função que testa se determinados critérios (definidos por você) são atendidos e gera uma exceção se algum teste falhar. Se este campo for definido, ele será executado antes que o fluxo seja invocado:

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

Ao executar esse fluxo, você precisa fornecer um objeto de autenticação usando withLocalAuthContext. Caso contrário, você vai receber um erro:

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

Ao executar com a interface de desenvolvimento do Genkit, você pode transmitir o objeto Auth inserindo JSON na guia "JSON de autenticação": {"uid": "abc-def"}.

Também é possível recuperar o contexto de autenticação do fluxo a qualquer momento chamando getFlowAuth(), inclusive em funções invocadas pelo fluxo:

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

Ao testar fluxos com as ferramentas para desenvolvedores do Genkit, você pode especificar esse objeto de autenticação na interface ou na linha de comando com a sinalização --auth:

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

Integração com o Cloud Functions para Firebase

O plug-in do Firebase fornece uma integração conveniente com o Firebase Auth / Google Cloud Identity Platform, além de suporte integrado ao Firebase App Check.

Autorização

O wrapper onFlow() fornecido pelo plug-in do Firebase funciona de maneira nativa com os SDKs de cliente do Cloud Functions para Firebase. Ao usar o SDK, o cabeçalho do Firebase Auth será incluído automaticamente, se o cliente do app também estiver usando o SDK do Firebase Auth. É possível usar o Firebase Auth para proteger seus fluxos definidos com 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) => {...})

Ao usar o plug-in do Firebase Auth, user será retornado como um DeencodedIdToken. É possível recuperar esse objeto a qualquer momento usando getFlowAuth(), conforme indicado acima. Ao executar esse fluxo durante o desenvolvimento, você transmitiria o objeto do usuário da mesma maneira:

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

Por padrão, o plug-in do Firebase Auth exige que o cabeçalho de autenticação seja enviado pelo cliente, mas nos casos em que você quiser permitir o acesso não autenticado com processamento especial para usuários autenticados (como recursos de upsell), é possível configurar a política da seguinte maneira:

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

Sempre que você expõe uma função do Cloud para uma Internet mais ampla, é de vital importância usar algum tipo de mecanismo de autorização para proteger os dados e os dados dos clientes. Dito isso, há momentos em que você precisa implantar uma função do Cloud sem verificações de autorização baseadas em código (por exemplo, sua função não é chamável globalmente, mas está protegida pelo Cloud IAM). O campo authPolicy é sempre obrigatório ao usar onFlow(), mas é possível indicar à biblioteca que você está renunciando às verificações de autorização usando a função 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) => {...})

Integridade do cliente

A autenticação por si só ajuda a proteger seu app. Mas também é importante garantir que somente os apps clientes chamem as funções. O plug-in do Firebase para genkit inclui suporte de primeira classe para o Firebase App Check. Basta adicionar as seguintes opções de configuração ao 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) => {...})

Autorização HTTP não Firebase

Ao implantar fluxos em um contexto de servidor fora do Cloud Functions para Firebase, é interessante ter uma maneira de configurar suas próprias verificações de autorização junto com os fluxos nativos. Você tem duas opções:

  1. Use o framework de servidor que quiser e transmita o contexto de autenticação pelo runFlow(), conforme mencionado acima.

  2. Use o startFlowsServer() integrado e forneça o middleware Express na configuração de fluxo:

    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
    

    Para mais informações sobre como usar o Express, consulte as instruções do Cloud Run.

Se você escolher (1), a opção de configuração middleware será ignorada por runFlow().