Autorizzazione e integrità

Quando crei un'applicazione rivolta al pubblico, è estremamente importante proteggere i dati memorizzati nel sistema. Per quanto riguarda i modelli LLM, è necessaria un'attenzione aggiuntiva per garantire che il modello acceda solo ai dati che deve, che le chiamate allo strumento siano opportunamente limitate all'utente che richiama il modello LLM e che il flusso venga richiamato solo da applicazioni client verificate.

Firebase Genkit fornisce meccanismi per la gestione dei criteri e dei contesti di autorizzazione. I flussi in esecuzione su Firebase possono utilizzare un callback (o un helper) per i criteri di autenticazione. In alternativa, Firebase fornisce anche il contesto di autenticazione nel flusso, dove può eseguire i propri controlli. Per i flussi non Functions, l'autenticazione può essere gestita e impostata tramite il middleware.

Autorizzazione all'interno di un flusso

I flussi possono controllare l'autorizzazione in due modi: il binding della richiesta (ad es. onCallGenkit per Funzioni Cloud per Firebase o express) può applicare l'autorizzazione oppure questi framework possono passare i criteri di autenticazione al flusso stesso, in cui il flusso ha accesso alle informazioni per l'autenticazione gestita all'interno del flusso.

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

In questo caso, è compito del binding della richiesta compilare context.auth. Ad esempio, onCallGenkit compila automaticamente context.auth (Firebase Authentication), context.app (Firebase App Check) e context.instanceIdToken (Firebase Cloud Messaging). Quando chiami un flusso manualmente, puoi aggiungere il tuo contesto di autenticazione 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' } },
  }
);

Quando esegui l'app con l'interfaccia utente di sviluppo di Genkit, puoi passare l'oggetto Auth inserendo il JSON nella scheda "JSON Auth": {"uid": "abc-def"}.

Puoi anche recuperare il contesto di autenticazione per il flusso in qualsiasi momento all'interno del flusso chiamando ai.currentContext(), anche nelle funzioni richiamate dal flusso:

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

Quando testi i flussi con gli strumenti di sviluppo Genkit, puoi specificare questo oggetto auth nell'interfaccia utente o nella riga di comando con il flag --context:

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

Autorizzare l'utilizzo di Cloud Functions for Firebase

Gli SDK Cloud Functions for Firebase supportano Genkit, inclusa l'integrazione con Firebase Auth / Google Cloud Identity Platform, nonché il supporto integrato di Firebase App Check.

Autenticazione degli utenti

Il wrapper onCallGenkit() fornito dalla libreria Firebase Functions supporta integrato per gli SDK client di Cloud Functions for Firebase. Quando utilizzi questi SDK, l'intestazione Firebase Auth viene inclusa automaticamente se il client dell'app utilizza anche l'SDK Firebase Auth. Puoi utilizzare Firebase Auth per proteggere i flussi definiti 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);

Quando utilizzi onCallGenkit, context.auth viene restituito come oggetto con un uid per l'ID utente e un token che è un DecodedIdToken. Puoi sempre recuperare questo oggetto in qualsiasi momento utilizzando ai.currentContext() come indicato in precedenza. Quando esegui questo flusso durante lo sviluppo, devi passare l'oggetto utente nello stesso modo:

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

Ogni volta che esponi una funzione cloud all'intera rete di internet, è di vitale importanza utilizzare una sorta di meccanismo di autorizzazione per proteggere i tuoi dati e quelli dei tuoi clienti. Detto questo, a volte è necessario eseguire il deployment di una funzione Cloud senza controlli di autorizzazione basati sul codice (ad esempio, la funzione non è richiamabile da tutti, ma è protetta da Cloud IAM). Cloud Functions per Firebase ti consente di farlo utilizzando la proprietà invoker, che controlla l'accesso IAM. Il valore speciale 'private' lascia la funzione come impostazione IAM predefinita, il che significa che solo i chiamanti con il ruolo Invoker di Cloud Run possono eseguire la funzione. In alternativa, puoi fornire l'indirizzo email di un account di servizio o utente a cui deve essere concessa l'autorizzazione per chiamare questa funzione esatta.

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

Integrità del cliente

L'autenticazione da sola contribuisce notevolmente a proteggere la tua app. È anche importante assicurarsi che solo le tue app client chiamino le tue funzioni. Il plug-in Firebase per Genkit include il supporto di primo livello per Firebase App Check. A tale scopo, aggiungi le seguenti opzioni di configurazione a 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);

Autorizzazione HTTP non Firebase

Quando esegui il deployment dei flussi in un contesto del server esterno a Cloud Functions per Firebase, devi avere un modo per configurare i tuoi controlli di autorizzazione insieme ai flussi integrati.

Utilizza un ContextProvider per compilare i valori di contesto come auth e per fornire una norma dichiarativa o un callback della norma. L'SDK Genkit fornisce ContextProvider come apiKey e anche i plug-in possono metterli in evidenza. Ad esempio, il plug-in @genkit-ai/firebase/context espone un provider di contesto per verificare le credenziali di Firebase Auth e compilare il contesto.

Con codice come il seguente, che potrebbe essere visualizzato in una serie di applicazioni:

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

Puoi proteggere una semplice app Express "flow server" scrivendo:

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

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

In alternativa, puoi creare un'applicazione Express personalizzata utilizzando gli stessi strumenti:

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

ContextProviderestrae il framework web, quindi questi strumenti funzionano anche in altri framework come Next.js. Ecco un esempio di app Firebase creata su Next.js.

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

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

Per ulteriori informazioni sull'utilizzo di Express, consulta le istruzioni di Cloud Run.