Authorization and integrity

When building any public-facing application, it's extremely important to protect the data stored in your system. When it comes to LLMs, extra diligence is necessary to ensure that the model is only accessing data it should, tool calls are properly scoped to the user invoking the LLM, and the flow is being invoked only by verified client applications.

Firebase Genkit provides mechanisms for managing authorization policies and contexts. For flows running on Cloud Functions for Firebase, developers are required to provide an auth policy or else explicitly acknowledge the lack of one. For non-Functions flows, auth can be managed and set as well, but requires a bit more manual integration.

Basic flow authorization

All flows can define an authPolicy in their config. An auth policy is a function that tests if certain criteria (defined by you) are met, and throws an exception if any test fails. If this field is set, it is executed before the flow is invoked:

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

When executing this flow, you must provide an auth object using withLocalAuthContext or else you'll receive an 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 
'  
 }, 
  
 } 
 ); 
 

When running with the Genkit Development UI, you can pass the Auth object by entering JSON in the "Auth JSON" tab: {"uid": "abc-def"} .

You can also retrieve the auth context for the flow at any time within the flow by calling getFlowAuth() , including in functions invoked by the flow:

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

When testing flows with Genkit dev tools, you are able to specify this auth object in the UI, or on the command line with the --auth flag:

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

Cloud Functions for Firebase integration

The Firebase plugin provides convenient integration with Firebase Auth / Google Cloud Identity Platform as well as built-in Firebase App Check support.

Authorization

The onFlow() wrapper provided by the Firebase plugin works natively with the Cloud Functions for Firebase client SDKs . When using the SDK, the Firebase Auth header will automatically be included as long as your app client is also using the Firebase Auth SDK . You can use Firebase Auth to protect your flows defined with 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 
 ) 
  
 = 
>  
 {...}) 
 

When using the Firebase Auth plugin, user will be returned as a DecodedIdToken . You can always retrieve this object at any time via getFlowAuth() as noted above. When running this flow during development, you would pass the user object in the same way:

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

By default the Firebase Auth plugin requires the auth header to be sent by the client, but in cases where you wish to allow unauthenticated access with special handling for authenticated users (upselling features, say), then you can configure the policy like so:

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

Whenever you expose a Cloud Function to the wider internet, it is vitally important that you use some sort of authorization mechanism to protect your data and the data of your customers. With that said, there are times when you need to deploy a Cloud Function with no code-based authorization checks (for example, your Function is not world-callable but instead is protected by Cloud IAM ). The authPolicy field is always required when using onFlow() , but you can indicate to the library that you are forgoing authorization checks by using the noAuth() function:

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

Client integrity

Authentication on its own goes a long way to protect your app. But it's also important to ensure that only your client apps are calling your functions. The Firebase plugin for genkit includes first-class support for Firebase App Check . Simply add the following configuration options to your 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 
 ) 
  
 = 
>  
 {...}) 
 

Non-Firebase HTTP authorization

When deploying flows to a server context outside of Cloud Functions for Firebase, you'll want to have a way to set up your own authorization checks alongside the native flows. You have two options:

  1. Use whatever server framework you like, and pass the auth context through via runFlow() as noted above.

  2. Use the built-in startFlowsServer() and provide Express middleware in the flow config:

      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 
     
    

    For more information about using Express, see the Cloud Run instructions.

Please note, if you go with (1), you the middleware configuration option will be ignored by runFlow() .