Lesson 8: Authentication with Firebase
Revised on 2025/12
Ionic v8.0.0
Angular v20.0.1
@angular/fire v7.21.0
firebase v9.21.0
(1)啟用Firebase Authentication的Email認證服務
前往 https://firebase.google.com/
(2) 呼叫firebase提供的API撰寫「登入/註冊/登出」功能
(3) 在頁面使用
引入服務、叫用「登入/註冊/登出」功能
ionic g service services/auth例:
進入專案後...
編輯要啟用的供應商
需符合各供應商的登入連線要求
確認已啟用
直接輸入電子郵件與密碼
(1) 呼叫firebase提供的API撰寫「登入/註冊/登出」功能
(2) 在頁面使用
引入服務、叫用「登入/註冊/登出」功能
Email/password登入
登入後顯示帳號資訊(預設只有email)
紀錄登入狀態?
取決於AuthState
登入認證連線至Firebase
cd loginApp
ionic g page login
ionic g page register1.建立空白專案
ionic start loginApp blank2.新增所需頁面
HomePage
已登入時,則至首頁
LoginPage
未登入時,則顯示登入畫面
RegisterPage
註冊頁面
AuthService
連結Firebase進行登入驗證
ionic g service services/auth.service3.新增登入認證service
6.安裝firebase, @angular/fire套件
npm i -g @angular/cli
npm i firebase
ng add @angular/fire如尚未安裝
ng add @angular/fire需依序回答下列問題
回傳使用資料: No
選擇欲設定的功能: Authentication(登入認證), Firestore(資料庫)
ng add @angular/fire選擇登入Firebase的Google帳號(應與前面Firebase主控台步驟相同)
選取先前建立的專案名稱
選取先前建立的[應用程式]名稱
自動更新app.module.ts
注意:自動更新的
app.module.ts有錯誤
//...[省略]...
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule],
providers: [{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
provideFirebaseApp(() => initializeApp(
{ projectId: "dbexample-512b4",
...[省略]...
messagingSenderId: "776927995954",
projectNumber: "....",
version: 2
})),
provideAuth(() => getAuth()), provideFirestore(() => getFirestore())],
bootstrap: [AppComponent],
})
// ...[省略]...刪除這兩個屬性,目前版本已不支援
src/app/app.module.ts
...
import { Auth, signInWithEmailAndPassword, signOut, User, user } from '@angular/fire/auth';
...
auth.service.ts片段
angular/fire/auth: 提供Firebase認證函式
...
export class AuthService {
router: Router = inject(Router);
// 登入狀態(authState)
auth: Auth = inject(Auth);
user$ = user(this.auth); // 建立登入狀態Observable, 狀態改變時會發出通知
currentUser: User | null = this.auth.currentUser; // 登入使用者資訊
constructor() { }
login(mail: string, password: string) { /* 功能1. 登入(email登入)*/
signInWithEmailAndPassword(this.auth, mail, password).then( uc=> {
console.log('current user:', uc.user); // 印出目前登入使用者的資訊
this.router.navigate(['home']); // 登入成功後,前往個人首頁
}).catch( error=> {
/* 帳密驗證失敗 */
console.log('帳密輸入錯誤', error);
});
}
logout(): Promise<any> { /* 功能2. 登出(所有登出)*/
return signOut(this.auth);
}
}auth.service.ts片段
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { Router } from '@angular/router';
import { User } from '@angular/fire/auth';
...
export class LoginPage implements OnInit {
email= ''; // email欄位
password=''; // 密碼欄位
currentUser: User | null = null; // 目前登入之使用者
userSubscription: Subscription; // 暫存subscription, 可用於取消訂閱
constructor(public as: AuthService, public router: Router) {
// 訂閱 user$ 以便隨時更新本地的 currentUser 變數
// 隨時知道當前是誰登入,或者使用者是否已登出
this.userSubscription = this.as.user$.subscribe((aUser: User | null) => {
this.currentUser = aUser; // 登入之使用者
console.log('current user', this.currentUser);
});
}
ngOnInit(){}
// 登入callback
signIn(email: string, password: string) {
this.as.login(email, password); // 呼叫service
}
}login.page.ts 片段
訂閱user$,authState streaming
<ion-header>
<ion-toolbar>
<ion-title>登入</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-item>
<ion-label position="floating">Email Address</ion-label>
<ion-input type="text" [(ngModel)]="email"></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">密碼</ion-label>
<ion-input type="password" [(ngModel)]="password"></ion-input>
</ion-item>
<ion-button expand="block" (click)='signIn(email, password)'>
登入
</ion-button>
</ion-content>
login.page.html
登入按鈕
帳號、密碼欄位
export interface User {
uid: string;
email: string;
displayName: string;
photoURL: string;
emailVerified: boolean;
}在src資料夾建立models/user.ts
此五個欄位是Firebase內建的五個欄位
方便與Firebase資料庫認證串連
import { Component } from '@angular/core';
import { AuthService } from '../services/auth.service';
import { User } from '@angular/fire/auth';
@Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
standalone: false,
})
export class HomePage {
currentUser: User | null = null; // 登入之user, 得自訂閱服務
constructor(public as: AuthService, public router: Router) {
this.as.user$.subscribe( (user: User|null) => {
console.log('登入使用者:', user)
this.currentUser = user;
})
}
signOut() { // 登出callback function
this.as.logout().then(()=>{
this.router.navigate(['/login']);
}).catch(e=>{
console.log('登出失敗', e);
});
}
}home.page.ts片段
<ion-header [translucent]="true">
<ion-toolbar>
<ion-title>
首頁
</ion-title>
<ion-buttons slot="end"> <!-- 登出按鈕-->
<ion-button (click)="signOut()">
<ion-icon slot="start" name="log-out"></ion-icon>
登出
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content [fullscreen]="true">
歡迎 {{ currentUser?.email }}
</ion-content>
home.page.html
...
export class AuthService {
auth: Auth = inject(Auth);
router: Router = inject(Router);
// 建立一個 Observable,當登入狀態改變時會發出通知
user$ = user(this.auth);
currentUser: User | null = this.auth.currentUser;
constructor() {}
....
// 功能3. 註冊新使用者
registerUser(mail: string, password: string) {
createUserWithEmailAndPassword(this.auth, mail, password).then(uc => {
console.log('註冊使用者:', uc.user); /* 註冊成功, 印出使用者 */
this.router.navigate(['login']); /* 前往登入畫面 */
}).catch(error => {
console.log('帳密註冊失敗', error);
});
}
...
}auth.service.ts片段
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { ConfirmedValidator } from '../helpers/confirm.validator';
....
export class RegisterPage implements OnInit {
// 使用 ! 告訴 TS 這會在 ngOnInit 初始化
registerForm!: FormGroup; // HTML表單
formErrors: Record<string, string> = { // 錯誤訊息的空白物件
email: '', // 注意:每一個欄位名稱必須與 //
password: '', // registerForm建立時的欄位 //
confirm: '' // 名稱相同 //
};
// errorMessages 的定義,讓它允許字串索引
errorMessages: { [key: string]: { [key: string]: string } } = { // 想要顯示在畫面上的「錯誤訊息」
'email': {
'required': '必填欄位',
'email': '請照電子郵件格式填入'
},
'password': {
'required': '必填欄位',
'minlength': '密碼最少8碼'
},
'confirm': {
'required': '必填欄位',
'confirmedValidator': '重新輸入密碼不符'
}
};
// ....未完,續下一頁...
}
register.page.ts片段(1/3)
import { AbstractControl, ValidationErrors } from '@angular/forms';
export function ConfirmedValidator(controlName: string, matchingControlName: string) {
return (formGroup: AbstractControl): ValidationErrors | null => {
const control = formGroup.get(controlName); // 第一個欄位
const matchingControl = formGroup.get(matchingControlName); // 比對欄位
if (matchingControl?.errors && !matchingControl.errors['confirmedValidator']) {
// 若有其他錯誤,則直接return
return null;
}
// 根據比對結果 設定matchingControl錯誤狀態
if (control?.value !== matchingControl?.value) { // 兩者不相等
matchingControl?.setErrors({ confirmedValidator: true });
return { confirmedValidator: true };
} else {
matchingControl?.setErrors(null);
return null;
}
}
}函式功能:比對兩個欄位的內容是否相同
helpers/confirm.validator.ts
手動建立 helpers/confirm.validator.ts
import { AuthService } from './../services/auth/auth.service';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { ConfirmedValidator } from '../helpers/confirm.validator';
....
export class RegisterPage implements OnInit {
// ...續上一頁(2/3)
constructor(private builder: FormBuilder,
public router: Router, public as: AuthService) { }
ngOnInit() {
this.buildForm();
}
//使用FormBuilder服務建立表單,並訂閱表單資料變動資料流
private buildForm() {
this.registerForm = this.builder.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]],
confirm: ['', [Validators.required]]
}, {
validators: [ConfirmedValidator('password', 'confirm')]
});
// 訂閱表單「輸入資料變動」的資料流,以便變動發生時,可於表單畫面即時回應錯誤訊息
this.registerForm.valueChanges.subscribe(data => this.onValueChanged(data));
}
// ...未完-續上一頁...
}register.page.ts片段(2/3)
export class RegisterPage implements OnInit {
// ...續上一頁(3/3)
private onValueChanged(data?: any) {
if (!this.registerForm) return; // 若無registerForm則直接回傳
this.formErrors = { email: '', password: '', confirm: '' }; // 1. 先清空該欄位的錯誤訊息
for (const field in this.formErrors) { // 走訪formErrors裡的每一個欄位
const control = this.registerForm.get(field); // 透過欄位名稱取得表單真正的欄位
if (control && control.dirty && control.invalid) {
// (存在 且 已編輯過 且 不正確) => 代表出現錯誤
const messages = this.errorMessages[field]; // 取得該欄位所有的錯誤訊息
console.log('message:', messages)
for (const key in control.errors) { // 走訪該欄位所有錯誤的狀況
this.formErrors[field] += messages[key];
}
}
}
}
// 註冊
register() {
const formValue = this.registerForm.value;
const email = formValue.email;
const password = formValue.password;
if (email && password) {
this.as.registerUser(email, password);
}
}
// 回到首頁
home() {
this.router.navigate(['home']);
}
}register.page.ts片段(3/3)
export class RegisterPage implements OnInit {
...
formErrors: Record<string, string> = {
email: '', // 注意:每一個欄位名稱必須與 //
password: '', // registerForm建立時的欄位 //
confirm: '' // 名稱相同 //
};
...
}
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { RegisterPageRoutingModule } from './register-routing.module';
import { RegisterPage } from './register.page';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
IonicModule,
RegisterPageRoutingModule
],
declarations: [RegisterPage]
})
export class RegisterPageModule {}register.module.ts
為了在頁面使用表單,必須引入FormsModule, ReactiveFormsModule
<ion-content class="ion-padding">
<form [formGroup]="registerForm" (ngSubmit)="register()">
<ion-card>
<ion-card-content>
<ion-item fill="solid"> <!--第一個欄位: 輸入區塊 -->
<ion-input label="Email Address" labelPlacement="floating" formControlName="email"></ion-input>
</ion-item>
@if (formErrors['email']!=='') { <!--第一個欄位: 錯誤訊息顯示區塊 -->
<div class="ion-padding-start">
<ion-text color="danger"><small>{{ formErrors['email'] }}</small></ion-text>
</div>
}
<!-- 第二、三欄位省略 -->
<ion-button type="submit" [disabled]="registerForm.invalid" expand="block" class="ion-margin-top">
註冊
</ion-button>
</ion-card-content>
</ion-card>
</form>
</ion-content>register.page.html片段
<ion-header>
<ion-toolbar color="primary">
<ion-title>註冊</ion-title>
<ion-buttons slot="end">
<ion-button (click)="home()">
<ion-icon slot="icon-only" name="home-outline"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<form [formGroup]="registerForm" (ngSubmit)="register()">
<ion-card>
<ion-card-content>
<ion-item fill="solid">
<ion-input label="Email Address" labelPlacement="floating" formControlName="email"></ion-input>
</ion-item>
@if (formErrors['email']!=='') {
<div class="ion-padding-start">
<ion-text color="danger"><small>{{ formErrors['email'] }}</small></ion-text>
</div>
}
<ion-item fill="solid" class="ion-margin-top">
<ion-input label="密碼" labelPlacement="floating" type="password" formControlName="password"></ion-input>
</ion-item>
@if (formErrors['password']) {
<div class="ion-padding-start">
<ion-text color="danger"><small>{{ formErrors['password'] }}</small></ion-text>
</div>
}
<ion-item fill="solid" class="ion-margin-top">
<ion-input label="驗證密碼" labelPlacement="floating" type="password" formControlName="confirm"></ion-input>
</ion-item>
@if (formErrors['confirm']) {
<div class="ion-padding-start">
<ion-text color="danger"><small>{{ formErrors['confirm'] }}</small></ion-text>
</div>
}
<ion-button type="submit" [disabled]="registerForm.invalid" expand="block" class="ion-margin-top">
註冊
</ion-button>
</ion-card-content>
</ion-card>
</form>
</ion-content>register.page.html完整
<ion-header>
<ion-toolbar>
<ion-title>登入</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-item>
<ion-label position="floating">Email Address</ion-label>
<ion-input type="text" [(ngModel)]="email"></ion-input>
</ion-item>
<ion-item>
<ion-label position="floating">密碼</ion-label>
<ion-input type="password" [(ngModel)]="password"></ion-input>
</ion-item>
<ion-button expand="block" (click)='signIn(email, password)'>
登入
</ion-button>
<ion-item>
<ion-label class="ion-text-center" [routerLink]="['/register']">我要註冊</ion-label>
</ion-item>
</ion-content>
記得將註冊功能的超連結放入頁面,如login頁面
修改login.page.html
ionic g guard guards/auth1. 建立authGuard服務
實作哪些介面? 選擇 CanActivate
import { CanActivateFn } from '@angular/router';
export const testGuard: CanActivateFn = (route, state) => {
return true;
};
guards/auth-guard.ts
預設內容
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';
import { inject } from '@angular/core';
import { map } from 'rxjs';
export const authGuard: CanActivateFn = (route, state) => {
const as = inject(AuthService); // 注入自訂服務
const router = inject(Router); // 注入Router服務
return as.user$.pipe( // 訂閱登入狀態
map( (user) => {
if (user) { // 登入狀態若為已登入
return true; // 映對至true
} else { // 反之, 映對至false
router.navigate(['login']); // 轉至login頁面
return false;
}
})
);
};
guards/auth-guard.ts
修改內容
pipe(): 串連運算子
map(): 逐一將訂閱資料映對(mapping)至新值
2. 修改app-routing.module.ts
//...省略....
import { authGuard } from './guards/auth-guard';
const routes: Routes = [
{
path: 'home',
canActivate: [authGuard],
loadChildren: () => import('./home/home.module').then( m => m.HomePageModule)
},
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'login',
loadChildren: () => import('./login/login.module').then( m => m.LoginPageModule)
},
];
//...省略....
app/app-routing.module.ts
加入canActivate屬性設定
沒有足夠的會員資料欄位
需另外儲存於資料庫!
1. 點選登入
2. 彈出OAuth對話框
3. 收取後續存取資訊
4. 送出認證碼到伺服器
5. 認證碼換取後續存取權杖
6. google回傳後續存取權杖
7.伺服器確認登入
伺服器
cd loginApp
ionic g page login1.建立空白專案,並設定app id!
ionic start loginApp blank --id com.aumis.loginapp2.新增所需頁面
注意如何修改appModule !
4.新增ionic storage所需[npm套件] 與[cordova外掛]
ionic cordova plugin add cordova-sqlite-storage
npm install --save @ionic/storageionic g service services/auth3.新增登入認證provider
<widget id="com.aumis.loginapp"...>或是開啟現有專案的config.xml, 修改 id值
id格式:
與網址順序相反
com.公司名.產品名
config.xml
import { IonicStorageModule } from '@ionic/storage';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
...
imports: [
...
HttpClientModule,
IonicStorageModule.forRoot()
...
],
...
app.module.ts片段
5.修改app.module.ts
引入IonicStorageModule, HttpClientModule 模組
6.安裝firebase, @angular/fire套件
npm install firebase --save
npm install @angular/fire --savefirebase: v. 7.14.3
@angular/fire: v. 6.0.0
2020/5/13
複製反白區域
7.在app資料夾新增app.firebase.config.ts, 貼入連結參數
export const environment = {
production: false,
firebaseConfig: {
apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
authDomain: '專案id.firebaseapp.com',
databaseURL: 'https://專案id.firebaseio.com',
projectId: '專案id',
storageBucket: '專案id.appspot.com',
messagingSenderId: 'XXXXXXXXXXXX'
}
};將上頁複製內容貼上
8.再次修改app.module.ts
import { NgModule } from '@angular/core';
import { AngularFireModule } from '@angular/fire';
import { AngularFireAuthModule } from '@angular/fire/auth';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { environment } from '../environments/environment';
...略...
@NgModule({
...略...
imports: [
BrowserModule,
AngularFireModule.initializeApp( environment.firebaseConfig),
AngularFireAuthModule,
AngularFirestoreModule
],
...略...
})
9.建立偵錯簽署憑證 SHA-1(使用JDK工具keytool)
keytool -exportcert -list -v -alias androiddebugkey -keystore ./.android/debug.keystore密碼輸入 android
keytool為JAVA SDK所提供
複製到Firebase
cd .android
keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -validity 1000010.將id與SHA-1憑證填入Firebase
iOS只須填入id
android須填入id與SHA-1憑證
1. 填入id
2. 填入SHA-1憑證
3.
4. iOS比照辦理,但只須填入id
11.從Firebase後台找出REVERSED_CLIENT_ID
1.專案設定
2. iOS應用程式
以便安裝google登入外掛
3.下載並開啟GoogleService-info.plist
4.從中找出REVERSED_CLIENT_ID
12. 安裝Ionic Native外掛:Google Plus
ionic cordova plugin add cordova-plugin-googleplus --variable REVERSED_CLIENT_ID=FIREBASE後台提供之ID
npm install --save @ionic-native/google-plusID在GoogleService-info.plist檔案內
註: angularfire2的版本(5.0.0-rc.8.0)有時會導致無法安裝
(可嘗試降版本重新安裝, 如降至5.0.0-rc.6.0)
13.再次修改app.module.ts
...
import { GooglePlus } from '@ionic-native/google-plus';
@NgModule({
...
providers: [
GooglePlus,
StatusBar,
...
]
})
...
app.module.ts片段
引入GooglePlus模組
app.component.ts首頁設定
...
import { Storage } from '@ionic/storage';
import { LoginPage } from '../pages/login/login';
...
export class MyApp {
rootPage:any = HomePage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen,
storage: Storage) {
platform.ready().then(() => {
storage.get('signin').then((result)=>{
if(result){ // 已登入, 'signin' is set to true in storage
this.rootPage = HomePage;
} else { // not logged in
this.rootPage = LoginPage;
}
});
statusBar.styleDefault();
splashScreen.hide();
});
}
}app.component.ts片段
storage 儲存資料"signin"
檢查"signin"值, 決定rootPage為何
login.html
<ion-header>
<ion-navbar color="primary">
<ion-title>登入</ion-title>
</ion-navbar>
</ion-header>
<ion-content padding>
<ion-item>
<ion-label floating>Email Address</ion-label>
<ion-input type="text" [(ngModel)]="user.email"></ion-input>
</ion-item>
<ion-item>
<ion-label floating>密碼</ion-label>
<ion-input type="password" [(ngModel)]="user.password"></ion-input>
</ion-item>
<button ion-button block (click)='signIn(user)'>登入</button>
<button ion-button block (click)="signInWithFB()"> FB登入 </button>
<button ion-button block color="danger" (click)="signInWithGoogle()"> Google登入 </button>
</ion-content>
login.ts 片段
import { User } from '../../models/user';
...
export class LoginPage {
user= {} as User;
constructor(public navCtrl: NavController, public navParams: NavParams,
private auth: AuthProvider, private storage: Storage) {
}
...
/** Google登入 **/
async signInWithGoogle(){
await this.auth.signInWithGoogle();
this.navCtrl.setRoot(HomePage);// 跳轉至home page
}
}
export interface User {
email: string;
password: string;
}
models/user.ts
login.ts
import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { Storage } from '@ionic/storage';
import { AuthProvider } from '../../providers/auth/auth';
import { User } from '../../models/user';
import { HomePage } from '../home/home';
@IonicPage()
@Component({
selector: 'page-login',
templateUrl: 'login.html',
})
export class LoginPage {
user= {} as User;
constructor(public navCtrl: NavController, public navParams: NavParams,
private auth: AuthProvider, private storage: Storage) {
}
signIn(user) {
this.auth.signIn(user).then((resp)=>{
console.log(resp);
this.storage.set('signin', true);
this.storage.set('name', resp.user.email);
this.navCtrl.setRoot(HomePage);// 跳轉至home page
}).catch((err)=>{
console.log('Error:', err);
});
}
/**
Facebook登入
**/
signInWithFB() {
this.auth.signInWithFacebook().then((resp)=>{
console.log(resp);
this.storage.set('signin', true);
this.storage.set('name', resp.user.displayName);
this.navCtrl.setRoot(HomePage);// 跳轉至home page
}).catch((err) => {
console.log("SignIn Error: FB", err);
});
}
async signInWithGoogle(){
await this.auth.signInWithGoogle();
this.navCtrl.setRoot(HomePage);// 跳轉至home page
}
}
...
import { Storage } from '@ionic/storage';
import { GooglePlus } from '@ionic-native/google-plus';
import { Platform } from 'ionic-angular';
@Injectable()
export class AuthProvider {
private user: Observable<firebase.User>; // 屬性:Observer of firebase.User
private userDetails: firebase.User = null; // user帳號資料
constructor(public http: HttpClient,
private google: GooglePlus,
private afAuth: AngularFireAuth,
private storage: Storage,
private platform: Platform) {
this.user = this.afAuth.authState; // 建立Observable
// 訂閱
this.user.subscribe((result) => {
if (result) { // 有結果
this.userDetails = result;
console.log(this.userDetails);
} else {
this.userDetails = null;
}
});
}
...
auth.ts片段
GooglePlus登入:手機版/Web版
手機版:需填寫google(非firebase)提供之id
Web版:angularfire2全權處理
1. 確認專案名稱與Firebase專案相同
2. 在OAuth 2.0用戶端 ID區塊,找到Web client
3. 複製最後一欄「用戶端id」
export class AuthProvider {
...
// Google login 瀏覽器版
async webGoogleLogin(): Promise<void> {
try {
const provider = new firebase.auth.GoogleAuthProvider();
const credential = await this.afAuth.auth.signInWithPopup(provider);
console.log('login credential:', credential);
this.storage.set('signin', true);
this.storage.set('name', credential.user.displayName);
} catch (err) {
console.log('Google Web Login Err:', err);
}
}
// Google login 手機版
async nativeGoogleLogin(): Promise<void> {
try {
const googleUser = await this.google.login({
'webClientId': 'Google Web Client用戶端id填在此',
'offline': true,
'scopes': 'profile email'
});
return await this.afAuth.auth.signInWithCredential(firebase.auth.GoogleAuthProvider.credential(googleUser.idToken));
} catch (err) {
console.log('Native Login Error:', err);
}
}
// Google 登入
async signInWithGoogle(){
if (this.platform.is('cordova')) {
await this.nativeGoogleLogin(); // 手機
} else {
await this.webGoogleLogin(); // 瀏覽器
}
}
...
auth.ts片段
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { User } from '../../models/user';
import { AngularFireAuth } from 'angularfire2/auth';
import firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';
import { Storage } from '@ionic/storage';
import { GooglePlus } from '@ionic-native/google-plus';
import { Platform } from 'ionic-angular';
@Injectable()
export class AuthProvider {
private user: Observable<firebase.User>; // 屬性:Observer of firebase.User
private userDetails: firebase.User = null; // user帳號資料
constructor(public http: HttpClient,
private google: GooglePlus,
private afAuth: AngularFireAuth,
private storage: Storage,
private platform: Platform) {
this.user = this.afAuth.authState; // 建立Observable
// 訂閱
this.user.subscribe((result) => {
if (result) { // 有結果
this.userDetails = result;
console.log(this.userDetails);
} else {
this.userDetails = null;
}
});
}
// FB 登入
signInWithFacebook() {
return this.afAuth.auth.signInWithPopup(
new firebase.auth.FacebookAuthProvider()
);
}
async webGoogleLogin(): Promise<void> {
try {
const provider = new firebase.auth.GoogleAuthProvider();
const credential = await this.afAuth.auth.signInWithPopup(provider);
console.log('login credential:', credential);
this.storage.set('signin', true);
this.storage.set('name', credential.user.displayName);
} catch (err) {
console.log('Google Web Login Err:', err);
}
}
// Google login 手機版
async nativeGoogleLogin(): Promise<void> {
try {
const googleUser = await this.google.login({
'webClientId': 'Google Web Client用戶端id填在此',
'offline': true,
'scopes': 'profile email'
});
return await this.afAuth.auth.signInWithCredential(firebase.auth.GoogleAuthProvider.credential(googleUser.idToken));
} catch (err) {
console.log('Native Login Error:', err);
}
}
// Google 登入
async signInWithGoogle(){
if (this.platform.is('cordova')) {
await this.nativeGoogleLogin(); // 手機
} else {
await this.webGoogleLogin(); // 瀏覽器
}
}
// email/password登入
signIn(user: User) {
return this.afAuth.auth.signInWithEmailAndPassword(user.email, user.password);
}
// 登出
signOut() {
return this.afAuth.auth.signOut();
}
state():boolean{
if(this.afAuth.authState) {
return true
} else {
return false
}
}
}
auth.ts
<ion-header>
<ion-navbar>
<ion-title>
首頁
</ion-title>
<ion-buttons end>
<button ion-button (click)="logout()">登出</button>
</ion-buttons>
</ion-navbar>
</ion-header>
<ion-content padding>
<p>
Welcome {{ name }}
</p>
</ion-content>home.html
import { Component } from '@angular/core';
import { NavController, NavParams } from 'ionic-angular';
import { AuthProvider } from '../../providers/auth/auth';
import { Storage } from '@ionic/storage';
import { LoginPage } from '../login/login';
...
export class HomePage {
name:any;
constructor(public navCtrl: NavController, public navParams: NavParams,
private auth: AuthProvider,
private storage: Storage) {
this.storage.get('name').then(data => {
this.name = data;
});
}
logout(){
this.auth.signOut().then(resp=>{
this.storage.set('signin', false);
this.navCtrl.setRoot(LoginPage);
});
}
}
home.ts片段
從storage中讀取 "name"值
get()回傳類型:Promise<any>
登出時將storage中"signin"設為false
signOut()回傳類型:Promise<any>
1. 點選登入
2. 彈出OAuth對話框
1. 送出FB登入認證
2. 要求app轉址
3. 進行轉址
4. 要求取得存取權限
5. 回傳存取權杖
6. 要求讀取用戶資料
7. 回傳用戶資料
8. 顯示用戶資料
FB應用程式
複製到Firebase
複製回Facebook