Firebase提供email登入與註冊功能,前文(【Ionic 4】新頁面瀏覽機制:Angular Router)就註冊做介紹,一旦註冊成功,後續便可直接登入,進入個人頁面,例如dashboard等。然而,在Angular Router機制下,Ionic apps各個頁面皆有直接存取路徑,如'/login', '/dashboard';只要輸入路徑便可前往該頁面。因此,必須有保護路徑的情境設定,例如'/dashboard'要在登入之後才允許進入。Angular Router提供了route guards,如CanActivate等界面(interface),透過實做(implement)這些界面,便可達到保護路徑的目的。底下以Firebase登入/註冊,以及個人dashboard三個頁面為例,說明Route Guards的使用方式。
建立Ionic 4專案
使用Ionic CLI 4版新加入的選項--type=angular,建立使用Angular Routing機制(可參考前文【Ionic 4】新頁面瀏覽機制:Angular Router) 的專案,並新增所需頁面:
ionic start firebaseAuthExample blank --type=angular cd firebaseAuthExample ionic g page login ionic g page register ionic g service services/auth ionic g guard services/auth
隨後修正專案內容,並將src/app/app-routing.module.ts檔裡Routes變數之'redirectTo'選項,導向LoginPage。
const routes: Routes = [ { path: '', redirectTo: 'login', pathMatch: 'full' }, { path: 'home',loadChildren: './home/home.module#HomePageModule'}, { path: 'login', loadChildren: './login/login.module#LoginPageModule' }, { path: 'register', loadChildren: './register/register.module#RegisterPageModule' }, { path: '**', redirectTo: 'login'} ];此處'home'路徑需要保護,實做route guards(即前述ionic g guard services/auth指令) 便是要完成保護的目的。
步驟0:Firebase相關設定
Firebase專案的準備工作、修改src/environments/environment.ts以及app.module.ts等,可參考前文【Ionic 4】會員Email註冊-以Firebase為雲端平台(含FormBuilder表單與輸入驗證)的說明。
步驟1:編輯auth.guard.ts保護頁面
建立專案最後一步ionic g guard services/auth產生了services/auth.guard.ts,預設內容如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import {CanActivate,ActivatedRouteSnapshot,RouterStateSnapshot} from '@angular/router'; | |
import { Observable } from 'rxjs'; | |
@Injectable({ | |
providedIn: 'root', | |
}) | |
export class AuthGuard implements CanActivate { | |
constructor() { } | |
canActivate( | |
next: ActivatedRouteSnapshot, | |
state: RouterStateSnapshot | |
): boolean | Observable | Promise { | |
return true | |
} | |
} |
實做Route Guard界面CanActivate(第7行),用來決定某個路徑是否可以瀏覽;因此,canActivate()方法內(第9-14行),如果該路徑可以瀏覽,則回傳true;如不能瀏覽,則回傳false。實做完畢的auth guard,便可在app-routing.module.ts裡,用來保護路徑,例如保護'/home':
import { AuthGuard } from './services/auth.guard'; //... const routes: Routes = [ //..., { path: 'home', loadChildren: './home/home.module#HomePageModule', canActivate: [AuthGuard] }, //.. ]留意canActivate屬性值是陣列,可指定多個guards。
AuthGuard完整程式碼如下。裡面用到使用firebase auth api的onAuthStateChanged(),觀察使用者登入的狀態變化:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Injectable } from '@angular/core'; | |
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; | |
import { Observable } from 'rxjs'; | |
import { Router } from '@angular/router'; | |
import * as firebase from 'firebase/app'; | |
import 'firebase/auth'; | |
@Injectable({ | |
providedIn: 'root' | |
}) | |
export class AuthGuard implements CanActivate { | |
constructor(private router: Router) { } | |
canActivate( | |
next: ActivatedRouteSnapshot, | |
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { | |
return new Promise((resolve, reject) => { | |
firebase.auth().onAuthStateChanged((user: firebase.User) => { | |
if (user) { | |
resolve(true); | |
} else { | |
console.log('尚未登入'); | |
this.router.navigate(['/login']); | |
resolve(false); | |
} | |
}); | |
}) | |
} | |
} |
- 第6-7行:第6行先引入Firebase的核心功能('firebase/app'),並以firebase為namespace。第7行則引入auth功能。如此便可使用onAuthStateChanged(),觀察使用者登入狀態的改變(第18行)。
- 第17-26行:自行建立promise,當有使用者登入時,回傳true;若無,則頁面導向至'/login'。
步驟2:login頁面與auth登入服務
login頁面和前文【Ionic 4】會員Email註冊-以Firebase為雲端平台(含FormBuilder表單與輸入驗證)一樣,使用Reactive Forms設計表單、提供欄位輸入驗證。因此,同樣要先在login.module.ts檔引入ReactiveFormsModule
import { ReactiveFormsModule } from '@angular/forms';//... @NgModule({ imports: [ //..., ReactiveFormsModule, ], //... }) export class LoginPageModule {}
另外,表單送出時,則會呼叫firebase auth api的signInWithEmailAndPassword(),進行登入。故src/app/services/auth.service.ts要加入signIn()程式碼:
signIn(user){ return this.afAuth.auth.signInWithEmailAndPassword(user.email, user.password) .then(credential=>{ console.log('登入成功'); }) .catch(error=> { console.log('登入失敗',error); this.router.navigate(['/login']); }); }
若登入失敗,將回到login頁面。登入成功則不須做任何動作,因為在constructor()處,已經建立了Observable變數,currentUser,該變數透過FirebaseAuth的authState變數,觀察登入使用者的profile('users/$uid')資料變化:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//... | |
export class AuthService { | |
currentUser: Observable; | |
constructor( | |
private afAuth: AngularFireAuth, | |
private db: AngularFirestore, private router: Router | |
) { | |
this.currentUser = this.afAuth.authState.pipe( | |
switchMap(user => { | |
if(user) { | |
return this.db.doc(`users/${user.uid}`).valueChanges(); | |
} else { | |
return of(null); | |
} | |
}) | |
); | |
} | |
//.... | |
} |
- 第9-12行:訂閱authState資料流,再搭配switchMap與valueChanged()便可取得目前登入的使用者資料(switchMap確保換人登入後,資料流會置換掉舊的user profile,只保留新登入者;valueChanged()則是確保user profile為最新資料)。
完整專案詳見GitHub專案。
沒有留言:
張貼留言