fix(app-check,app): Revert app check default provider initialization (#10210)

This commit is contained in:
Christina Holland
2026-07-24 09:44:16 -07:00
committed by GitHub
parent fbd1f9aeb8
commit d4366fcff5
13 changed files with 32 additions and 220 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'@firebase/app-check': minor
'@firebase/app': minor
'firebase': minor
---
Remove ability to call `initializeAppCheck` without a `provider`.
+2 -5
View File
@@ -22,7 +22,7 @@ export type _AppCheckInternalComponentName = 'app-check-internal';
// @public
export interface AppCheckOptions {
isTokenAutoRefreshEnabled?: boolean;
provider?: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
}
// @public
@@ -67,10 +67,7 @@ export function getLimitedUseToken(appCheckInstance: AppCheck): Promise<AppCheck
export function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise<AppCheckTokenResult>;
// @public
export function initializeAppCheck(app?: FirebaseApp, options?: AppCheckOptions): AppCheck;
// @internal
export function _initializeAppCheckInternal(initializerName: string, app?: FirebaseApp, options?: AppCheckOptions): AppCheck;
export function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck;
// @public
export function onTokenChanged(appCheckInstance: AppCheck, observer: PartialObserver<AppCheckTokenResult>): Unsubscribe;
-1
View File
@@ -68,7 +68,6 @@ export interface FirebaseOptions {
measurementId?: string;
messagingSenderId?: string;
projectId?: string;
recaptchaSiteKey?: string;
storageBucket?: string;
}
+1 -1
View File
@@ -42,5 +42,5 @@ A reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider.
<b>Signature:</b>
```typescript
provider?: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
```
+4 -4
View File
@@ -19,7 +19,7 @@ Firebase App Check does not work in a Node.js environment using `ReCaptchaV3Prov
| Function | Description |
| --- | --- |
| <b>function(app, ...)</b> |
| [initializeAppCheck(app, options)](./app-check.md#initializeappcheck_3a806cb) | Activate App Check for the given app. Can be called only once per app. |
| [initializeAppCheck(app, options)](./app-check.md#initializeappcheck_5548dfc) | Activate App Check for the given app. Can be called only once per app. |
| <b>function(appCheckInstance, ...)</b> |
| [getLimitedUseToken(appCheckInstance)](./app-check.md#getlimitedusetoken_53ef5e3) | Requests a Firebase App Check token. This method should be used only if you need to authorize requests to a non-Firebase backend.<!-- -->Returns limited-use tokens that are intended for use with your non-Firebase backend endpoints that are protected with <a href="https://firebase.google.com/docs/app-check/custom-resource-backend#replay-protection"> Replay Protection</a>. This method does not affect the token generation behavior of the \#getAppCheckToken() method. |
| [getToken(appCheckInstance, forceRefresh)](./app-check.md#gettoken_39fc1b3) | Get the current App Check token. If <code>forceRefresh</code> is false, this function first checks for a valid token in memory, then local persistence (IndexedDB). If not found, or if <code>forceRefresh</code> is true, it makes a request to the App Check endpoint for a fresh token. That request attaches to the most recent in-flight request if one is present. |
@@ -53,21 +53,21 @@ Firebase App Check does not work in a Node.js environment using `ReCaptchaV3Prov
## function(app, ...)
### initializeAppCheck(app, options) {:#initializeappcheck_3a806cb}
### initializeAppCheck(app, options) {:#initializeappcheck_5548dfc}
Activate App Check for the given app. Can be called only once per app.
<b>Signature:</b>
```typescript
export declare function initializeAppCheck(app?: FirebaseApp, options?: AppCheckOptions): AppCheck;
export declare function initializeAppCheck(app: FirebaseApp | undefined, options: AppCheckOptions): AppCheck;
```
#### Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) | the [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) to activate App Check for |
| app | [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) \| undefined | the [FirebaseApp](./app.firebaseapp.md#firebaseapp_interface) to activate App Check for |
| options | [AppCheckOptions](./app-check.appcheckoptions.md#appcheckoptions_interface) | App Check initialization options |
<b>Returns:</b>
-11
View File
@@ -29,7 +29,6 @@ export interface FirebaseOptions
| [measurementId](./app.firebaseoptions.md#firebaseoptionsmeasurementid) | string | An ID automatically created when you enable Analytics in your Firebase project and register a web app. In versions 7.20.0 and higher, this parameter is optional. |
| [messagingSenderId](./app.firebaseoptions.md#firebaseoptionsmessagingsenderid) | string | Unique numerical value used to identify each sender that can send Firebase Cloud Messaging messages to client apps. |
| [projectId](./app.firebaseoptions.md#firebaseoptionsprojectid) | string | The unique identifier for the project across all of Firebase and Google Cloud. |
| [recaptchaSiteKey](./app.firebaseoptions.md#firebaseoptionsrecaptchasitekey) | string | ReCAPTCHA Enterprise site key associated with this app. |
| [storageBucket](./app.firebaseoptions.md#firebaseoptionsstoragebucket) | string | The default Cloud Storage bucket name. |
## FirebaseOptions.apiKey
@@ -102,16 +101,6 @@ The unique identifier for the project across all of Firebase and Google Cloud.
projectId?: string;
```
## FirebaseOptions.recaptchaSiteKey
ReCAPTCHA Enterprise site key associated with this app.
<b>Signature:</b>
```typescript
recaptchaSiteKey?: string;
```
## FirebaseOptions.storageBucket
The default Cloud Storage bucket name.
-97
View File
@@ -19,7 +19,6 @@ import { expect } from 'chai';
import { SinonStub, spy, stub } from 'sinon';
import {
setTokenAutoRefreshEnabled,
_initializeAppCheckInternal,
initializeAppCheck,
getToken,
onTokenChanged,
@@ -104,14 +103,6 @@ describe('api', () => {
})
).to.throw(/appCheck\/already-initialized/);
});
it('can only be called once (if default then rCV3)', () => {
initializeAppCheck(app, {});
expect(() =>
initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(FAKE_SITE_KEY)
})
).to.throw(/appCheck\/already-initialized/);
});
it('can only be called once (if given different ReCaptchaEnterpriseProviders)', () => {
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
@@ -122,45 +113,6 @@ describe('api', () => {
})
).to.throw(/appCheck\/already-initialized/);
});
it('can only be called once (if default then rCE with different key)', () => {
initializeAppCheck(app, {});
expect(() =>
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY + 'X')
})
).to.throw(/appCheck\/already-initialized/);
});
it('can only be called once (if rCE with different key then default)', () => {
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY + 'X')
});
expect(() => initializeAppCheck(app, {})).to.throw(
/appCheck\/already-initialized/
);
});
it('throws internal init error if previously initialized by internal SDK', () => {
_initializeAppCheckInternal('Product A', app);
expect(() =>
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY + 'X')
})
).to.throw(
/initialized by Product A.*appCheck\/already-internally-initialized/
);
});
it('does not throw internal init error if developer initialized first', () => {
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
});
_initializeAppCheckInternal('Product A', app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
});
expect(() =>
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY + 'X')
})
).to.throw(/appCheck\/already-initialized/);
});
it('can only be called once (if given different CustomProviders)', () => {
initializeAppCheck(app, {
provider: new CustomProvider({
@@ -195,28 +147,6 @@ describe('api', () => {
})
).to.equal(appCheckInstance);
});
it('can be called multiple times (default provider no options)', () => {
const appCheckInstance = initializeAppCheck(app);
expect(initializeAppCheck(app)).to.equal(appCheckInstance);
});
it('can be called multiple times (default provider empty options)', () => {
const appCheckInstance = initializeAppCheck(app, {});
expect(initializeAppCheck(app, {})).to.equal(appCheckInstance);
});
it('can be called multiple times (default provider then rCE)', () => {
const appCheckInstance = initializeAppCheck(app, {});
expect(
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
})
).to.equal(appCheckInstance);
});
it('can be called multiple times (rCE then default)', () => {
const appCheckInstance = initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
});
expect(initializeAppCheck(app)).to.equal(appCheckInstance);
});
it('can be called multiple times (if given equivalent CustomProviders)', () => {
const appCheckInstance = initializeAppCheck(app, {
provider: new CustomProvider({
@@ -301,33 +231,6 @@ describe('api', () => {
);
});
it('initialize reCAPTCHA when no provider is provided', () => {
const initReCAPTCHAStub = stub(reCAPTCHA, 'initializeEnterprise').returns(
Promise.resolve({} as any)
);
initializeAppCheck(app, {});
expect(initReCAPTCHAStub).to.have.been.calledWithExactly(
app,
FAKE_SITE_KEY
);
});
it('does not throw if provider passed, and no sitekey in config', () => {
app.options.recaptchaSiteKey = undefined;
expect(
initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider(FAKE_SITE_KEY)
})
).to.be.instanceOf(AppCheckService);
});
it('throws if no provider and no sitekey in config', () => {
app.options.recaptchaSiteKey = undefined;
expect(() => initializeAppCheck(app, {})).to.throw(
/appCheck\/no-provider/
);
});
it('sets activated to true', () => {
expect(getStateReference(app).activated).to.equal(false);
initializeAppCheck(app, {
+11 -80
View File
@@ -44,7 +44,6 @@ import {
import { readTokenFromStorage } from './storage';
import { getDebugToken, initializeDebugMode, isDebugMode } from './debug';
import { logger } from './logger';
import { ReCaptchaEnterpriseProvider } from './providers';
declare module '@firebase/component' {
interface NameServiceMapping {
@@ -66,9 +65,10 @@ export {
*/
export function initializeAppCheck(
app: FirebaseApp = getApp(),
options?: AppCheckOptions
options: AppCheckOptions
): AppCheck {
app = getModularInstance(app);
const provider = _getProvider(app, 'app-check');
// Ensure initializeDebugMode() is only called once.
if (!getDebugState().initialized) {
@@ -87,68 +87,25 @@ export function initializeAppCheck(
);
}
let defaultProvider: ReCaptchaEnterpriseProvider | undefined;
/**
* If user did not pass a provider, look for site key in project
* config and create a default ReCaptchaEnterpriseProvider with it.
*/
if (!options?.provider && app.options.recaptchaSiteKey) {
defaultProvider = new ReCaptchaEnterpriseProvider(
app.options.recaptchaSiteKey
);
}
/**
* If there's no passed provider and no siteKey in project config,
* throw.
*/
if (!options?.provider && !defaultProvider) {
throw ERROR_FACTORY.create(AppCheckError.NO_PROVIDER);
}
interface InitOptions extends Omit<AppCheckOptions, 'provider'> {
provider: AppCheckProvider;
}
const initOptions: InitOptions = {
...options,
provider: options?.provider || defaultProvider!
};
const componentProvider = _getProvider(app, 'app-check');
if (componentProvider.isInitialized()) {
const existingInstance = componentProvider.getImmediate();
const existingOptions =
componentProvider.getOptions() as unknown as AppCheckOptions;
/**
* Check if all AppCheckOptions previously passed to initializeAppCheck
* (`isTokenAutoRefreshEnabled` and `provider`) match those being passed
* now. If so, return previous existing instance. Otherwise throw error.
*/
if (provider.isInitialized()) {
const existingInstance = provider.getImmediate();
const initialOptions = provider.getOptions() as unknown as AppCheckOptions;
if (
existingOptions.isTokenAutoRefreshEnabled ===
initOptions.isTokenAutoRefreshEnabled &&
existingOptions.provider?.isEqual(initOptions.provider)
initialOptions &&
!!initialOptions.isTokenAutoRefreshEnabled ===
!!options.isTokenAutoRefreshEnabled &&
initialOptions.provider?.isEqual(options.provider)
) {
return existingInstance;
} else {
if (typeof getStateReference(app).internallyInitializedBy === 'string') {
throw ERROR_FACTORY.create(
AppCheckError.ALREADY_INTERNALLY_INITIALIZED,
{
initializerName: getStateReference(app)
.internallyInitializedBy as string
}
);
}
throw ERROR_FACTORY.create(AppCheckError.ALREADY_INITIALIZED, {
appName: app.name
});
}
}
const appCheck = componentProvider.initialize({ options: initOptions });
_activate(app, initOptions.provider, initOptions.isTokenAutoRefreshEnabled);
const appCheck = provider.initialize({ options });
_activate(app, options.provider, options.isTokenAutoRefreshEnabled);
// If isTokenAutoRefreshEnabled is false, do not send any requests to the
// exchange endpoint without an explicit call from the user either directly
// or through another Firebase library (storage, functions, etc.)
@@ -164,32 +121,6 @@ export function initializeAppCheck(
return appCheck;
}
/**
* Internal wrapper that sets a state variable flagging that this was
* initialized under the hood by a product SDK.
*
* @internal
*/
export function _initializeAppCheckInternal(
// String to be used in error message if there is a future conflict.
// Example: "Firebase AI Logic"
initializerName: string,
app: FirebaseApp = getApp(),
options?: AppCheckOptions
): AppCheck {
const componentProvider = _getProvider(app, 'app-check');
const previouslyInitialized = componentProvider.isInitialized();
if (previouslyInitialized) {
// Product SDKs should accept any previously initialized
// App Check configuration without error.
return componentProvider.getImmediate();
} else {
const appCheck = initializeAppCheck(app, options);
getStateReference(app).internallyInitializedBy = initializerName;
return appCheck;
}
}
/**
* Activate App Check
* @param app - Firebase app to activate App Check for.
-10
View File
@@ -19,7 +19,6 @@ import { ErrorFactory, ErrorMap } from '@firebase/util';
export const enum AppCheckError {
ALREADY_INITIALIZED = 'already-initialized',
ALREADY_INTERNALLY_INITIALIZED = 'already-internally-initialized',
USE_BEFORE_ACTIVATION = 'use-before-activation',
FETCH_NETWORK_ERROR = 'fetch-network-error',
FETCH_PARSE_ERROR = 'fetch-parse-error',
@@ -28,7 +27,6 @@ export const enum AppCheckError {
STORAGE_GET = 'storage-get',
STORAGE_WRITE = 'storage-set',
RECAPTCHA_ERROR = 'recaptcha-error',
NO_PROVIDER = 'no-provider',
INITIAL_THROTTLE = 'initial-throttle',
THROTTLED = 'throttled'
}
@@ -39,10 +37,6 @@ const ERRORS: ErrorMap<AppCheckError> = {
'different options. To avoid this error, call initializeAppCheck() with the ' +
'same options as when it was originally called. This will return the ' +
'already initialized instance.',
[AppCheckError.ALREADY_INTERNALLY_INITIALIZED]:
'App Check has already been automatically initialized by {$initializerName} ' +
'with default options. If you want to initialize App Check with custom options, ' +
'call initializeAppCheck() with those options before initializing {$initializerName}.',
[AppCheckError.USE_BEFORE_ACTIVATION]:
'App Check is being used before initializeAppCheck() is called for FirebaseApp {$appName}. ' +
'Call initializeAppCheck() before instantiating other Firebase services.',
@@ -61,16 +55,12 @@ const ERRORS: ErrorMap<AppCheckError> = {
[AppCheckError.STORAGE_WRITE]:
'Error thrown when writing to storage. Original error: {$originalErrorMessage}.',
[AppCheckError.RECAPTCHA_ERROR]: 'ReCAPTCHA error.',
[AppCheckError.NO_PROVIDER]:
'No attestation provider was passed to initializeAppCheck() and ' +
'no ReCAPTCHA Enterprise site key was found in the Firebase config.',
[AppCheckError.INITIAL_THROTTLE]: `{$httpStatus} error. Attempts allowed again after {$time}`,
[AppCheckError.THROTTLED]: `Requests throttled due to previous {$httpStatus} error. Attempts allowed again after {$time}`
};
interface ErrorParams {
[AppCheckError.ALREADY_INITIALIZED]: { appName: string };
[AppCheckError.ALREADY_INTERNALLY_INITIALIZED]: { initializerName: string };
[AppCheckError.USE_BEFORE_ACTIVATION]: { appName: string };
[AppCheckError.FETCH_NETWORK_ERROR]: { originalErrorMessage: string };
[AppCheckError.FETCH_PARSE_ERROR]: { originalErrorMessage: string };
+1 -1
View File
@@ -60,7 +60,7 @@ export interface AppCheckOptions {
/**
* A reCAPTCHA V3 provider, reCAPTCHA Enterprise provider, or custom provider.
*/
provider?: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
provider: CustomProvider | ReCaptchaV3Provider | ReCaptchaEnterpriseProvider;
/**
* If set to true, enables automatic background refresh of App Check token.
*/
-1
View File
@@ -34,7 +34,6 @@ export interface AppCheckState {
tokenRefresher?: Refresher;
reCAPTCHAState?: ReCAPTCHAState;
isTokenAutoRefreshEnabled?: boolean;
internallyInitializedBy?: string;
}
export interface ReCAPTCHAState {
+2 -3
View File
@@ -46,8 +46,7 @@ const fakeConfig = {
messagingSenderId: 'messagingSenderId',
databaseURL: 'databaseUrl',
storageBucket: 'storageBucket',
appId: '1:777777777777:web:d93b5ca1475efe57',
recaptchaSiteKey: FAKE_SITE_KEY
appId: '1:777777777777:web:d93b5ca1475efe57'
};
export function getFakeApp(overrides: Record<string, any> = {}): FirebaseApp {
@@ -113,7 +112,7 @@ export function getFakeHeartbeatServiceProvider(
() =>
({
getHeartbeatsHeader: () => Promise.resolve(fakeLogString)
} as HeartbeatService),
}) as HeartbeatService,
ComponentType.PRIVATE
)
);
+4 -6
View File
@@ -151,10 +151,6 @@ export interface FirebaseOptions {
* and higher, this parameter is optional.
*/
measurementId?: string;
/**
* ReCAPTCHA Enterprise site key associated with this app.
*/
recaptchaSiteKey?: string;
}
/**
@@ -179,8 +175,10 @@ export interface FirebaseAppSettings {
*
* Configuration options given to {@link (initializeServerApp:1) | initializeServerApp()}
*/
export interface FirebaseServerAppSettings
extends Omit<FirebaseAppSettings, 'name'> {
export interface FirebaseServerAppSettings extends Omit<
FirebaseAppSettings,
'name'
> {
/**
* An optional Auth ID token used to resume a signed in user session from a client
* runtime environment.