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. Los flujos que se ejecutan en Firebase pueden usar una devolución de llamada (o un ayudante) de la política de autenticación. Como alternativa, Firebase también proporciona contexto de autenticación en el flujo, donde puede hacer sus propias verificaciones. En el caso de los flujos que no son de Functions, la autenticación se puede administrar y configurar a través de un middleware.

Cómo autorizar dentro de un flujo

Los flujos pueden verificar la autorización de dos maneras: la vinculación de solicitudes (p.ej., onCallGenkit para Cloud Functions para Firebase o express) puede aplicar la autorización, o bien esos frameworks pueden pasar políticas de autenticación al flujo en sí, en el que el flujo tiene acceso a la información de la autenticación administrada dentro del flujo.

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

Depende de la vinculación de solicitudes propagar context.auth en este caso. Por ejemplo, onCallGenkit propaga automáticamente context.auth (Firebase Authentication), context.app (Verificación de aplicaciones de Firebase) y context.instanceIdToken (Firebase Cloud Messaging). Cuando llames a un flujo manualmente, puedes agregar tu propio contexto de autenticación de forma manual.

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

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 ai.currentContext(), incluso en las funciones que invoca el flujo:

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

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 --context:

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

Cómo autorizar el uso de Cloud Functions para Firebase

Los SDKs de Cloud Functions para Firebase admiten Genkit, incluida la integración con Firebase Auth o Google Cloud Identity Platform, así como la compatibilidad integrada con Firebase App Check.

Autenticación de usuarios

El wrapper onCallGenkit() que proporciona la biblioteca de Firebase Functions tiene compatibilidad integrada con los SDK de cliente de Cloud Functions para Firebase. Cuando usas estos SDKs, el encabezado de Firebase Auth se incluye 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 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);

Cuando usas onCallGenkit, context.auth se muestra como un objeto con un uid para el ID de usuario y un token que es un DecodedIdToken. Puedes recuperar este objeto en cualquier momento con ai.currentContext(), como se indicó antes. Cuando ejecutes este flujo durante el desarrollo, pasarás el objeto de usuario de la misma manera:

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

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). Cloud Functions para Firebase te permite hacerlo con la propiedad invoker, que controla el acceso de IAM. El valor especial 'private' deja la función como la configuración predeterminada de IAM, lo que significa que solo los llamadores con el rol de invocador de Cloud Run pueden ejecutar la función. En su lugar, puedes proporcionar la dirección de correo electrónico de un usuario o una cuenta de servicio a la que se le debe otorgar permiso para llamar a esta función exacta.

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

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. Para ello, agrega las siguientes opciones de configuración a tu 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);

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 integrados.

Usa un ContextProvider para propagar valores de contexto, como auth, y para proporcionar una política declarativa o una devolución de llamada de política. El SDK de Genkit proporciona ContextProvider, como apiKey, y los complementos también pueden exponerlos. Por ejemplo, el complemento @genkit-ai/firebase/context expone un proveedor de contexto para verificar las credenciales de Firebase Authentication y propagarlas en el contexto.

Con código como el siguiente, que puede aparecer en una variedad de aplicaciones:

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

Para proteger una app exprés simple de "servidor de flujo", escribe lo siguiente:

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

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

También puedes crear una aplicación exprés personalizada con las mismas herramientas:

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

ContextProvider abstrae el framework web, por lo que estas herramientas también funcionan en otros frameworks, como Next.js. Este es un ejemplo de una app de Firebase compilada en Next.js.

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

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

Para obtener más información sobre el uso de Express, consulta las instrucciones de Cloud Run.