Angular 5 canActivate重定向以在浏览器刷新时登录 [英] Angular 5 canActivate redirecting to login on browser refresh
问题描述
使用angularfire2和firebase的Angular 5身份验证应用程序. 该应用可以使用应用内链接(例如,登录后重定向到仪表板,或通过应用程序中的按钮/链接链接到另一个页面(组件). 但是,如果在" http://localhost:4300/dashboard "页面上时,我点击了浏览器刷新( Chrome),它将我重定向回登录"页面. 在浏览器上使用BACK/NEXT可以正常工作-但我想是因为我并不是专门要求去特定的路线.
我有一个NavBar,可以通过使用订阅来标识我是否已登录(请参见右上方的屏幕截图...)-一切正常.
我猜测是在浏览器刷新或直接URL导航上,它会尝试在确定我是否已通过身份验证之前加载页面. 开发控制台从我插入到nav-bar组件中的console.log语句中建议了这一点,并且在Angular核心表明我们在开发模式下运行之前它们是未定义的"事实:
应用路由:
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './views/login/login.component';
import { DashboardComponent } from './views/dashboard/dashboard.component';
import { ProfileComponent } from './views/profile/profile.component';
import { AuthGuard } from './services/auth-guard.service';
const appRoutes: Routes = [
{
path: '',
component: LoginComponent
},
{
path: 'dashboard',
canActivate: [AuthGuard],
component: DashboardComponent
},
{
path: 'profile',
canActivate: [AuthGuard],
component: ProfileComponent
},
{
path: '**',
redirectTo: ''
}
];
export const AppRoutes = RouterModule.forRoot(appRoutes);
身份验证区:
import { AuthService } from './auth.service';
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
status: string;
constructor(private router: Router,
private authService: AuthService) { }
canActivate() {
this.authService.authState.subscribe(state =>
this.status = state.toString());
console.log('Can Activate ' + this.authService.authState);
console.log('Can Activate ' + this.authService.isLoggedIn());
console.log('Can Activate ' + this.status);
if(this.authService.isLoggedIn()) {
return true;
}
this.router.navigate(['/']);
return false;
}
}
身份验证服务:
import { Injectable } from '@angular/core';
import { Router } from "@angular/router";
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';
import { GoogleAuthProvider, GoogleAuthProvider_Instance } from '@firebase/auth-types';
import { userInfo } from 'os';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class AuthService {
private user: Observable<firebase.User>;
private userDetails: firebase.User = null;
public authState = new Subject();
constructor(private _firebaseAuth: AngularFireAuth, private router: Router) {
this.user = _firebaseAuth.authState;
this.user.subscribe((user) => {
if (user) {
this.userDetails = user;
this.authState.next('Logged In');
//console.log(this.userDetails);
} else {
this.userDetails = null;
this.authState.next('Not Logged In');
}
});
}
isLoggedIn() {
if (this.userDetails == null) {
return false;
} else {
return true;
}
}
}
导航栏.
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-nav-bar',
templateUrl: './nav-bar.component.html',
styleUrls: ['./nav-bar.component.css']
})
export class NavBarComponent implements OnInit {
status: string;
constructor(private authService: AuthService) {
console.log('Constructor ' + this.status);
}
ngOnInit() {
//this.authService.isLoggedIn().subscribe((state) => this.status = state.toString());
this.authService.authState.subscribe(state =>
this.status = state.toString());
console.log('ngOnInit ' + this.status);
}
}
canActivate()
方法在页面刷新时直接调用.因此它总是返回false
:
canActivate() {
this.authService.authState.subscribe(state => {
this.status = state.toString(); // This is called async/delayed.
});
// so method execution proceeds
// isLoggedIn() returns false since the login stuff in AuthService.constructor
// is also async: .subscribe((user) => { /* delayed login */ });
if(this.authService.isLoggedIn()) {
return true;
}
// so it comes here
this.router.navigate(['/']); // navigating to LoginComponent
return false; // and canActivate returns false
}
解决方案:
import { CanActivate, Router, ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
// ...
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
// when the user is logged in and just navigated to another route...
if (this.authService.isLoggedIn) { return true; }
// proceeds if not loggedIn or F5/page refresh
// Store the attempted URL for redirecting later
this.authService.redirectUrl = state.url;
// go login page
this.router.navigate(['/']);
return false;
}
现在,返回稍作更改的AuthService :(此处仅更改/相关代码)
export class AuthService {
// new
redirectUrl: string;
// BehaviorSubjects have an initial value.
// isLoggedIn is property (not function) now:
isLoggedIn = new BehaviorSubject<boolean>(false);
// params declared private and public in constructor become properties of the class
constructor(private firebaseAuth: AngularFireAuth, private router: Router) {
// so this.user is not required since it is reference to this.firebaseAuth
this.firebaseAuth.authState.subscribe((user) => {
if (user) {
this.loggedIn.next(true);
// NOW, when the callback from firebase came, and user is logged in,
// we can navigate to the attempted URL (if exists)
if(this.redirectUrl) {
this.router.navigate([this.redirectUrl]);
}
} else {
this.loggedIn.next(false);
}
}
}
}
注意:我已将此代码编写在答案框中,并已在大脑中进行编译.因此可能存在错误.我也不知道这是否是最佳实践.但是这个主意应该清楚吗?!
基于《角路由指南》
似乎存在类似的问题/解决方案: Angular 2 AuthGuard + Firebase Auth
Angular 5 authentication app using angularfire2 and firebase. The app works fine navigating using in-app links e.g. redirect to dashboard after login or link to another page (component) via a button/link in the app. However, if when on the "http://localhost:4300/dashboard" page I hit the browser refresh (Chrome), it redirects me back to the Login page. Using BACK / NEXT on the browser works fine - but I guess because I am not specifically asking to go to a particular route.
I have a NavBar that, through use of subscription, identifies whether I am logged in or not (see screenshot top right...) - and this all works fine.
I am guessing that on browser refresh or direct URL navigation that it tries to load the page before identifying whether I am already authenticated or not. The dev console suggests this from the console.log statements I inserted into the nav-bar component and the fact they are "undefined" before Angular core suggests we are running in dev mode:
app.routes:
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './views/login/login.component';
import { DashboardComponent } from './views/dashboard/dashboard.component';
import { ProfileComponent } from './views/profile/profile.component';
import { AuthGuard } from './services/auth-guard.service';
const appRoutes: Routes = [
{
path: '',
component: LoginComponent
},
{
path: 'dashboard',
canActivate: [AuthGuard],
component: DashboardComponent
},
{
path: 'profile',
canActivate: [AuthGuard],
component: ProfileComponent
},
{
path: '**',
redirectTo: ''
}
];
export const AppRoutes = RouterModule.forRoot(appRoutes);
auth-gaurd:
import { AuthService } from './auth.service';
import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
@Injectable()
export class AuthGuard implements CanActivate {
status: string;
constructor(private router: Router,
private authService: AuthService) { }
canActivate() {
this.authService.authState.subscribe(state =>
this.status = state.toString());
console.log('Can Activate ' + this.authService.authState);
console.log('Can Activate ' + this.authService.isLoggedIn());
console.log('Can Activate ' + this.status);
if(this.authService.isLoggedIn()) {
return true;
}
this.router.navigate(['/']);
return false;
}
}
auth.service:
import { Injectable } from '@angular/core';
import { Router } from "@angular/router";
import { AngularFireAuth } from 'angularfire2/auth';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';
import { GoogleAuthProvider, GoogleAuthProvider_Instance } from '@firebase/auth-types';
import { userInfo } from 'os';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class AuthService {
private user: Observable<firebase.User>;
private userDetails: firebase.User = null;
public authState = new Subject();
constructor(private _firebaseAuth: AngularFireAuth, private router: Router) {
this.user = _firebaseAuth.authState;
this.user.subscribe((user) => {
if (user) {
this.userDetails = user;
this.authState.next('Logged In');
//console.log(this.userDetails);
} else {
this.userDetails = null;
this.authState.next('Not Logged In');
}
});
}
isLoggedIn() {
if (this.userDetails == null) {
return false;
} else {
return true;
}
}
}
nav-bar.component:
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-nav-bar',
templateUrl: './nav-bar.component.html',
styleUrls: ['./nav-bar.component.css']
})
export class NavBarComponent implements OnInit {
status: string;
constructor(private authService: AuthService) {
console.log('Constructor ' + this.status);
}
ngOnInit() {
//this.authService.isLoggedIn().subscribe((state) => this.status = state.toString());
this.authService.authState.subscribe(state =>
this.status = state.toString());
console.log('ngOnInit ' + this.status);
}
}
The canActivate()
method is called directly on page refresh. So it always returns false
:
canActivate() {
this.authService.authState.subscribe(state => {
this.status = state.toString(); // This is called async/delayed.
});
// so method execution proceeds
// isLoggedIn() returns false since the login stuff in AuthService.constructor
// is also async: .subscribe((user) => { /* delayed login */ });
if(this.authService.isLoggedIn()) {
return true;
}
// so it comes here
this.router.navigate(['/']); // navigating to LoginComponent
return false; // and canActivate returns false
}
The solution:
import { CanActivate, Router, ActivatedRouteSnapshot,
RouterStateSnapshot } from '@angular/router';
// ...
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
// when the user is logged in and just navigated to another route...
if (this.authService.isLoggedIn) { return true; }
// proceeds if not loggedIn or F5/page refresh
// Store the attempted URL for redirecting later
this.authService.redirectUrl = state.url;
// go login page
this.router.navigate(['/']);
return false;
}
now, back in the little changed AuthService: (only have changed/relevant code here)
export class AuthService {
// new
redirectUrl: string;
// BehaviorSubjects have an initial value.
// isLoggedIn is property (not function) now:
isLoggedIn = new BehaviorSubject<boolean>(false);
// params declared private and public in constructor become properties of the class
constructor(private firebaseAuth: AngularFireAuth, private router: Router) {
// so this.user is not required since it is reference to this.firebaseAuth
this.firebaseAuth.authState.subscribe((user) => {
if (user) {
this.loggedIn.next(true);
// NOW, when the callback from firebase came, and user is logged in,
// we can navigate to the attempted URL (if exists)
if(this.redirectUrl) {
this.router.navigate([this.redirectUrl]);
}
} else {
this.loggedIn.next(false);
}
}
}
}
Note: I have written this code in the answer box and compiled it in my brain. So bugs may exist. Also I don't know if this is actually best practise. But the idea should be clear?!
Based on the Angular Routing Guide
Seems like there are similar problems/solutions out there: Angular 2 AuthGuard + Firebase Auth
这篇关于Angular 5 canActivate重定向以在浏览器刷新时登录的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!