angularfire
angularfire copied to clipboard
Auth Guard error when providing authpipe direct rather than as pipe generator (v9 new API not compat)
Version info
Angular: 14
Firebase: 9
AngularFire: ^7.4.1
Other (e.g. Ionic/Cordova, Node, browser, operating system): Node, Firefox/Chrome, Windows
How to reproduce these conditions
I've created stackoverflow Q&A of the issue https://stackoverflow.com/questions/73799781/how-to-create-auth-guard-in-angular-fire-v9-with-new-api-not-compat
I believe this is a bug and not a documentation issue given the naming of authGuardPipe
When using guard below and navigating to a guarded feature as a user with email not verified (used emulators) this throws the error TypeError: Unable to lift unknown Observable type triggered by https://github.com/angular/angularfire/blame/master/src/auth-guard/auth-guard.ts#L21
const redirectUnauthorizedAndUnverifiedToAuth: AuthPipe = map((user: User | null) => {
// if not logged in, redirect to `auth`
// if logged in and email verified, allow redirect
// if logged in and email not verified, redirect to `auth/verify`
return !!user ? (user.emailVerified ? true : ['auth', 'verify']) : ['auth']
})
Replacing redirectUnauthorizedAndUnverifiedToAuth in data : { authGuardPipe: redirectUnauthorizedAndUnverifiedToAuth } with authPipeGenerator fixes
Failing test unit, Stackblitz demonstrating the problem
Can't produce stackblitz due to error https://github.com/stackblitz/core/issues/2039
Steps to set up and reproduce
Created basic auth & my-feature modules, v9 new API angular imports
...
import { initializeApp, provideFirebaseApp } from '@angular/fire/app';
import { provideAuth, getAuth, connectAuthEmulator } from '@angular/fire/auth';
import {
provideFirestore,
getFirestore,
connectFirestoreEmulator,
} from '@angular/fire/firestore';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
imports: [
BrowserModule,
FormsModule,
AppRoutingModule,
provideFirebaseApp(() => initializeApp(firebase)), <--- provide firebase
provideAuth(() => {
const auth = getAuth();
// connectAuthEmulator(auth, 'http://localhost:9099')
return auth;
}),
provideFirestore(() => {
const firestore = getFirestore();
// connectFirestoreEmulator(firestore, 'localhost', 8080)
return firestore;
}),
],
declarations: [AppComponent, HelloComponent],
bootstrap: [AppComponent],
})
export class AppModule {}
app-routing.module.ts
import { NgModule } from '@angular/core';
import { User } from '@angular/fire/auth';
import { AuthGuard, AuthPipe, AuthPipeGenerator } from '@angular/fire/auth-guard';
import { Routes, RouterModule } from '@angular/router';
import { map } from 'rxjs';
const redirectUnauthorizedAndUnverifiedToAuth: AuthPipe = map((user: User | null) => {
// if not logged in, redirect to `auth`
// if logged in and email verified, allow redirect
// if logged in and email not verified, redirect to `auth/verify`
return !!user ? (user.emailVerified ? true : ['auth', 'verify']) : ['auth']
})
const authPipeGenerator: AuthPipeGenerator = () => redirectUnauthorizedAndUnverifiedToAuth
const routes: Routes = [
{
path: "auth",
loadChildren: () => import("./auth/auth.module").then(m => m.AuthModule)
},
{
path: "my-feature",
loadChildren: () => import("./my-feature/my-feature.module").then(m => m.MyFeatureModule),
canActivate: [AuthGuard],
data: { authGuardPipe: redirectUnauthorizedAndUnverifiedToAuth }
},
{ path: "", redirectTo: "auth", pathMatch: "full" },
{ path: "**", redirectTo: "auth" }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Expected behavior
if you provide pipe this should work esp. given the name authGuardPipe
{
path: "my-feature",
loadChildren: () => import("./my-feature/my-feature.module").then(m => m.MyFeatureModule),
canActivate: [AuthGuard],
data: { authGuardPipe: redirectUnauthorizedAndUnverifiedToAuth }
},
Actual behavior
Error TypeError: Unable to lift unknown Observable type
This issue does not seem to follow the issue template. Make sure you provide all the required information.
Hey! Can you provide a repo or stackblitz that showcases this?
Confirming this is still an issue with v9.4. Since the error is swallowed, this had me spinning wheels for half a day before discovering this thread. Thank you @AdditionAddict
In my case, I have my pipe defined as a static class function rather than as a const:
export class AppAuthGuard extends AngularFireAuthGuard {
static hasPermissionPipe(role?: string): AuthPipe {
return pipe(
switchMap((user: firebase.User | null) => (user ? user.getIdTokenResult() : of(null))),
map(result =>
result
? // If signed in, check the user's role claim
!role || result.claims['role'] === role || ['forbidden']
: // Otherwise navigate to the sign-in page
['sign-in'],
),
);
}
static hasPermissionGenerator(role?: string): AuthPipeGenerator {
return () => AppAuthGuard.hasPermissionPipe(role);
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> {
return this.canActivate(route, state);
}
}
With the routes below, navigating to /administration and child paths works fine, whereas /crud does not. Stepping past a breakpoint in auth-guard.ts, I see the same TypeError: Unable to lift unknown Observable type.
const routes: Routes = [
{
path: 'administration',
data: { authGuardPipe: AppAuthGuard.hasPermissionGenerator('admin') },
canActivate: [AppAuthGuard],
canActivateChild: [AppAuthGuard],
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
},
{
path: 'crud',
data: { authGuardPipe: AppAuthGuard.hasPermissionPipe('admin') },
canActivate: [AppAuthGuard],
canActivateChild: [AppAuthGuard],
loadChildren: () => import('./crud/crud.module').then(m => m.CrudModule),
},
{
path: '',
loadChildren: () => import('./main/main.module').then(m => m.MainModule),
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
did you find a solution for this?
@rubenheymans opening post has "correct" answer, issue details unexpected usage
// changing this
const adminGuard = pipe(
customClaims,
map((claims) => claims.role === 'admin' || ['login'])
);
// to this, fixes the issue for me
const adminGuard = () =>
pipe(
customClaims,
map((claims) => claims.role === 'admin' || ['login'])
);