Ionic Tutorial

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

登入認證方式

  • Email/Password登入
    • 帳號/密碼儲存於雲端資料庫
      • 可使用Firebase Authentication功能
         
  • 社群網站帳號登入
    • Google登入
      • 必須在Google後台設定「伺服器」
    • Facebook登入
      • 必須在FB for Developer後台設定「FB應用程式」

Email/Password登入

(1)啟用Firebase Authentication的Email認證服務
前往  https://firebase.google.com/​

 

(2) 呼叫firebase提供的API撰寫「登入/註冊/登出」功能

 

 

(3) 在頁面使用

引入服務、叫用「登入/註冊/登出」功能

ionic g service services/auth

例:

啟用Email認證服務新增firebase專案(1/4)

啟用Email認證服務啟用登入方式供應商(2/4)

進入專案後...

編輯要啟用的供應商

需符合各供應商的登入連線要求

啟用Email認證服務啟用電子郵件登入(3/4)

啟用Email認證服務建立登入帳號(4/4)

確認已啟用

啟用Email認證服務建立登入帳號(4/4)

啟用Email認證服務建立登入帳號(4/4)

直接輸入電子郵件與密碼

啟用Email認證服務建立登入帳號(4/4)

Email/Password登入

  • 練習範例

 

(1) 呼叫firebase提供的API撰寫「登入/註冊/登出」功能

 

 

(2) 在頁面使用

引入服務、叫用「登入/註冊/登出」功能

練習: 登入頁面

Email/password登入

登入後顯示帳號資訊(預設只有email)

紀錄登入狀態?

取決於AuthState

登入認證連線至Firebase

登入頁面範例建立專案

cd loginApp
ionic g page login
ionic g page register

1.建立空白專案

ionic start loginApp blank

2.新增所需頁面

HomePage
    已登入時,則至首頁
LoginPage

    未登入時,則顯示登入畫面

RegisterPage

    註冊頁面

AuthService

    連結Firebase進行登入驗證

ionic g service services/auth.service

3.新增登入認證service

登入頁面範例安裝firebase套件(1/4)

6.安裝firebase, @angular/fire套件

npm i -g @angular/cli

npm i firebase
ng add @angular/fire

如尚未安裝

登入頁面範例安裝firebase套件(2/4)

ng add @angular/fire

需依序回答下列問題

回傳使用資料: No

選擇欲設定的功能: Authentication(登入認證), Firestore(資料庫)

登入頁面範例安裝firebase套件(3/4)

ng add @angular/fire

選擇登入Firebase的Google帳號(應與前面Firebase主控台步驟相同)

選取先前建立的專案名稱

選取先前建立的[應用程式]名稱

自動更新app.module.ts

注意:自動更新的

app.module.ts有錯誤

登入頁面範例安裝firebase套件(4/4)

//...[省略]...
@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

登入頁面範例登入認證服務 (1/2)

...
import { Auth, signInWithEmailAndPassword, signOut, User, user } from '@angular/fire/auth';
...

auth.service.ts片段

angular/fire/auth: 提供Firebase認證函式

登入頁面範例登入認證服務 (2/2)

...
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片段

登入頁面範例登入頁面(1/3)

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

登入頁面範例登入頁面(2/3)

<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

登入按鈕

帳號、密碼欄位

登入頁面範例登入頁面:資料模型(3/3)

export interface User {
    uid: string;
    email: string;
    displayName: string;
    photoURL: string;
    emailVerified: boolean;
}

在src資料夾建立models/user.ts

此五個欄位是Firebase內建的五個欄位

方便與Firebase資料庫認證串連

登入頁面範例首頁 (1/2)

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片段

登入頁面範例首頁 (2/2)

<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

註冊註冊服務 (1/7)

...
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片段

註冊註冊頁面 (2/7)

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

註冊註冊頁面 (3/7)

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)

註冊註冊頁面 (4/7)

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: ''      // 名稱相同                //
  };
  ...
}

註冊註冊頁面 (5/7)

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

註冊註冊頁面 (6/7)

<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片段

註冊註冊頁面 (7/7)

<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

練習

  • 將DiaryApp加上登入功能
  • 歷史上的今天不需登入即可使用
  • 資料庫編修功能則需要登入

Angular Route Guard

  • 路徑守衛:管理頁面的存取權限
  • 在app-routing.module.ts加上設定
ionic g guard guards/auth

Route Guard路徑守衛 (1/3)

1. 建立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;
      }
    })
  );
};

Route Guard路徑守衛 (2/3)

guards/auth-guard.ts

修改內容

pipe(): 串連運算子

map(): 逐一將訂閱資料映對(mapping)至新值

Route Guard路徑守衛 (3/3)

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屬性設定

What's the problem?

沒有足夠的會員資料欄位

 

需另外儲存於資料庫!

Google登入

  • Firebase端設定
    • iOS版/Android版不同

 

Google帳號登入

1. 點選登入

2. 彈出OAuth對話框

3. 收取後續存取資訊

4. 送出認證碼到伺服器

5. 認證碼換取後續存取權杖

6. google回傳後續存取權杖

7.伺服器確認登入

伺服器

Google登入頁面範例建立專案(1/2)

cd loginApp
ionic g page login

1.建立空白專案,並設定app id!

ionic start loginApp blank --id com.aumis.loginapp

2.新增所需頁面

注意如何修改appModule !

4.新增ionic storage所需[npm套件] 與[cordova外掛]

ionic cordova plugin add cordova-sqlite-storage
npm install --save @ionic/storage
ionic g service services/auth

3.新增登入認證provider

<widget id="com.aumis.loginapp"...>

或是開啟現有專案的config.xml, 修改 id值

id格式:

    與網址順序相反

    com.公司名.產品名

config.xml

Google登入頁面範例建立專案(2/2)

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 模組

Google登入頁面範例安裝firebase套件(1/4)

6.安裝firebase, @angular/fire套件

npm install firebase --save
npm install @angular/fire --save

firebase: v. 7.14.3

@angular/fire: v. 6.0.0

2020/5/13

Google登入頁面範例firebase套件(2/4)

複製反白區域

Google登入頁面範例firebase套件(3/4)

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'
  }
};

將上頁複製內容貼上

Google登入頁面範例firebase套件(4/4)

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
  ],
  ...略...
})

Google登入頁面範例Firebase設定(1/3)

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 10000

10.將id與SHA-1憑證填入Firebase

iOS只須填入id

android須填入id與SHA-1憑證

Google登入頁面範例Firebase設定(2/3)

1. 填入id

2. 填入SHA-1憑證

3.

4. iOS比照辦理,但只須填入id

Google登入頁面範例Firebase設定(3/3)

Google登入頁面範例安裝google外掛(1/3)

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-plus

ID在GoogleService-info.plist檔案內

註:  angularfire2的版本(5.0.0-rc.8.0)有時會導致無法安裝
(可嘗試降版本重新安裝, 如降至5.0.0-rc.6.0)

Google登入頁面範例安裝google外掛(2/3)

13.再次修改app.module.ts

...
import { GooglePlus } from '@ionic-native/google-plus';
@NgModule({
  ...
  providers: [
    GooglePlus,
    StatusBar,
    ...
  ]
})
...

app.module.ts片段

引入GooglePlus模組

Google登入頁面範例安裝google外掛(3/3)

Google登入頁面範例首頁設定

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為何

Google登入頁面範例登入頁面(1/3)

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>

Google登入頁面範例登入頁面(2/3)

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

Google登入頁面範例登入頁面(3/3)

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
  }

}


Google登入頁面範例登入認證(1/4)

...
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全權處理

Google登入頁面範例登入認證(2/4)

1. 確認專案名稱與Firebase專案相同

2. 在OAuth 2.0用戶端 ID區塊,找到Web client

3. 複製最後一欄「用戶端id」

Google登入頁面範例登入認證(3/4)

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片段

Google登入頁面範例登入認證(4/4)

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

Google登入頁面範例首頁(1/2)

<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

Google登入頁面範例首頁(2/2)

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>

Google登入頁面範例執行畫面

Facebook帳號登入

1. 點選登入

2. 彈出OAuth對話框

1. 送出FB登入認證

2. 要求app轉址

3. 進行轉址

4. 要求取得存取權限

5. 回傳存取權杖

6. 要求讀取用戶資料

7. 回傳用戶資料

8. 顯示用戶資料

FB應用程式

FB登入fackbook應用程式設定

FB登入fackbook應用程式設定

FB登入fackbook應用程式設定

複製到Firebase

FB登入fackbook應用程式設定

複製回Facebook

FB登入fackbook應用程式設定