Integridad y autorización

Cuando compilas cualquier aplicación para el público, es muy importante proteger los datos almacenados en tu sistema. En el caso de los LLM, se requiere diligencia adicional para garantizar que el modelo solo acceda a los datos que debe, que las llamadas a 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 políticas de autorización y contextos. 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, de lo contrario, reconocer de forma explícita la falta de una. En el caso de los flujos que no son de Functions, la autenticación también se puede administrar y configurar, pero requiere una integración un poco más manual.

Autorización de flujo básico

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

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

Cuando ejecutes este flujo, debes proporcionar un objeto de autenticación con withLocalAuthContext, de lo contrario, recibirás un error:

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

Cuando se ejecuta con la IU de desarrollo de Genkit, puedes pasar el objeto Auth ingresando JSON en la pestaña "Auth JSON": {"uid": "abc-def"}.

También puedes recuperar el contexto de autenticación del flujo en cualquier momento dentro del flujo llamando a getFlowAuth(), incluso en las funciones que invoca el flujo:

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

Cuando pruebas 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 con Firebase Authentication o Google Cloud Identity Platform, así como compatibilidad integrada con Firebase App Check.

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 uses el SDK, el encabezado de Firebase Auth se incluirá automáticamente, siempre que el cliente de tu app también use el SDK de Firebase Auth. Puedes usar Firebase Auth para proteger tus flujos definidos con 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...
  }
);

Cuando uses el complemento de Firebase Auth, user se mostrará como un DecodedIdToken. Puedes recuperar este objeto en cualquier momento a través de getFlowAuth(), como se indicó anteriormente. Cuando ejecutes este flujo durante el desarrollo, pasarás el objeto de usuario de la misma manera:

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

De forma predeterminada, el complemento de Firebase Auth requiere que el cliente envíe el encabezado de autenticación, pero en los casos en que desees permitir el acceso no autenticado con un manejo 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 expongas una Cloud Function a Internet en general, es de vital 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 es invocable por el público, sino que está protegida por Cloud IAM). El campo authPolicy siempre es obligatorio cuando se usa onFlow(), pero puedes indicarle a la biblioteca que estás omitiendo las verificaciones de autorización con la función 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...
  }
);

Integridad del cliente

La autenticación por sí sola es muy útil para proteger tu app. Sin embargo, también es importante garantizar que solo tus apps cliente llamen a tus funciones. El complemento de Firebase para genkit incluye compatibilidad de primer nivel con 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(
  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...
  }
);

Autorización de HTTP que no es de Firebase

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

  1. Usa el framework de servidor que quieras y pasa el contexto de autenticación a través de la llamada de flujo como se indicó anteriormente.

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

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

Ten en cuenta que, si eliges (1), se ignorará la opción de configuración middleware cuando se invoque el flujo directamente.