Skip to content

Commit 5fdf024

Browse files
authored
[FAL] Add support for limited-use tokens (#7285)
Per [b/440338679](https://b.corp.google.com/issues/440338679), This adds support for the `useLimitedUseAppCheckTokens` parameter to the `getInstance` constructor and the `.ai` helper method. To help assist this process, I've also update the `FirebaseAIMultiResourceComponent` to use a hashed data class containing all the unique data of each `getInstance`. Previously, it only tracked instances by the location. Now, it tracks instances by the location, backend, and the value of `useLimitedUseAppCheckTokens`. In all practicality, this is unlikely to get much usage, but it helps avoid any potential edge-case issues where customers expect different instances. Documentation is present for the added changes, as well as a changelog entry. These both match what we did on the iCore side, but feel free to adjust as needed. Furthermore, the `api.txt` file has been updated with the corresponding changes.
1 parent ecd5392 commit 5fdf024

File tree

8 files changed

+102
-11
lines changed

8 files changed

+102
-11
lines changed

firebase-ai/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@
77
configured.
88
* [changed] Added a `dilation` parameter to `ImagenMaskReference.generateMaskAndPadForOutpainting`
99
(#7260)
10+
* [feature] Added a new configuration option to enable limited-use App Check tokens for attesting
11+
Firebase AI Logic requests. This enhances security against replay attacks. To use this feature,
12+
configure it explicitly via the new `useLimitedUseAppCheckTokens` parameter when initializing
13+
`FirebaseAI`. We recommend migrating to limited-use tokens now, so your app will be ready to take
14+
advantage of replay protection when it becomes available for Firebase AI Logic.
1015

1116
# 17.1.0
1217
=======

firebase-ai/api.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ package com.google.firebase.ai {
2323
method public com.google.firebase.ai.GenerativeModel generativeModel(String modelName, com.google.firebase.ai.type.GenerationConfig? generationConfig = null, java.util.List<com.google.firebase.ai.type.SafetySetting>? safetySettings = null, java.util.List<com.google.firebase.ai.type.Tool>? tools = null, com.google.firebase.ai.type.ToolConfig? toolConfig = null, com.google.firebase.ai.type.Content? systemInstruction = null, com.google.firebase.ai.type.RequestOptions requestOptions = com.google.firebase.ai.type.RequestOptions());
2424
method public static com.google.firebase.ai.FirebaseAI getInstance();
2525
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend);
26+
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
2627
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
2728
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
29+
method public static com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
2830
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName);
2931
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null);
3032
method @com.google.firebase.ai.type.PublicPreviewAPI public com.google.firebase.ai.ImagenModel imagenModel(String modelName, com.google.firebase.ai.type.ImagenGenerationConfig? generationConfig = null, com.google.firebase.ai.type.ImagenSafetySettings? safetySettings = null);
@@ -41,13 +43,16 @@ package com.google.firebase.ai {
4143
public static final class FirebaseAI.Companion {
4244
method public com.google.firebase.ai.FirebaseAI getInstance();
4345
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend);
46+
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
4447
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app);
4548
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend);
49+
method public com.google.firebase.ai.FirebaseAI getInstance(com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend, boolean useLimitedUseAppCheckTokens);
4650
property public final com.google.firebase.ai.FirebaseAI instance;
4751
}
4852

4953
public final class FirebaseAIKt {
5054
method public static com.google.firebase.ai.FirebaseAI ai(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend = GenerativeBackend.googleAI());
55+
method public static com.google.firebase.ai.FirebaseAI ai(com.google.firebase.Firebase, com.google.firebase.FirebaseApp app = Firebase.app, com.google.firebase.ai.type.GenerativeBackend backend = GenerativeBackend.googleAI(), boolean useLimitedUseAppCheckTokens);
5156
method public static com.google.firebase.ai.FirebaseAI getAi(com.google.firebase.Firebase);
5257
}
5358

firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAI.kt

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ internal constructor(
4646
@Blocking private val blockingDispatcher: CoroutineContext,
4747
private val appCheckProvider: Provider<InteropAppCheckTokenProvider>,
4848
private val internalAuthProvider: Provider<InternalAuthProvider>,
49+
private val useLimitedUseAppCheckTokens: Boolean
4950
) {
5051

5152
/**
@@ -92,6 +93,7 @@ internal constructor(
9293
modelUri,
9394
firebaseApp.options.apiKey,
9495
firebaseApp,
96+
useLimitedUseAppCheckTokens,
9597
generationConfig,
9698
safetySettings,
9799
tools,
@@ -152,7 +154,8 @@ internal constructor(
152154
requestOptions,
153155
appCheckProvider.get(),
154156
internalAuthProvider.get(),
155-
backend
157+
backend,
158+
useLimitedUseAppCheckTokens,
156159
)
157160
}
158161

@@ -194,6 +197,7 @@ internal constructor(
194197
modelUri,
195198
firebaseApp.options.apiKey,
196199
firebaseApp,
200+
useLimitedUseAppCheckTokens,
197201
generationConfig,
198202
safetySettings,
199203
requestOptions,
@@ -218,9 +222,40 @@ internal constructor(
218222
public fun getInstance(
219223
app: FirebaseApp = Firebase.app,
220224
backend: GenerativeBackend
225+
): FirebaseAI {
226+
return getInstance(app, backend, false)
227+
}
228+
229+
/**
230+
* Returns the [FirebaseAI] instance for the provided [FirebaseApp] and [backend].
231+
*
232+
* @param backend the backend reference to make generative AI requests to.
233+
* @param useLimitedUseAppCheckTokens when sending tokens to the backend, this option enables
234+
* the usage of App Check's limited-use tokens instead of the standard cached tokens.
235+
*
236+
* A new limited-use tokens will be generated for each request; providing a smaller attack
237+
* surface for malicious parties to hijack tokens. When used alongside replay protection,
238+
* limited-use tokens are also _consumed_ after each request, ensuring they can't be used again.
239+
*
240+
* _This flag is set to `false` by default._
241+
*
242+
* **Important:** Replay protection is not currently supported for the FirebaseAI backend. While
243+
* this feature is being developed, you can still migrate to using limited-use tokens. Because
244+
* limited-use tokens are backwards compatible, you can still use them without replay
245+
* protection. Due to their shorter TTL over standard App Check tokens, they still provide a
246+
* security benefit. Migrating to limited-use tokens sooner minimizes disruption when support
247+
* for replay protection is added.
248+
*/
249+
// TODO(b/440356335): Update docs above when web page goes live in M170
250+
@JvmStatic
251+
@JvmOverloads
252+
public fun getInstance(
253+
app: FirebaseApp = Firebase.app,
254+
backend: GenerativeBackend,
255+
useLimitedUseAppCheckTokens: Boolean,
221256
): FirebaseAI {
222257
val multiResourceComponent = app[FirebaseAIMultiResourceComponent::class.java]
223-
return multiResourceComponent.get(backend)
258+
return multiResourceComponent.get(InstanceKey(backend, useLimitedUseAppCheckTokens))
224259
}
225260

226261
/** The [FirebaseAI] instance for the provided [FirebaseApp] using the Google AI Backend. */
@@ -249,3 +284,17 @@ public fun Firebase.ai(
249284
app: FirebaseApp = Firebase.app,
250285
backend: GenerativeBackend = GenerativeBackend.googleAI()
251286
): FirebaseAI = FirebaseAI.getInstance(app, backend)
287+
288+
/**
289+
* Returns the [FirebaseAI] instance for the provided [FirebaseApp] and [backend].
290+
*
291+
* @param backend the backend reference to make generative AI requests to.
292+
* @param useLimitedUseAppCheckTokens use App Check's limited-use tokens when sending requests to
293+
* the backend. To learn more about what this means, see the full docs on [FirebaseAI.getInstance].
294+
*/
295+
// TODO(b/440356335): Update docs above when web page goes live in M170
296+
public fun Firebase.ai(
297+
app: FirebaseApp = Firebase.app,
298+
backend: GenerativeBackend = GenerativeBackend.googleAI(),
299+
useLimitedUseAppCheckTokens: Boolean
300+
): FirebaseAI = FirebaseAI.getInstance(app, backend, useLimitedUseAppCheckTokens)

firebase-ai/src/main/kotlin/com/google/firebase/ai/FirebaseAIMultiResourceComponent.kt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,24 @@ internal class FirebaseAIMultiResourceComponent(
3737
private val internalAuthProvider: Provider<InternalAuthProvider>,
3838
) {
3939

40-
@GuardedBy("this") private val instances: MutableMap<String, FirebaseAI> = mutableMapOf()
40+
@GuardedBy("this") private val instances: MutableMap<InstanceKey, FirebaseAI> = mutableMapOf()
4141

42-
fun get(backend: GenerativeBackend): FirebaseAI =
42+
fun get(key: InstanceKey): FirebaseAI =
4343
synchronized(this) {
44-
instances.getOrPut(backend.location) {
44+
instances.getOrPut(key) {
4545
FirebaseAI(
4646
app,
47-
backend,
47+
key.backend,
4848
blockingDispatcher,
4949
appCheckProvider,
5050
internalAuthProvider,
51+
key.useLimitedUseAppCheckTokens
5152
)
5253
}
5354
}
5455
}
56+
57+
internal data class InstanceKey(
58+
val backend: GenerativeBackend,
59+
val useLimitedUseAppCheckTokens: Boolean
60+
)

firebase-ai/src/main/kotlin/com/google/firebase/ai/GenerativeModel.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ internal constructor(
6565
modelName: String,
6666
apiKey: String,
6767
firebaseApp: FirebaseApp,
68+
useLimitedUseAppCheckTokens: Boolean,
6869
generationConfig: GenerationConfig? = null,
6970
safetySettings: List<SafetySetting>? = null,
7071
tools: List<Tool>? = null,
@@ -73,7 +74,7 @@ internal constructor(
7374
requestOptions: RequestOptions = RequestOptions(),
7475
generativeBackend: GenerativeBackend,
7576
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
76-
internalAuthProvider: InternalAuthProvider? = null,
77+
internalAuthProvider: InternalAuthProvider? = null
7778
) : this(
7879
modelName,
7980
generationConfig,
@@ -88,7 +89,12 @@ internal constructor(
8889
requestOptions,
8990
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
9091
firebaseApp,
91-
AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
92+
AppCheckHeaderProvider(
93+
TAG,
94+
useLimitedUseAppCheckTokens,
95+
appCheckTokenProvider,
96+
internalAuthProvider
97+
),
9298
),
9399
)
94100

firebase-ai/src/main/kotlin/com/google/firebase/ai/ImagenModel.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ internal constructor(
5858
modelName: String,
5959
apiKey: String,
6060
firebaseApp: FirebaseApp,
61+
useLimitedUseAppCheckTokens: Boolean,
6162
generationConfig: ImagenGenerationConfig? = null,
6263
safetySettings: ImagenSafetySettings? = null,
6364
requestOptions: RequestOptions = RequestOptions(),
@@ -73,7 +74,12 @@ internal constructor(
7374
requestOptions,
7475
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
7576
firebaseApp,
76-
AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
77+
AppCheckHeaderProvider(
78+
TAG,
79+
useLimitedUseAppCheckTokens,
80+
appCheckTokenProvider,
81+
internalAuthProvider
82+
),
7783
),
7884
)
7985

firebase-ai/src/main/kotlin/com/google/firebase/ai/LiveGenerativeModel.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ internal constructor(
7070
appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
7171
internalAuthProvider: InternalAuthProvider? = null,
7272
generativeBackend: GenerativeBackend,
73+
useLimitedUseAppCheckTokens: Boolean,
7374
) : this(
7475
modelName,
7576
blockingDispatcher,
@@ -83,7 +84,12 @@ internal constructor(
8384
requestOptions,
8485
"gl-kotlin/${KotlinVersion.CURRENT}-ai fire/${BuildConfig.VERSION_NAME}",
8586
firebaseApp,
86-
AppCheckHeaderProvider(TAG, appCheckTokenProvider, internalAuthProvider),
87+
AppCheckHeaderProvider(
88+
TAG,
89+
useLimitedUseAppCheckTokens,
90+
appCheckTokenProvider,
91+
internalAuthProvider
92+
),
8793
generativeBackend
8894
),
8995
)

firebase-ai/src/main/kotlin/com/google/firebase/ai/common/AppCheckHeaderProvider.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import kotlinx.coroutines.tasks.await
2525

2626
internal class AppCheckHeaderProvider(
2727
private val logTag: String,
28+
private val useLimitedUseAppCheckTokens: Boolean,
2829
private val appCheckTokenProvider: InteropAppCheckTokenProvider? = null,
2930
private val internalAuthProvider: InternalAuthProvider? = null,
3031
) : HeaderProvider {
@@ -36,7 +37,14 @@ internal class AppCheckHeaderProvider(
3637
if (appCheckTokenProvider == null) {
3738
Log.w(logTag, "AppCheck not registered, skipping")
3839
} else {
39-
val token = appCheckTokenProvider.getToken(false).await()
40+
val result =
41+
if (useLimitedUseAppCheckTokens) {
42+
appCheckTokenProvider.limitedUseToken
43+
} else {
44+
appCheckTokenProvider.getToken(false)
45+
}
46+
47+
val token = result.await()
4048

4149
if (token.error != null) {
4250
Log.w(logTag, "Error obtaining AppCheck token", token.error)

0 commit comments

Comments
 (0)