AngularFirebaseAuth:在firebase auth之后调用服务器api?

Cét*_*tia 7 javascript rxjs firebase typescript angular

我的身份验证基于两件事:

  • firebase auth(电子邮件/密码)
  • 调用服务器API以从BDD和firebaseID检索完整的客户实体(用户必须存在)因此,如果满足这两个条件,用户将被"验证".

我还有基于isAuthenticated()返回Observable的authGuards (因为在页面刷新时,guard必须等待auth完成才能将用户重定向到任何地方).

问题:我无法找到一种方法来使所有async和rxjs混乱/地狱工作..目前它正在工作,但每次isAuthenticated调用,每次都调用serverAPI auth ...我怎么能按顺序重构只调用一次服务器,所有的异步/重载东西仍然有效吗?

AuthService:

export class AuthService {
    public userRole: UserBoRole;
    public authState$: Observable<firebase.User>;

    constructor(
        private afAuth: AngularFireAuth,
        private snackBar: SnackBarService,
        private translate: TranslateService,
        private router: Router,
        private grpcService: GrpcService
    ) {
        this.authState$ = this.afAuth.authState.pipe(
            take(1),
            mergeMap(user => {
                if (!user) {
                    return of(user);
                }

                // User is successfully logged in,
                // now we need to check if he has a correct role to access our app
                // if an error occured, consider our user has not logged in, so we return null
                return this.checkProfile().pipe(
                    take(1),
                    map(() => {
                        this.test = true;
                        return user;
                    }),
                    catchError(err => {
                        console.error(err);
                        return of(null);
                    })
                );
            })
        );

        // Subscribing to auth state change. (useless here because access logic is handled by the AuthGuard)
        this.authState$.subscribe(user => {
            console.log('authState$ changed :', user ? user.toJSON() : 'not logged in');
        });
    }

    checkProfile() {
        return this.callAuthApi().pipe(
            map((customer) => {
                if (!customer || customer.hasRole() === "anonymous") {
                    return Promise.reject(new Error(AuthService.AUTH_ERROR_ROLE));
                }
                this.userRole = customer.getRole();
            })
        );
    }

    isAuthenticated(): Observable<boolean> {
        return this.authState$.pipe(map(authState => !!authState));
    }
}
Run Code Online (Sandbox Code Playgroud)

AuthGuard:

export class AuthGuard implements CanActivate, CanActivateChild {
    constructor(private authService: AuthService, private router: Router) {}

    check(): Observable<boolean> {
        return this.authService.isAuthenticated().pipe(
            catchError(err => {
                // notifying UI of the error
                this.authService.handleAuthError(err);

                // signout user
                this.authService.signOut();

                // if an error occured, consider our user has not logged in
                return of(false);
            }),
            tap(isAuthenticated => {
                if (!isAuthenticated) {    
                    // redirecting to login
                    this.router.navigate(['login']);
                }
            })
        );
    }

    canActivateChild(): Observable<boolean> {
        return this.check();
    }

    canActivate(): Observable<boolean> {
        return this.check();
    }
}
Run Code Online (Sandbox Code Playgroud)

谢谢

Joh*_*ohn 1

哈哈,ReactiveX 并不容易。它有一个相当陡峭的学习曲线。但它确实很强大。

1.只调用一次服务器

您可以使用共享重播。

要了解 shareReplay 的工作原理,请查看此处https://ng-rxjs-share-replay.stackblitz.io

//shareReplay example
ngOnInit() {    
    const tods$ = this.getTodos();
    tods$.subscribe(console.log);// 1st sub
    tods$.subscribe(console.log);// 2st sub
}

getTodos(): Observable<Todo[]> {
return this.http.get<Todo[]>(this.url)
  .pipe(
    tap(() => console.log('Request')),
    shareReplay(1) // compare with comment and uncomment
  );
}  
Run Code Online (Sandbox Code Playgroud)

使用 shareReplay 输出

Request
[Object, Object, Object]
[Object, Object, Object]
Run Code Online (Sandbox Code Playgroud)

不带 shareReplay 的输出

Request
[Object, Object, Object]
Request
[Object, Object, Object]
Run Code Online (Sandbox Code Playgroud)

您可以在您的身份验证服务代码中使用 shareReplay。

//auth.services.ts
import { shareReplay } from 'rxjs/operators';
...

this.user$ = this.afAuth.authState.pipe(
    tap(user => {
        console.log('login user$ here', user)
    }),
    switchMap(user => {
        if (user) {
            //do something
            return this.db.object(`users/${user.uid}`).valueChanges();
        } else {
            return of(null);
        }
    }),
    shareReplay(1)  //**** this will prevent unnecessary request****
);
Run Code Online (Sandbox Code Playgroud)

2. 异步和等待 toPromise()

//auth.service.ts
...
getUser() {
    return this.user$.pipe(first()).toPromise();
}

//auth.guard.ts
...
async canActivate(next: ActivatedRouteSnapshot
  , state: RouterStateSnapshot
): Promise<boolean> {

  const user = await this.auth.getUser();
  //TODO your API code or other conditional authentication here

  if (!user) {
    this.router.navigate(['/login']);
  }
  return !!user;    
}
Run Code Online (Sandbox Code Playgroud)

希望对你有帮助。