Angularfire: 5.2.1 AngularFireAuthGuard causes TypeError: source.lift is not a function

Created on 2 Jun 2019  Β·  39Comments  Β·  Source: angular/angularfire

Performing basic docs for AngularFireAuthGuard produces an error related to source.lift.

Version info

sputnik:testauthguard katowulf$ npm list --depth 0
[email protected] /Users/katowulf/projects/testauthguard
β”œβ”€β”€ @angular-devkit/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @angular/[email protected]
β”œβ”€β”€ @types/[email protected]
β”œβ”€β”€ @types/[email protected]
β”œβ”€β”€ @types/[email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
β”œβ”€β”€ [email protected]
└── [email protected]

sputnik:testauthguard katowulf$ ng version

Angular CLI: 7.1.4
Node: 8.9.3
OS: darwin x64
Angular: 7.1.4
... animations, cli, common, compiler, compiler-cli, core, forms
... language-service, platform-browser, platform-browser-dynamic
... router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.11.4
@angular-devkit/build-angular     0.11.4
@angular-devkit/build-optimizer   0.11.4
@angular-devkit/build-webpack     0.11.4
@angular-devkit/core              7.1.4
@angular-devkit/schematics        7.1.4
@angular/fire                     5.2.1
@ngtools/webpack                  7.1.4
@schematics/angular               7.1.4
@schematics/update                0.11.4
rxjs                              6.3.3
typescript                        3.1.6
webpack                           4.23.1

package.json

{
  "name": "testauthguard",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~7.1.0",
    "@angular/common": "~7.1.0",
    "@angular/compiler": "~7.1.0",
    "@angular/core": "~7.1.0",
    "@angular/fire": "^5.2.1",
    "@angular/forms": "~7.1.0",
    "@angular/platform-browser": "~7.1.0",
    "@angular/platform-browser-dynamic": "~7.1.0",
    "@angular/router": "~7.1.0",
    "core-js": "^2.5.4",
    "firebase": "^6.1.0",
    "rxjs": "~6.3.3",
    "tslib": "^1.9.0",
    "zone.js": "~0.8.26"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~0.11.0",
    "@angular/cli": "~7.1.0",
    "@angular/compiler-cli": "~7.1.0",
    "@angular/language-service": "~7.1.0",
    "@types/node": "~8.9.4",
    "@types/jasmine": "~2.8.8",
    "@types/jasminewd2": "~2.0.3",
    "codelyzer": "~4.5.0",
    "jasmine-core": "~2.99.1",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~3.1.1",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "~2.0.1",
    "karma-jasmine": "~1.1.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.4.0",
    "ts-node": "~7.0.0",
    "tslint": "~5.11.0",
    "typescript": "~3.1.6"
  }
}

How to reproduce these conditions

Failing test unit, Plunkr, or JSFiddle demonstrating the problem

testauthguard.zip
Gist of app.module.ts and app-routing.module.ts here.

Steps to set up and reproduce

  1. Download and decompress zip file
  2. npm install
  3. npm start

How I created the contents of the zip:

ng new testauthguard
cd testauthguard
npm install @angular/fire firebase --save
ng generate component test
ng generate component login

Then I modified auth-routing.module.ts as indicated in the docs for AuthGuard.

Sample data and security rules

n/a

Debug output

* Errors in the JavaScript console *

ERROR Error: Uncaught (in promise): TypeError: source.lift is not a function
TypeError: source.lift is not a function
    at mapOperation (map.js:9)
    at pipe.js:18
    at Array.reduce (<anonymous>)
    at piped (pipe.js:18)
    at AngularFireAuthGuard.push../node_modules/@angular/fire/auth-guard/auth-guard.js.AngularFireAuthGuard.canActivate (auth-guard.js:23)
    at router.js:3097
    at Observable._subscribe (defer.js:9)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable._trySubscribe (Observable.js:43)
    at Observable.push../node_modules/rxjs/_esm5/internal/Observable.js.Observable.subscribe (Observable.js:29)
    at TakeOperator.push../node_modules/rxjs/_esm5/internal/operators/take.js.TakeOperator.call (take.js:24)
    at resolvePromise (zone.js:831)
    at resolvePromise (zone.js:788)
    at zone.js:892
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:423)
    at Object.onInvokeTask (core.js:16147)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:422)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:195)
    at drainMicroTaskQueue (zone.js:601)
defaultErrorLogger @ core.js:14597
push../node_modules/@angular/core/fesm5/core.js.ErrorHandler.handleError @ core.js:14645
next @ core.js:16628
schedulerFn @ core.js:12609
push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:196
push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next @ Subscriber.js:134
push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next @ Subscriber.js:77
push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next @ Subscriber.js:54
push../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next @ Subject.js:47
push../node_modules/@angular/core/fesm5/core.js.EventEmitter.emit @ core.js:12593
(anonymous) @ core.js:16178
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:391
push../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:150
push../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular @ core.js:16115
onHandleError @ core.js:16178
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.handleError @ zone.js:395
push../node_modules/zone.js/dist/zone.js.Zone.runGuarded @ zone.js:164
_loop_1 @ zone.js:694
api.microtaskDrainDone @ zone.js:703
drainMicroTaskQueue @ zone.js:608
Promise.then (async)
scheduleMicroTask @ zone.js:584
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.scheduleTask @ zone.js:413
push../node_modules/zone.js/dist/zone.js.Zone.scheduleTask @ zone.js:238
push../node_modules/zone.js/dist/zone.js.Zone.scheduleMicroTask @ zone.js:258
scheduleResolveOrReject @ zone.js:879
ZoneAwarePromise.then @ zone.js:1012
push../node_modules/@angular/core/fesm5/core.js.PlatformRef.bootstrapModule @ core.js:16660
./src/main.ts @ main.ts:11
__webpack_require__ @ bootstrap:78
0 @ main.ts:12
__webpack_require__ @ bootstrap:78
checkDeferredModules @ bootstrap:45
webpackJsonpCallback @ bootstrap:32
(anonymous) @ main.js:1

* Output from firebase.database().enableLogging(true); *

n/a

* Screenshots *

Screen Shot 2019-06-02 at 1 21 13 PM

Expected behavior

Adding Auth guard should not generate an error for such a simple repro directly copying the docs.

Actual behavior

Addition of Auth guard causes error. Commenting it out removes the error.

Most helpful comment

I worked in this way

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(["login"]);

const routes: Routes = [
  {
    path: "home",
    children: [],
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin }
  }
]

All 39 comments

Hmmm, I'll check this out, thanks for the repro Kato.

If I use ... canActivate (redirectUnauthorizedToLogin) will work perfectly.
But it is only working on localhost.
When the build is done --prod does not work
The route guardian is allowing access to the routes

ng serve localhost - work
ng build localhost - work
ng build firebase - work
ng build --prod localhost - fail
ng build --prod firebase - fail

i got the same error msg when using angularfireauthguard even when using ... canActivate (redirectUnauthorizedToLogin)

I have the same issue either with canActivate or authGuardPipe.

Having the same issue

I have the same issue

Also having the same issue

Π£ мСня такая ΠΆΠ΅ ошибка ΠΏΡ€ΠΈ Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΈ data: { authGuardPipe: redirectUnauthorizedToLogin }

...canActivate(redirectUnauthorizedToLogin) ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΡ это всё Ρ€Π΅ΡˆΠ°Π΅Ρ‚

was so excited to implement the new and simplified auth guard... and that excitement landed me here with no solution , hope it gets resolved soon

I'll be looking at this this week. Will aim to cut 5.2.2 with a fix for AOT ASAP.

I worked in this way

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(["login"]);

const routes: Routes = [
  {
    path: "home",
    children: [],
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin }
  }
]

Great lisp719 ! Your solution worked for me too! Thanks!!!!

I was also able to get this working using the canActivate helper.

const redirectUnauthorizedToLogin = redirectUnauthorizedTo(['login']);

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'home', loadChildren: './home/home.module#HomePageModule', ...canActivate(redirectUnauthorizedToLogin) },
  { path: 'login', loadChildren: './login/login.module#LoginPageModule' },
];

EDIT:
This actually failed to work when running in production mode. I seemed to have the same issue noted here: https://github.com/angular/angularfire2/issues/2114

For now the solution provided by @lisp719 works the best for me, even in production mode.

These work locally and prod/SSR πŸ‘

const isUser = () => pipe(map(user => {
  return !!user ? !!user : ['/'];
}));

const isAdmin = () => pipe(customClaims, map(claims => {
  return claims.admin === true ? claims.admin : ['/'];
}));

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'login', component: LoginComponent },
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: isAdmin }
  },
  {
    path: 'profile',
    component: ProfileComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: isUser }
  },
  { path: '**', component: ErrorComponent }
];

I worked in this way

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(["login"]);

const routes: Routes = [
  {
    path: "home",
    children: [],
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin }
  }
]

Great, thanks for your help!

Im trying to use the AF AuthGuard and I'm trying to protect a route from not logged in users and not admin users. To redirect the users when not logged in I use const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']); but how do I choose where to redirect the users when not admin?

What about redirectLoggedInTo. How can I implement this?

I had same issue and is not resolved by solution proposed, my routes are:

const redirectUnauthorizedToLogin = redirectUnauthorizedTo(['access']);

const appRoutes: Routes = [
    {
        path: 'access',
        loadChildren: () => import('./main/authentication/auth.module').then(m => m.AuthModule),
    },
    {
        path: '',
        loadChildren: () => import('./main/pages/pages.module').then( m => m.PagesModule),
        ...canActivate(redirectUnauthorizedToLogin)
    },
    {
        path        : '**',
        redirectTo: 'access'
    }
];

AuthModule and PagesModule had GuardRedirects also.
Everything worked perfectly while serving, but when build with prod configuration guards did not trigger, neither this way or this other:
canActivate: [AngularFireAuthGuard], data: { authGuardPipe: redirectUnauthorizedToLogin}

As the only difference between scenarios is build options, i tryed by test/error removing options.
Removing this two finally worked:
"aot": true, "buildOptimizer": true,

Followed @jrodl3r comment and created own redirects.

const redirectUnauthorizedToLogin = () => map(user => !user ? ['login'] : true );
const redirectLoggedInToHome = () => map(user => !!user ? [''] : true );

. . .

const routes: Routes = [
  {
    path: '',
    component: HomePageComponent,
    canActivate: [AngularFireAuthGuard],
    data: { authGuardPipe: redirectUnauthorizedToLogin }
  },

etc...

Seems to work locally and in prod.

It works! Thank you @third-estate !

Any news on this? Its a bit odd to have the functionality straight from your documentation not working at all.

This is the point

const redirectUnauthorizedToLogin = redirectUnauthorizedTo(['login']);

The docs now use the work around for AOT,

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']);

https://github.com/angular/angularfire/blob/master/docs/auth/router-guards.md

To me, it seems like a similar issue posted on the Angular repo about using concat in route guards. I think this may be an Angular bug... https://github.com/angular/angular/issues/29374

@lisp719 's comment was what worked for me as well.

Any suggestions for using redirectLoggedInTo? Using the following code helper doesn't seem to work:

const redirectLoggedInToDashboard = () => redirectLoggedInTo(['dashboard']);

...canActivate(redirectLoggedInToDashboard)

I get the same TypeError: source.lift is not a function here.

`
import { redirectUnauthorizedTo, canActivate, AngularFireAuthGuard } from '@angular/fire/auth-guard';

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']);

const routes: Routes = [
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{ path: 'home',
loadChildren: './pages/home/home.module#HomePageModule',
canActivate: [AngularFireAuthGuard],
data: { authGuardPipe: redirectUnauthorizedToLogin }
},
{ path: 'entrance', loadChildren: './pages/entrance/entrance.module#EntrancePageModule' },
{ path: 'register', loadChildren: './pages/register/register.module#RegisterPageModule' },
{ path: 'login', loadChildren: './pages/login/login.module#LoginPageModule' }
];
`
I have it this way, no error in console and doesn't redirect unauthorized users. Any help please? Its the only thing remaining with my project.

hello guys, how would i create a pipe that redirects the user if the email is not yet verified?

const redirectOrVerifyEmail = () =>
  map((user: any) => {
    return user && user.emailVerified ? ['speeches'] : ['auth/verify-email'];
  });

i've tried the code above but it returns an error

ERROR Error: "Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'auth/verify-email'

hello guys, how would i create a pipe that redirects the user if the email is not yet verified?

const redirectOrVerifyEmail = () =>
  map((user: any) => {
    return user && user.emailVerified ? ['speeches'] : ['auth/verify-email'];
  });

i've tried the code above but it returns an error

ERROR Error: "Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'auth/verify-email'

I think this does not belong to this issue. This solution should work but perhaps with this syntax:
['auth', 'verify-email']
or there is a mistake in your routing.

I think the problem is this line
https://github.com/angular/angularfire/blob/master/src/auth-guard/auth-guard.ts#L28

authGuardPipe: pipe.name === ""

what the ...canActivate( needs to know if the function that is being passed is a factory function or simply a rxjs pipe.

To do this they used pipe.name === '' the problem is that in the latest version of rxjs the returned function name of the rxjs is piped which means that the ...canActivate function tries to run pass the factory function to the rxjs pipe and it fails.

I tried copying the canActivate function and changing to`pipe.name === 'piped' which works in development mode, but then fails in production, which i think is because of minification.

So the solution would be to change how to know when the function passed to canActivate is a factory function and when it's just a observable stream.

Anyhow, the solution with skipping using the ...canActivate function should work.

Still having the same issues, it's now 2020 is this getting fixed? if not it should be removed from the docs temporarily

After playing around with the source code I have found somewhat what is causing this error.
On these lines:
https://github.com/angular/angularfire/blob/master/src/auth-guard/auth-guard.ts#L27-L29

You will notice the ternary of pipe.name === '' ? pipe : () => pipe
Well it turns out when this resolves to () => pipe it causes the issue.
However if you remove the ternary and resolve to pipe then the issue does not occur.
My question is why would you need a function that returns the pipe?
There isn't a comment in this section to describe why a function would be needed.
Maybe it should have been pipe.name !== '' ? pipe : () => pipe?

Either way I will be using this custom helper in my code till this is resolved.

const canActivate = (pipe: AuthPipe | AuthPipeGenerator) => ({
  canActivate: [AngularFireAuthGuard],
  data: { authGuardPipe: pipe.name !== '' ? pipe : () => pipe },
});

Update:
This code seems to work for me without the source.lift error.
Notice how the redirectUnauthorizedToLogin and other definitions are not () => redirectUnauthorizedTo(['']) but instead just redirectUnauthorizedTo([''])
I believe the issue does lie with that turnery giving false positives. It seems like it was intending to check if it was in this format or the () => pipe format already but failing to do so with the pipe.name check.

const redirectUnauthorizedToLogin = redirectUnauthorizedTo(['']);
const redirectLoginToDashboard = redirectLoggedInTo(['dashboard']);
const adminOnly = hasCustomClaim('admin');

const routes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('@story-squad/pages').then((m) => m.AdminModule),
    ...canActivate(adminOnly),
  },
  {
    path: 'dashboard',
    loadChildren: () => import('@story-squad/pages').then((m) => m.DashboardModule),
    ...canActivate(redirectUnauthorizedToLogin),
  },
  {
    path: '',
    loadChildren: () => import('@story-squad/pages').then((m) => m.LoginModule),
    ...canActivate(redirectLoginToDashboard),
  },
];

Update:

After testing in production it seems that pipe.name is always an empty string. This appears to be the difference in behavior between production and development, thus I believe something like this would work in both production and development.

const canActivate = (authGuardPipe: AuthPipe | AuthPipeGenerator) => ({
  canActivate: [AngularFireAuthGuard],
  data: { authGuardPipe },
});

This will require you to use this format when declaring the pipes:

const adminOnly = () => hasCustomClaim('admin');

I resolved the problem by replacing
...canActivate(redirectUnauthorizedToLogin)
to
...canActivate(redirectUnauthorizedToLogin())

I resolved the problem by replacing
...canActivate(redirectUnauthorizedToLogin)
to
...canActivate(redirectUnauthorizedToLogin())

That would the the equivalent of just just not use the () => pipe syntax when defineing redirectUnauthorizedToLogin

const example1 = () => redirectUnauthorizedTo(['']);
const example2 = redirectUnauthorizedTo(['']);

// These would resolve the same
example1();
example2;

In my testing the biggest problem is how the pipe.name !== '' resolves in canActivate
In Development example1 would yield example1 where as example2 would yield piped
In both cases it would not satisfy pipe.name !== '' this the example1 getting () => added to it which does not seem intended.

However in my testing of production mode (which can be tested with ng serve --prod) pipe.name always resolves as '' which would break example2 since it requires the () => to be added to it to work.

So the result differs in development and production thus making this helper function not usable as is.
What is required is either dropping the pipe.name !== '' or thinking of a better way to determine if pipe is already a function.

Yes i got around a bunch of these issues with the lazy solution of wrapping things in a lodash _.constant.
Which is just a value function.

If actually fixing the issue is a problem. Why not just add the different syntax to the readme? Its not like its significantly more complicated, its like one extra line of code compared to the ...canActivate() method.

I resolved the problem by replacing
...canActivate(redirectUnauthorizedToLogin)
to
...canActivate(redirectUnauthorizedToLogin())

That would the the equivalent of just just not use the () => pipe syntax when defineing redirectUnauthorizedToLogin

Thanks to @ismailkoksal & @wSedlacek: ng build --prod is successful using either solution.

The docs now use the work around for AOT,

const redirectUnauthorizedToLogin = () => redirectUnauthorizedTo(['login']);

https://github.com/angular/angularfire/blob/master/docs/auth/router-guards.md

Can say that this still the solution for this issue..

Should be addressed with our pipe changes in 6.0

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mypark picture mypark  Β·  3Comments

itisparas picture itisparas  Β·  3Comments

cre8 picture cre8  Β·  3Comments

goekaypamuk picture goekaypamuk  Β·  3Comments

Leanvitale picture Leanvitale  Β·  3Comments