Autorisation et intégrité

Lorsque vous créez une application publique, il est extrêmement important de protéger les données stockées dans votre système. En ce qui concerne les LLM, une diligence supplémentaire est nécessaire pour s'assurer que le modèle n'accède qu'aux données qu'il doit, que les appels d'outils sont correctement définis pour l'utilisateur qui appelle le LLM et que le flux n'est appelé que par des applications client validées.

Firebase Genkit fournit des mécanismes permettant de gérer les stratégies et les contextes d'autorisation. Pour les flux exécutés sur Cloud Functions for Firebase, les développeurs doivent fournir une règle d'autorisation ou reconnaître explicitement l'absence de règle. Pour les flux autres que Functions, l'authentification peut également être gérée et définie, mais nécessite une intégration un peu plus manuelle.

Autorisation de flux de base

Tous les flux peuvent définir un authPolicy dans leur configuration. Une stratégie d'authentification est une fonction qui vérifie si certains critères (définis par vous) sont remplis et génère une exception si l'un des tests échoue. Si ce champ est défini, il est exécuté avant l'appel du flux:

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

Lorsque vous exécutez ce flux, vous devez fournir un objet d'authentification à l'aide de withLocalAuthContext, sinon un message d'erreur s'affiche:

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

Lorsque vous exécutez l'UI de développement Genkit, vous pouvez transmettre l'objet Auth en saisissant du code JSON dans l'onglet "Auth JSON" : {"uid": "abc-def"}.

Vous pouvez également récupérer le contexte d'authentification du flux à tout moment dans le flux en appelant getFlowAuth(), y compris dans les fonctions appelées par le flux:

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

Lorsque vous testez des flux avec les outils de développement Genkit, vous pouvez spécifier cet objet d'authentification dans l'UI ou sur la ligne de commande avec l'indicateur --auth:

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

Intégration de Cloud Functions for Firebase

Le plug-in Firebase permet une intégration pratique avec Firebase Auth / Google Cloud Identity Platform, ainsi qu'une prise en charge intégrée de Firebase App Check.

Autorisation

Le wrapper onFlow() fourni par le plug-in Firebase fonctionne de manière native avec les SDK client Cloud Functions for Firebase. Lorsque vous utilisez le SDK, l'en-tête Firebase Auth est automatiquement inclus tant que le client de votre application utilise également le SDK Firebase Auth. Vous pouvez utiliser Firebase Authentication pour protéger vos flux définis avec 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...
  }
);

Lorsque vous utilisez le plug-in Firebase Auth, user est renvoyé sous la forme d'un DecodedIdToken. Vous pouvez toujours récupérer cet objet à tout moment via getFlowAuth(), comme indiqué ci-dessus. Lorsque vous exécutez ce flux pendant le développement, vous devez transmettre l'objet utilisateur de la même manière:

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

Par défaut, le plug-in Firebase Auth nécessite que l'en-tête d'authentification soit envoyé par le client. Toutefois, si vous souhaitez autoriser un accès non authentifié avec un traitement spécial pour les utilisateurs authentifiés (par exemple, des fonctionnalités de vente incitative), vous pouvez configurer la stratégie comme suit:

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

Chaque fois que vous exposez une fonction Cloud à Internet, il est essentiel d'utiliser un mécanisme d'autorisation pour protéger vos données et celles de vos clients. Toutefois, il arrive que vous deviez déployer une fonction Cloud sans aucune vérification d'autorisation basée sur le code (par exemple, votre fonction n'est pas accessible à tous, mais est protégée par Cloud IAM). Le champ authPolicy est toujours obligatoire lorsque vous utilisez onFlow(), mais vous pouvez indiquer à la bibliothèque que vous renoncez aux vérifications d'autorisation à l'aide de la fonction 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...
  }
);

Intégrité du client

L'authentification seule permet de protéger votre application dans une large mesure. Toutefois, il est également important de vous assurer que seules vos applications clientes appellent vos fonctions. Le plug-in Firebase pour Genkit inclut une prise en charge de premier ordre de Firebase App Check. Il vous suffit d'ajouter les options de configuration suivantes à votre 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...
  }
);

Autorisation HTTP autre que Firebase

Lorsque vous déployez des flux dans un contexte serveur en dehors de Cloud Functions pour Firebase, vous devez pouvoir configurer vos propres vérifications d'autorisation en plus des flux natifs. Deux possibilités s'offrent à vous :

  1. Utilisez le framework de serveur de votre choix et transmettez le contexte d'authentification via l'appel de flux, comme indiqué ci-dessus.

  2. Utilisez le startFlowsServer() intégré et fournissez le middleware Express dans la configuration du flux:

    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
    

    Pour en savoir plus sur l'utilisation d'Express, consultez les instructions Cloud Run.

Veuillez noter que si vous choisissez (1), l'option de configuration middleware sera ignorée lorsque le flux sera appelé directement.