Integridad y autorización

Cuando compilas cualquier aplicación pública, es muy importante proteger los datos almacenados en tu sistema. Cuando se trata de los LLM, se requiere diligencia adicional para garantizar que el modelo solo acceda a los datos que debería, que las llamadas a las herramientas tengan el alcance adecuado para el usuario que invoca el LLM y que solo las aplicaciones cliente verificadas invoquen el flujo.

Firebase Genkit proporciona mecanismos para administrar contextos y políticas de autorización. En el caso de los flujos que se ejecutan en Cloud Functions para Firebase, los desarrolladores deben proporcionar una política de autenticación o confirmar explícitamente que no hay una. Para los flujos que no son de Functions, también se puede administrar y configurar la autenticación, pero requiere un poco más de integración manual.

Autorización básica de flujo

Todos los flujos pueden definir un authPolicy en su configuración. Una política de autenticación es una función que prueba si se cumplen ciertos criterios (definidos por ti) y arroja una excepción si falla alguna de las pruebas. Si se configura este campo, se ejecuta antes de que se invoque el flujo:

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

Cuando ejecutas este flujo, debes proporcionar un objeto Auth con withLocalAuthContext o, de lo contrario, recibirás un error:

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

Cuando ejecutas con la IU de desarrollo de Genkit, puedes pasar el objeto de Auth si ingresas JSON en la pestaña “Auth JSON”: {"uid": "abc-def"}.

También puedes recuperar el contexto de Auth para el flujo en cualquier momento dentro del flujo. Para ello, llama a getFlowAuth(), incluidas las funciones que este invoca:

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

Cuando pruebas los flujos con las herramientas para desarrolladores de Genkit, puedes especificar este objeto de autenticación en la IU o en la línea de comandos con la marca --auth:

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

Integración de Cloud Functions para Firebase

El complemento de Firebase proporciona una integración conveniente en Firebase Auth / Google Cloud Identity Platform, así como la compatibilidad integrada con la Verificación de aplicaciones de Firebase.

Autorización

El wrapper onFlow() que proporciona el complemento de Firebase funciona de forma nativa con los SDK de cliente de Cloud Functions para Firebase. Cuando se usa el SDK, el encabezado de Firebase Auth se incluirá automáticamente siempre y cuando el cliente de tu app también use el SDK de Firebase Auth. Puedes usar Firebase Auth para proteger los flujos definidos con 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) => {...})

Cuando se usa el complemento de Firebase Auth, se mostrará user como un DecodedIdToken. Como se indicó antes, puedes recuperar este objeto en cualquier momento a través de getFlowAuth(). Si ejecutas este flujo durante el desarrollo, debes pasar el objeto del usuario de la misma manera:

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

Según la configuración predeterminada, el complemento de Firebase Auth requiere que el cliente envíe el encabezado de Auth, pero, en los casos en los que desees permitir el acceso sin autenticación con control especial para los usuarios autenticados (por ejemplo, funciones de venta incremental), puedes configurar la política de la siguiente manera:

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

Cada vez que expones una Cloud Function a la Internet más amplia, es de suma importancia que uses algún tipo de mecanismo de autorización para proteger tus datos y los de tus clientes. Dicho esto, hay ocasiones en las que necesitas implementar una Cloud Function sin verificaciones de autorización basadas en código (por ejemplo, tu función no se puede llamar a todo el mundo, sino que Cloud IAM la protege). El campo authPolicy siempre es obligatorio cuando se usa onFlow(), pero puedes indicar a la biblioteca que renuncias a las verificaciones de autorización mediante la función 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) => {...})

Integridad del cliente

La autenticación por sí sola sirve para proteger tu app en gran medida, pero también es importante asegurarse de que solo las apps cliente llamen a tus funciones. El complemento de Firebase para genkit incluye asistencia de primera clase para la Verificación de aplicaciones de Firebase. Simplemente agrega las siguientes opciones de configuración a tu 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) => {...})

Autorización HTTP que no es de Firebase

Cuando implementes flujos en un contexto de servidor fuera de Cloud Functions para Firebase, debes tener una forma de configurar tus propias verificaciones de autorización junto con los flujos nativos. Tienes estas dos opciones:

  1. Usa el framework del servidor que desees y pasa el contexto de autenticación a través de runFlow() como se indicó más arriba.

  2. Usa el startFlowsServer() integrado y proporciona middleware de Express en la configuración del flujo:

    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 obtener más información sobre cómo usar Express, consulta las instrucciones de Cloud Run.

Ten en cuenta que si eliges el punto (1), runFlow() ignorará la opción de configuración middleware.