Autorização e integridade

Ao criar qualquer aplicativo voltado ao público, é extremamente importante proteger os dados armazenados no sistema. No caso de LLMs, é necessário ter mais diligência para garantir que o modelo acesse apenas os dados necessários, que as chamadas de ferramentas sejam corretamente delimitadas para o usuário que invoca o LLM e que o fluxo seja invocado apenas por aplicativos de cliente verificados.

O Firebase Genkit oferece mecanismos para gerenciar políticas de autorização e contextos. Para fluxos executados no Cloud Functions para Firebase, os desenvolvedores precisam fornecer uma política de autenticação ou reconhecer explicitamente a falta dela. Para fluxos que não são de funções, a autenticação também pode ser gerenciada e definida, mas requer uma integração um pouco mais manual.

Autorização de fluxo básica

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 esse campo estiver definido, ele será executado antes da invocação do fluxo:

import { genkit, z } from 'genkit';

const ai = genkit({ ... });

export const selfSummaryFlow = ai.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) => {
    // Flow logic here...
  }
);

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

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

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

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

Ao executar com a interface do Genkit Development, é possível transmitir o objeto de autenticação digitando JSON na guia "Auth JSON": {"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 { genkit, z } from 'genkit';

const ai = genkit({ ... });;

async function readDatabase(uid: string) {
  const auth = ai.getAuthContext();
  if (auth?.admin) {
    // Do something special if the user is an admin
  } else {
    // Otherwise, use the `uid` variable to retrieve the relevant document
  }
}

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

Ao testar fluxos com ferramentas de desenvolvimento do Genkit, é possível especificar esse objeto de autenticação na interface ou na linha de comando com a flag --auth:

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

Integração do Cloud Functions para Firebase

O plug-in do Firebase oferece 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 forma 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, desde que o cliente do app também esteja usando o SDK do Firebase Auth. Você pode usar o Firebase Auth para proteger seus fluxos definidos com onFlow():

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

const ai = genkit({ ... });;

export const selfSummaryFlow = onFlow(
  ai,
  {
    name: 'selfSummaryFlow',
    inputSchema: z.string(),
    outputSchema: z.string(),
    authPolicy: firebaseAuth((user) => {
      if (!user.email_verified && !user.admin) {
        throw new Error('Email not verified');
      }
    }),
  },
  async (input) => {
        // Flow logic here...
  }
);

Ao usar o plug-in do Firebase Auth, user será retornado como um DecodedIdToken. Você pode recuperar esse objeto a qualquer momento usando getFlowAuth(), conforme observado 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. No entanto, nos casos em que você quer permitir o acesso não autenticado com um tratamento especial para usuários autenticados (por exemplo, para upsell de recursos), é 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 a Internet, é de vital importância usar algum tipo de mecanismo de autorização para proteger seus dados e os dados dos seus clientes. No entanto, há momentos em que você precisa implantar um Cloud Function sem verificações de autorização baseadas em código. Por exemplo, sua função não pode ser chamada por qualquer pessoa, mas é protegida pelo Cloud IAM. O campo authPolicy é sempre necessário ao usar onFlow(), mas você pode indicar à biblioteca que está dispensando as verificações de autorização usando a função noAuth():

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

export const selfSummaryFlow = onFlow(
  ai,
  {
    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(),
  },
  async (input) => {
        // Flow logic here...
  }
);

Integridade do cliente

A autenticação por si só protege o app, mas também é importante garantir que apenas os apps clientes estejam chamando as funções. O plug-in do Firebase para o 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(
  ai,
  {
    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: ...,
  },
  async (input) => {
        // Flow logic here...
  }
);

Autorização HTTP que não é do Firebase

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

  1. Use a framework do servidor que preferir e transmita o contexto de autenticação pela chamada de fluxo, conforme observado acima.

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

    import { genkit, z } from 'genkit';
    
    const ai = genkit({ ... });;
    
    export const selfSummaryFlow = ai.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);
    
            // Pass auth information to the flow
            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) => {
        // Flow logic here...
      }
    );
    
    ai.startFlowServer({
      flows: [selfSummaryFlow],
    });  // Registers the middleware
    

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

Se você escolher (1), a opção de configuração middleware será ignorada quando o fluxo for invocado diretamente.