diff --git a/.changeset/brave-ducks-relax.md b/.changeset/brave-ducks-relax.md
new file mode 100644
index 00000000000..e23674343dd
--- /dev/null
+++ b/.changeset/brave-ducks-relax.md
@@ -0,0 +1,6 @@
+---
+'@firebase/auth': minor
+'@firebase/auth-compat': minor
+---
+
+[feature] Added Firebase App Check support to Firebase Auth.
diff --git a/packages/auth-compat/src/auth.test.ts b/packages/auth-compat/src/auth.test.ts
index af49682afed..2cab2890697 100644
--- a/packages/auth-compat/src/auth.test.ts
+++ b/packages/auth-compat/src/auth.test.ts
@@ -24,7 +24,10 @@ import sinonChai from 'sinon-chai';
 import { Auth } from './auth';
 import { CompatPopupRedirectResolver } from './popup_redirect';
 import * as platform from './platform';
-import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';
+import {
+  FAKE_APP_CHECK_CONTROLLER_PROVIDER,
+  FAKE_HEARTBEAT_CONTROLLER_PROVIDER
+} from '../test/helpers/helpers';
 
 use(sinonChai);
 
@@ -45,6 +48,7 @@ describe('auth compat', () => {
       underlyingAuth = new exp.AuthImpl(
         app,
         FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+        FAKE_APP_CHECK_CONTROLLER_PROVIDER,
         {
           apiKey: 'api-key'
         } as exp.ConfigInternal
diff --git a/packages/auth-compat/src/popup_redirect.test.ts b/packages/auth-compat/src/popup_redirect.test.ts
index e7686193e57..9426d928076 100644
--- a/packages/auth-compat/src/popup_redirect.test.ts
+++ b/packages/auth-compat/src/popup_redirect.test.ts
@@ -22,7 +22,10 @@ import * as exp from '@firebase/auth/internal';
 import * as platform from './platform';
 import { CompatPopupRedirectResolver } from './popup_redirect';
 import { FirebaseApp } from '@firebase/app-compat';
-import { FAKE_HEARTBEAT_CONTROLLER_PROVIDER } from '../test/helpers/helpers';
+import {
+  FAKE_APP_CHECK_CONTROLLER_PROVIDER,
+  FAKE_HEARTBEAT_CONTROLLER_PROVIDER
+} from '../test/helpers/helpers';
 
 use(sinonChai);
 
@@ -42,9 +45,14 @@ describe('popup_redirect/CompatPopupRedirectResolver', () => {
   beforeEach(() => {
     compatResolver = new CompatPopupRedirectResolver();
     const app = { options: { apiKey: 'api-key' } } as FirebaseApp;
-    auth = new exp.AuthImpl(app, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
-      apiKey: 'api-key'
-    } as exp.ConfigInternal);
+    auth = new exp.AuthImpl(
+      app,
+      FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+      FAKE_APP_CHECK_CONTROLLER_PROVIDER,
+      {
+        apiKey: 'api-key'
+      } as exp.ConfigInternal
+    );
   });
 
   afterEach(() => {
diff --git a/packages/auth-compat/test/helpers/helpers.ts b/packages/auth-compat/test/helpers/helpers.ts
index aa5069461c6..e689b77eb67 100644
--- a/packages/auth-compat/test/helpers/helpers.ts
+++ b/packages/auth-compat/test/helpers/helpers.ts
@@ -34,6 +34,13 @@ export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER = {
   }
 } as unknown as Provider<'heartbeat'>;
 
+// App Check is fully tested in core auth impl
+export const FAKE_APP_CHECK_CONTROLLER_PROVIDER = {
+  getImmediate(): undefined {
+    return undefined;
+  }
+} as unknown as Provider<'app-check-internal'>;
+
 export function initializeTestInstance(): void {
   firebase.initializeApp(getAppConfig());
   const stub = stubConsoleToSilenceEmulatorWarnings();
diff --git a/packages/auth/src/api/index.ts b/packages/auth/src/api/index.ts
index 6c9d775d243..4b07fa6e18a 100644
--- a/packages/auth/src/api/index.ts
+++ b/packages/auth/src/api/index.ts
@@ -42,7 +42,8 @@ export const enum HttpHeader {
   X_FIREBASE_LOCALE = 'X-Firebase-Locale',
   X_CLIENT_VERSION = 'X-Client-Version',
   X_FIREBASE_GMPID = 'X-Firebase-gmpid',
-  X_FIREBASE_CLIENT = 'X-Firebase-Client'
+  X_FIREBASE_CLIENT = 'X-Firebase-Client',
+  X_FIREBASE_APP_CHECK = 'X-Firebase-AppCheck'
 }
 
 export const enum Endpoint {
diff --git a/packages/auth/src/core/auth/auth_impl.test.ts b/packages/auth/src/core/auth/auth_impl.test.ts
index 6167de7b62b..514a61d25e4 100644
--- a/packages/auth/src/core/auth/auth_impl.test.ts
+++ b/packages/auth/src/core/auth/auth_impl.test.ts
@@ -24,6 +24,8 @@ import { FirebaseApp } from '@firebase/app';
 import { FirebaseError } from '@firebase/util';
 
 import {
+  FAKE_APP_CHECK_CONTROLLER,
+  FAKE_APP_CHECK_CONTROLLER_PROVIDER,
   FAKE_HEARTBEAT_CONTROLLER,
   FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
   testAuth,
@@ -62,6 +64,7 @@ describe('core/auth/auth_impl', () => {
     const authImpl = new AuthImpl(
       FAKE_APP,
       FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+      FAKE_APP_CHECK_CONTROLLER_PROVIDER,
       {
         apiKey: FAKE_APP.options.apiKey!,
         apiHost: DefaultConfig.API_HOST,
@@ -582,6 +585,7 @@ describe('core/auth/auth_impl', () => {
       const authImpl = new AuthImpl(
         FAKE_APP,
         FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+        FAKE_APP_CHECK_CONTROLLER_PROVIDER,
         {
           apiKey: FAKE_APP.options.apiKey!,
           apiHost: DefaultConfig.API_HOST,
@@ -656,5 +660,33 @@ describe('core/auth/auth_impl', () => {
         'X-Client-Version': 'v'
       });
     });
+
+    it('adds the App Check token if available', async () => {
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(Promise.resolve({ token: 'fake-token' }));
+      expect(await auth._getAdditionalHeaders()).to.eql({
+        'X-Client-Version': 'v',
+        'X-Firebase-AppCheck': 'fake-token'
+      });
+    });
+
+    it('does not add the App Check token if none returned', async () => {
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(Promise.resolve({ token: '' }));
+      expect(await auth._getAdditionalHeaders()).to.eql({
+        'X-Client-Version': 'v'
+      });
+    });
+
+    it('does not add the App Check token if controller unavailable', async () => {
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(undefined as any);
+      expect(await auth._getAdditionalHeaders()).to.eql({
+        'X-Client-Version': 'v'
+      });
+    });
   });
 });
diff --git a/packages/auth/src/core/auth/auth_impl.ts b/packages/auth/src/core/auth/auth_impl.ts
index 92467933da7..89a85c3cac4 100644
--- a/packages/auth/src/core/auth/auth_impl.ts
+++ b/packages/auth/src/core/auth/auth_impl.ts
@@ -17,6 +17,7 @@
 
 import { _FirebaseService, FirebaseApp } from '@firebase/app';
 import { Provider } from '@firebase/component';
+import { AppCheckInternalComponentName } from '@firebase/app-check-interop-types';
 import {
   Auth,
   AuthErrorMap,
@@ -62,6 +63,7 @@ import { _getUserLanguage } from '../util/navigator';
 import { _getClientVersion } from '../util/version';
 import { HttpHeader } from '../../api';
 import { AuthMiddlewareQueue } from './middleware';
+import { _logWarn } from '../util/log';
 
 interface AsyncAction {
   (): Promise<void>;
@@ -108,6 +110,7 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
   constructor(
     public readonly app: FirebaseApp,
     private readonly heartbeatServiceProvider: Provider<'heartbeat'>,
+    private readonly appCheckServiceProvider: Provider<AppCheckInternalComponentName>,
     public readonly config: ConfigInternal
   ) {
     this.name = app.name;
@@ -645,8 +648,31 @@ export class AuthImpl implements AuthInternal, _FirebaseService {
     if (heartbeatsHeader) {
       headers[HttpHeader.X_FIREBASE_CLIENT] = heartbeatsHeader;
     }
+
+    // If the App Check service exists, add the App Check token in the headers
+    const appCheckToken = await this._getAppCheckToken();
+    if (appCheckToken) {
+      headers[HttpHeader.X_FIREBASE_APP_CHECK] = appCheckToken;
+    }
+
     return headers;
   }
+
+  async _getAppCheckToken(): Promise<string | undefined> {
+    const appCheckTokenResult = await this.appCheckServiceProvider
+      .getImmediate({ optional: true })
+      ?.getToken();
+    if (appCheckTokenResult?.error) {
+      // Context: appCheck.getToken() will never throw even if an error happened.
+      // In the error case, a dummy token will be returned along with an error field describing
+      // the error. In general, we shouldn't care about the error condition and just use
+      // the token (actual or dummy) to send requests.
+      _logWarn(
+        `Error while retrieving App Check token: ${appCheckTokenResult.error}`
+      );
+    }
+    return appCheckTokenResult?.token;
+  }
 }
 
 /**
diff --git a/packages/auth/src/core/auth/register.ts b/packages/auth/src/core/auth/register.ts
index 10b061ee15a..1d779421c89 100644
--- a/packages/auth/src/core/auth/register.ts
+++ b/packages/auth/src/core/auth/register.ts
@@ -63,36 +63,38 @@ export function registerAuth(clientPlatform: ClientPlatform): void {
         const app = container.getProvider('app').getImmediate()!;
         const heartbeatServiceProvider =
           container.getProvider<'heartbeat'>('heartbeat');
+        const appCheckServiceProvider =
+          container.getProvider<'app-check-internal'>('app-check-internal');
         const { apiKey, authDomain } = app.options;
-        return ((app, heartbeatServiceProvider) => {
-          _assert(
-            apiKey && !apiKey.includes(':'),
-            AuthErrorCode.INVALID_API_KEY,
-            { appName: app.name }
-          );
-          // Auth domain is optional if IdP sign in isn't being used
-          _assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, {
-            appName: app.name
-          });
-          const config: ConfigInternal = {
-            apiKey,
-            authDomain,
-            clientPlatform,
-            apiHost: DefaultConfig.API_HOST,
-            tokenApiHost: DefaultConfig.TOKEN_API_HOST,
-            apiScheme: DefaultConfig.API_SCHEME,
-            sdkClientVersion: _getClientVersion(clientPlatform)
-          };
 
-          const authInstance = new AuthImpl(
-            app,
-            heartbeatServiceProvider,
-            config
-          );
-          _initializeAuthInstance(authInstance, deps);
+        _assert(
+          apiKey && !apiKey.includes(':'),
+          AuthErrorCode.INVALID_API_KEY,
+          { appName: app.name }
+        );
+        // Auth domain is optional if IdP sign in isn't being used
+        _assert(!authDomain?.includes(':'), AuthErrorCode.ARGUMENT_ERROR, {
+          appName: app.name
+        });
+        const config: ConfigInternal = {
+          apiKey,
+          authDomain,
+          clientPlatform,
+          apiHost: DefaultConfig.API_HOST,
+          tokenApiHost: DefaultConfig.TOKEN_API_HOST,
+          apiScheme: DefaultConfig.API_SCHEME,
+          sdkClientVersion: _getClientVersion(clientPlatform)
+        };
+
+        const authInstance = new AuthImpl(
+          app,
+          heartbeatServiceProvider,
+          appCheckServiceProvider,
+          config
+        );
+        _initializeAuthInstance(authInstance, deps);
 
-          return authInstance;
-        })(app, heartbeatServiceProvider);
+        return authInstance;
       },
       ComponentType.PUBLIC
     )
diff --git a/packages/auth/src/core/util/handler.ts b/packages/auth/src/core/util/handler.ts
index 4d21b6cdd7b..363e04a16d5 100644
--- a/packages/auth/src/core/util/handler.ts
+++ b/packages/auth/src/core/util/handler.ts
@@ -40,6 +40,13 @@ const WIDGET_PATH = '__/auth/handler';
  */
 const EMULATOR_WIDGET_PATH = 'emulator/auth/handler';
 
+/**
+ * Fragment name for the App Check token that gets passed to the widget
+ *
+ * @internal
+ */
+const FIREBASE_APP_CHECK_FRAGMENT_ID = encodeURIComponent('fac');
+
 // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
 type WidgetParams = {
   apiKey: ApiKey;
@@ -54,14 +61,14 @@ type WidgetParams = {
   tid?: string;
 } & { [key: string]: string | undefined };
 
-export function _getRedirectUrl(
+export async function _getRedirectUrl(
   auth: AuthInternal,
   provider: AuthProvider,
   authType: AuthEventType,
   redirectUrl?: string,
   eventId?: string,
   additionalParams?: Record<string, string>
-): string {
+): Promise<string> {
   _assert(auth.config.authDomain, auth, AuthErrorCode.MISSING_AUTH_DOMAIN);
   _assert(auth.config.apiKey, auth, AuthErrorCode.INVALID_API_KEY);
 
@@ -107,7 +114,17 @@ export function _getRedirectUrl(
       delete paramsDict[key];
     }
   }
-  return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(1)}`;
+
+  // Sets the App Check token to pass to the widget
+  const appCheckToken = await auth._getAppCheckToken();
+  const appCheckTokenFragment = appCheckToken
+    ? `#${FIREBASE_APP_CHECK_FRAGMENT_ID}=${encodeURIComponent(appCheckToken)}`
+    : '';
+
+  // Start at index 1 to skip the leading '&' in the query string
+  return `${getHandlerBase(auth)}?${querystring(paramsDict).slice(
+    1
+  )}${appCheckTokenFragment}`;
 }
 
 function getHandlerBase({ config }: AuthInternal): string {
diff --git a/packages/auth/src/core/util/log.ts b/packages/auth/src/core/util/log.ts
index 649c35d4317..5d7ba4a780c 100644
--- a/packages/auth/src/core/util/log.ts
+++ b/packages/auth/src/core/util/log.ts
@@ -37,6 +37,12 @@ export function _logDebug(msg: string, ...args: string[]): void {
   }
 }
 
+export function _logWarn(msg: string, ...args: string[]): void {
+  if (logClient.logLevel <= LogLevel.WARN) {
+    logClient.warn(`Auth (${SDK_VERSION}): ${msg}`, ...args);
+  }
+}
+
 export function _logError(msg: string, ...args: string[]): void {
   if (logClient.logLevel <= LogLevel.ERROR) {
     logClient.error(`Auth (${SDK_VERSION}): ${msg}`, ...args);
diff --git a/packages/auth/src/model/auth.ts b/packages/auth/src/model/auth.ts
index 6aaa6c6781f..f0584368c94 100644
--- a/packages/auth/src/model/auth.ts
+++ b/packages/auth/src/model/auth.ts
@@ -82,6 +82,7 @@ export interface AuthInternal extends Auth {
   _logFramework(framework: string): void;
   _getFrameworks(): readonly string[];
   _getAdditionalHeaders(): Promise<Record<string, string>>;
+  _getAppCheckToken(): Promise<string | undefined>;
 
   readonly name: AppName;
   readonly config: ConfigInternal;
diff --git a/packages/auth/src/platform_browser/auth.test.ts b/packages/auth/src/platform_browser/auth.test.ts
index 13c78ee8d3d..44570d2ca20 100644
--- a/packages/auth/src/platform_browser/auth.test.ts
+++ b/packages/auth/src/platform_browser/auth.test.ts
@@ -29,6 +29,7 @@ import {
 import { OperationType } from '../model/enums';
 
 import {
+  FAKE_APP_CHECK_CONTROLLER_PROVIDER,
   FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
   testAuth,
   testUser
@@ -73,6 +74,7 @@ describe('core/auth/auth_impl', () => {
     const authImpl = new AuthImpl(
       FAKE_APP,
       FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+      FAKE_APP_CHECK_CONTROLLER_PROVIDER,
       {
         apiKey: FAKE_APP.options.apiKey!,
         apiHost: DefaultConfig.API_HOST,
@@ -141,15 +143,20 @@ describe('core/auth/initializeAuth', () => {
       authDomain = FAKE_APP.options.authDomain,
       blockMiddleware = false
     ): Promise<Auth> {
-      const auth = new AuthImpl(FAKE_APP, FAKE_HEARTBEAT_CONTROLLER_PROVIDER, {
-        apiKey: FAKE_APP.options.apiKey!,
-        apiHost: DefaultConfig.API_HOST,
-        apiScheme: DefaultConfig.API_SCHEME,
-        tokenApiHost: DefaultConfig.TOKEN_API_HOST,
-        authDomain,
-        clientPlatform: ClientPlatform.BROWSER,
-        sdkClientVersion: _getClientVersion(ClientPlatform.BROWSER)
-      });
+      const auth = new AuthImpl(
+        FAKE_APP,
+        FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+        FAKE_APP_CHECK_CONTROLLER_PROVIDER,
+        {
+          apiKey: FAKE_APP.options.apiKey!,
+          apiHost: DefaultConfig.API_HOST,
+          apiScheme: DefaultConfig.API_SCHEME,
+          tokenApiHost: DefaultConfig.TOKEN_API_HOST,
+          authDomain,
+          clientPlatform: ClientPlatform.BROWSER,
+          sdkClientVersion: _getClientVersion(ClientPlatform.BROWSER)
+        }
+      );
 
       _initializeAuthInstance(auth, {
         persistence,
@@ -378,6 +385,7 @@ describe('core/auth/initializeAuth', () => {
         const auth = new AuthImpl(
           FAKE_APP,
           FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+          FAKE_APP_CHECK_CONTROLLER_PROVIDER,
           {
             apiKey: FAKE_APP.options.apiKey!,
             apiHost: DefaultConfig.API_HOST,
diff --git a/packages/auth/src/platform_browser/popup_redirect.test.ts b/packages/auth/src/platform_browser/popup_redirect.test.ts
index be8078f3c1b..7c81dd139b1 100644
--- a/packages/auth/src/platform_browser/popup_redirect.test.ts
+++ b/packages/auth/src/platform_browser/popup_redirect.test.ts
@@ -30,7 +30,8 @@ import {
   TEST_AUTH_DOMAIN,
   TEST_KEY,
   testAuth,
-  TestAuth
+  TestAuth,
+  FAKE_APP_CHECK_CONTROLLER
 } from '../../test/helpers/mock_auth';
 import { AuthEventManager } from '../core/auth/auth_event_manager';
 import { OAuthProvider } from '../core/providers/oauth';
@@ -125,6 +126,49 @@ describe('platform_browser/popup_redirect', () => {
       );
     });
 
+    it('includes the App Check token in the url fragment if present', async () => {
+      await resolver._initialize(auth);
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(Promise.resolve({ token: 'fake-token' }));
+
+      await resolver._openPopup(auth, provider, event);
+
+      const matches = (popupUrl as string).match(/.*?#(.*)/);
+      expect(matches).not.to.be.null;
+      const fragment = matches![1];
+      expect(fragment).to.include('fac=fake-token');
+    });
+
+    it('does not add the App Check token in the url fragment if none returned', async () => {
+      await resolver._initialize(auth);
+      // Redundant, already set in mock_auth.ts but adding here for clarity
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(Promise.resolve({ token: '' }));
+
+      await resolver._openPopup(auth, provider, event);
+
+      const matches = (popupUrl as string).match(/.*?#(.*)/);
+      // The '#' character will not be included when the url fragment is not attached,
+      // so the url will not match the pattern
+      expect(matches).to.be.null;
+    });
+
+    it('does not add the App Check token in the url fragment if controller unavailable', async () => {
+      await resolver._initialize(auth);
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(undefined as any);
+
+      await resolver._openPopup(auth, provider, event);
+
+      const matches = (popupUrl as string).match(/.*?#(.*)/);
+      // The '#' character will not be included when the url fragment is not attached,
+      // so the url will not match the pattern
+      expect(matches).to.be.null;
+    });
+
     it('throws an error if apiKey is unspecified', async () => {
       delete (auth.config as Partial<Config>).apiKey;
       await resolver._initialize(auth);
@@ -157,8 +201,10 @@ describe('platform_browser/popup_redirect', () => {
       // eslint-disable-next-line @typescript-eslint/no-floating-promises
       resolver._openRedirect(auth, provider, event);
 
-      // Delay one tick
-      await Promise.resolve();
+      // Wait a bit so the _openRedirect() call completes
+      await new Promise((resolve): void => {
+        setTimeout(resolve, 100);
+      });
 
       expect(newWindowLocation).to.include(
         `https://${TEST_AUTH_DOMAIN}/__/auth/handler`
@@ -177,6 +223,67 @@ describe('platform_browser/popup_redirect', () => {
       );
     });
 
+    it('includes the App Check token in the url fragment if present', async () => {
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(Promise.resolve({ token: 'fake-token' }));
+
+      // This promise will never resolve on purpose
+      // eslint-disable-next-line @typescript-eslint/no-floating-promises
+      resolver._openRedirect(auth, provider, event);
+
+      // Wait a bit so the _openRedirect() call completes
+      await new Promise((resolve): void => {
+        setTimeout(resolve, 100);
+      });
+
+      const matches = newWindowLocation.match(/.*?#(.*)/);
+      expect(matches).not.to.be.null;
+      const fragment = matches![1];
+      expect(fragment).to.include('fac=fake-token');
+    });
+
+    it('does not add the App Check token in the url fragment if none returned', async () => {
+      // Redundant, already set in mock_auth.ts but adding here for clarity
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(Promise.resolve({ token: '' }));
+
+      // This promise will never resolve on purpose
+      // eslint-disable-next-line @typescript-eslint/no-floating-promises
+      resolver._openRedirect(auth, provider, event);
+
+      // Wait a bit so the _openRedirect() call completes
+      await new Promise((resolve): void => {
+        setTimeout(resolve, 100);
+      });
+
+      const matches = newWindowLocation.match(/.*?#(.*)/);
+      // The '#' character will not be included when the url fragment is not attached,
+      // so the url will not match the pattern
+      expect(matches).to.be.null;
+    });
+
+    it('does not add the App Check token in the url fragment if controller unavailable', async () => {
+      sinon
+        .stub(FAKE_APP_CHECK_CONTROLLER, 'getToken')
+        .returns(undefined as any);
+
+      // This promise will never resolve on purpose
+      // eslint-disable-next-line @typescript-eslint/no-floating-promises
+      resolver._openRedirect(auth, provider, event);
+
+      // Wait a bit so the _openRedirect() call completes
+      await new Promise((resolve): void => {
+        setTimeout(resolve, 100);
+      });
+
+      const matches = newWindowLocation.match(/.*?#(.*)/);
+      // The '#' character will not be included when the url fragment is not attached,
+      // so the url will not match the pattern
+      expect(matches).to.be.null;
+    });
+
     it('throws an error if authDomain is unspecified', async () => {
       delete auth.config.authDomain;
 
diff --git a/packages/auth/src/platform_browser/popup_redirect.ts b/packages/auth/src/platform_browser/popup_redirect.ts
index f17d142f3a3..36c3805c4ec 100644
--- a/packages/auth/src/platform_browser/popup_redirect.ts
+++ b/packages/auth/src/platform_browser/popup_redirect.ts
@@ -75,7 +75,7 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal {
       '_initialize() not called before _openPopup()'
     );
 
-    const url = _getRedirectUrl(
+    const url = await _getRedirectUrl(
       auth,
       provider,
       authType,
@@ -92,9 +92,14 @@ class BrowserPopupRedirectResolver implements PopupRedirectResolverInternal {
     eventId?: string
   ): Promise<never> {
     await this._originValidation(auth);
-    _setWindowLocation(
-      _getRedirectUrl(auth, provider, authType, _getCurrentUrl(), eventId)
+    const url = await _getRedirectUrl(
+      auth,
+      provider,
+      authType,
+      _getCurrentUrl(),
+      eventId
     );
+    _setWindowLocation(url);
     return new Promise(() => {});
   }
 
diff --git a/packages/auth/test/helpers/mock_auth.ts b/packages/auth/test/helpers/mock_auth.ts
index 4923ee78e87..e5e30fa1384 100644
--- a/packages/auth/test/helpers/mock_auth.ts
+++ b/packages/auth/test/helpers/mock_auth.ts
@@ -17,6 +17,7 @@
 
 import { FirebaseApp } from '@firebase/app';
 import { Provider } from '@firebase/component';
+import { AppCheckTokenResult } from '@firebase/app-check-interop-types';
 import { PopupRedirectResolver } from '../../src/model/public_types';
 import { debugErrorMap } from '../../src';
 
@@ -55,6 +56,19 @@ export const FAKE_HEARTBEAT_CONTROLLER_PROVIDER: Provider<'heartbeat'> = {
   }
 } as unknown as Provider<'heartbeat'>;
 
+export const FAKE_APP_CHECK_CONTROLLER = {
+  getToken: async () => {
+    return { token: '' } as AppCheckTokenResult;
+  }
+};
+
+export const FAKE_APP_CHECK_CONTROLLER_PROVIDER: Provider<'app-check-internal'> =
+  {
+    getImmediate(): typeof FAKE_APP_CHECK_CONTROLLER {
+      return FAKE_APP_CHECK_CONTROLLER;
+    }
+  } as unknown as Provider<'app-check-internal'>;
+
 export class MockPersistenceLayer extends InMemoryPersistence {
   lastObjectSet: PersistedBlob | null = null;
 
@@ -77,6 +91,7 @@ export async function testAuth(
   const auth: TestAuth = new AuthImpl(
     FAKE_APP,
     FAKE_HEARTBEAT_CONTROLLER_PROVIDER,
+    FAKE_APP_CHECK_CONTROLLER_PROVIDER,
     {
       apiKey: TEST_KEY,
       authDomain: TEST_AUTH_DOMAIN,