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. Os fluxos executados no Firebase podem usar um callback (ou auxiliar) de política de autenticação. Como alternativa, o Firebase também fornece o contexto de autenticação no fluxo em que ele pode fazer as próprias verificações. Para fluxos que não são de funções, a autenticação pode ser gerenciada e definida pelo middleware.

Autorizar em um fluxo

Os fluxos podem verificar a autorização de duas maneiras: a vinculação de solicitação (por exemplo, onCallGenkit para o Cloud Functions para Firebase ou express) pode aplicar a autorização, ou esses frameworks podem transmitir políticas de autenticação para o próprio fluxo, em que o fluxo tem acesso às informações de autenticação gerenciadas no fluxo.

import { genkit, z, UserFacingError } from 'genkit';

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

export const selfSummaryFlow = ai.defineFlow( {
  name: 'selfSummaryFlow',
  inputSchema: z.object({ uid: z.string() }),
  outputSchema: z.string(),
}, async (input, { context }) => {
  if (!context.auth) {
    throw new UserFacingErrorError('UNAUTHENTICATED', 'Unauthenticated');
  }
  if (input.uid !== context.auth.uid) {
    throw new UserFacingError('PERMISSION_DENIED', 'You may only summarize your own profile data.');
  }
  // Flow logic here...
});

Cabe à vinculação de solicitação preencher context.auth nesse caso. Por exemplo, o onCallGenkit preenche automaticamente context.auth (Firebase Authentication), context.app (Firebase App Check) e context.instanceIdToken (Firebase Cloud Messaging). Ao chamar um fluxo manualmente, você pode adicionar seu próprio contexto de autenticação manualmente.

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

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

// Success
await selfSummaryFlow(
  { uid: 'abc-def' },
  {
    context: { auth: { 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 ai.currentContext(), inclusive em funções invocadas pelo fluxo:

import { genkit, z } from 'genkit';

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

async function readDatabase(uid: string) {
  const auth = ai.currentContext()?.auth;
  // Note: the shape of context.auth depends on the provider. onCallGenkit puts
  // claims information in auth.token
  if (auth?.token?.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 --context:

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

Autorizar usando o Cloud Functions para Firebase

Os SDKs do Cloud Functions for Firebase oferecem suporte ao Genkit, incluindo a integração com o Firebase Auth / Google Cloud Identity Platform, além do suporte integrado do Firebase App Check.

Autenticação do usuário

O wrapper onCallGenkit() fornecido pela biblioteca do Firebase Functions tem suporte integrado aos SDKs de cliente do Cloud Functions para Firebase. Quando você usa esses SDKs, o cabeçalho do Firebase Auth é 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 onCallGenkit():

import { genkit } from 'genkit';
import { onCallGenkit } from 'firebase-functions/https';

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

const selfSummaryFlow = ai.defineFlow({
  name: 'selfSummaryFlow',
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (input) => {
  // Flow logic here...
});

export const selfSummary = onCallGenkit({
  authPolicy: (auth) => auth?.token?.['email_verified'] && auth?.token?.['admin'],
}, selfSummaryFlow);

Quando você usa onCallGenkit, context.auth é retornado como um objeto com um uid para o ID do usuário e um token que é um DecodedIdToken. É possível recuperar esse objeto a qualquer momento usando ai.currentContext(), como indicado anteriormente. Ao executar esse fluxo durante o desenvolvimento, você transmite o objeto do usuário da mesma maneira:

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

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 Cloud Functions para Firebase permite fazer isso usando a propriedade invoker, que controla o acesso do IAM. O valor especial 'private' deixa a função como a configuração padrão do IAM, o que significa que apenas os autores de chamadas com o papel de Chamador do Cloud Run podem executar a função. Em vez disso, forneça o endereço de e-mail de um usuário ou de uma conta de serviço que precisa receber permissão para chamar essa função exata.

import { onCallGenkit } from 'firebase-functions/https'

const selfSummaryFlow = ai.defineFlow({
  name: 'selfSummaryFlow',
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (input) => {
  // Flow logic here...
});

export const selfSummary = onCallGenkit({
  invoker: 'private',
}, selfSummaryFlow);

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. Para fazer isso, adicione as seguintes opções de configuração ao onCallGenkit():

import { onCallGenkit } from 'firebase-functions/https';

const selfSummaryFlow = ai.defineFlow({
  name: 'selfSummaryFlow',
  inputSchema: z.string(),
  outputSchema: z.string(),
}, async (input) => {
  // Flow logic here...
});

export const selfSummary = onCallGenkit({
  // 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: ...,
}, selfSummaryFlow);

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

Ao implantar fluxos em um contexto de servidor fora do Cloud Functions para Firebase, é importante ter uma maneira de configurar suas próprias verificações de autorização com os fluxos integrados.

Use um ContextProvider para preencher valores de contexto, como auth, e fornecer uma política declarativa ou um callback de política. O SDK do Genkit fornece ContextProviders, como apiKey, e os plug-ins também podem expô-los. Por exemplo, o plug-in @genkit-ai/firebase/context expõe um provedor de contexto para verificar as credenciais do Firebase Auth e preencher o contexto.

Com um código como o seguinte, que pode aparecer em vários aplicativos:

// Express app with a simple API key
import { genkit, z } from 'genkit';

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

export const selfSummaryFlow = ai.defineFlow(
  {
    name: 'selfSummaryFlow',
    inputSchema: z.object({ uid: z.string() }),
    outputSchema: z.string(),
  },
  async (input) => {
    // Flow logic here...
  }
);

Você pode proteger um app expresso "flow server" simples escrevendo:

import { apiKey } from "genkit";
import { startFlowServer, withContext } from "@genkit-ai/express";

startFlowServer({
  flows: [
    withContext(selfSummaryFlow, apiKey(process.env.REQUIRED_API_KEY))
  ],
});

Ou você pode criar um aplicativo Express personalizado usando as mesmas ferramentas:

import { apiKey } from "genkit";
import * as express from "express";
import { expressHandler } from "@genkit-ai/express;

const app = express();
// Capture but don't validate the API key (or its absence)
app.post('/summary', expressHandler(selfSummaryFlow, { contextProvider: apiKey()}))

app.listen(process.env.PORT, () => {
  console.log(`Listening on port ${process.env.PORT}`);
})

Os ContextProviders abstraem o framework da Web, para que essas ferramentas funcionem em outros frameworks, como o Next.js. Confira um exemplo de um app do Firebase criado no Next.js.

import { appRoute } from "@genkit-ai/express";
import { firebaseContext } from "@genkit-ai/firebase";

export const POST = appRoute(selfSummaryFlow, { contextProvider: firebaseContext })

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