angular / angularfire

Angular + Firebase = ❤️
https://firebaseopensource.com/projects/angular/angularfire2
MIT License
7.7k stars 2.19k forks source link

AppCheck Build Error During Prerender for Angular SSR #3128

Open asithade opened 2 years ago

asithade commented 2 years ago

Version info

Angular: 13.0.3

Firebase: 9.6.4

AngularFire: 7.2.0

Other (e.g. Ionic/Cordova, Node, browser, operating system): Node 14

How to reproduce these conditions

Steps to set up and reproduce

After setting up AppCheck as such:


import { initializeAppCheck, provideAppCheck, ReCaptchaV3Provider } from '@angular/fire/app-check';

    provideAppCheck(() => initializeAppCheck(getApp(), { provider: new ReCaptchaV3Provider(environment.recaptchaSiteKey), isTokenAutoRefreshEnabled: true })),

I am able to serve my application with both yarn server and yarn dev:ssr and I'm able to load the site with AppCheck seemingly working. However, when I try to run yarn prerender, I get the following error:

Unhandled Promise rejection: document is not defined ; Zone: <root> ; Task: Promise.then ; Value: ReferenceError: document is not defined
google-oss-bot commented 2 years ago

This issue does not seem to follow the issue template. Make sure you provide all the required information.

ribalnasr commented 2 years ago

Did you find a solution for this?

ribalnasr commented 2 years ago

Here's my workaround for now...

I created a service:

@Injectable({
    providedIn: 'root'
})
export class RecaptchaBrowser {

    constructor(
        @Inject(PLATFORM_ID) private platformId: string
    ) { }

    provider(siteKey: string) {
        return isPlatformBrowser(this.platformId)
            ? new ReCaptchaV3Provider(siteKey)
            : new CustomProvider({
                getToken: () => new Promise(() => { })
            })
    }
}

and then I import the AppCheck module as follows:

    provideAppCheck((injector) => initializeAppCheck(getApp(), {
      provider: injector.get(RecaptchaBrowser).provider(environment.recaptchaSiteKey),
      isTokenAutoRefreshEnabled: true
    })),

This will skip the ReCaptchaV3Provider for server-side rendering and import it only in the browser.

miikaeru commented 2 years ago

I have the same issue. Is AppCheck not supported in Angular Universal?

posva commented 1 year ago

There is actually an example at https://github.com/angular/angularfire/blob/master/samples/advanced/src/app/app.module.ts#L47-L57

You need to use the admin sdk on server

MawRojas commented 1 year ago

Has anyone found a solution for this? The closest I've gotten is to check if the app is being checked on the server; if it is, then I create a custom provider similar to @ribalnasr. The issue with this is that it only works if app check is not enforced on Cloud Firestore. Once it is actually enforced, the request from the server will be rejected, which is to be expected. I haven't found a way to generate valid recaptchav3 token on the sever. If there's a way to create this token then we should be able to use it in a custom provider for app check.

Abdulali97 commented 11 months ago

I have the same issue: Angular SSR v17

Abdulali97 commented 11 months ago

this works for me

app config

const FIREBASE_ADMIN = new InjectionToken<app.App>('firebase-admin');

export function initializeAppCheckFactory(
  platformId: Object,
  injector: Injector
) {
  if (isPlatformBrowser(platformId)) {
    const admin = injector.get<app.App | null>(FIREBASE_ADMIN, null);
    if (admin) {
      const provider = new CustomProvider({
        getToken: () =>
          admin
            .appCheck()
            .createToken(environment.firebase.appId, { ttlMillis: 604_800_000 })
            .then(({ token, ttlMillis: expireTimeMillis }) => ({
              token,
              expireTimeMillis,
            })),
      });
      return initializeAppCheck(undefined, {
        provider,
        isTokenAutoRefreshEnabled: false,
      });
    } else {
      const provider = new ReCaptchaEnterpriseProvider(
        environment.recaptchaEnterpriseSiteKey
      );
      return initializeAppCheck(undefined, {
        provider,
        isTokenAutoRefreshEnabled: true,
      });
    }
  }
  return undefined;
}

export const appConfig: ApplicationConfig = {
  providers: [
    {
      provide: AppCheck,
      useFactory: (platformId: Object, injector: Injector) =>
        initializeAppCheckFactory(platformId, injector),
      deps: [PLATFORM_ID, Injector],
    },
    {
      provide: FIREBASE_OPTIONS,
      useValue: environment.firebase,
    },
    provideClientHydration(),
    provideAnimationsAsync(),
    provideRouter(
      appRoutes,
      withEnabledBlockingInitialNavigation(),
      withViewTransitions()
      // withDebugTracing()
    ),
    importProvidersFrom(
      provideFirebaseApp(() => initializeApp(environment.firebase)),
      provideAuth(() => getAuth())
    ),
    provideStore({ auth: authReducer }),
    provideEffects([AuthEffects]),
  ],
};

and Injecting AppCheck into AppComponent ensures initialization in a client-side context

export class AppComponent{
  constructor(
    private router: Router,
    private authFacade: AuthFacade,
    private appCheck: AppCheck,
  ) {} 
hittten commented 10 months ago

https://github.com/angular/angularfire/blob/master/samples/advanced/src/app/app.module.ts#L47-L57

It seems like a good solution but it has a dependency on the "firebase-admin" package. In my case, I just want to configure the app-check correctly but I don't need the firebase-admin.

It would be great if angular-fire provided a correct example of recommended implementation for AppCheck with SSR and prerender