Ionic Tutorial

Lesson 8: Authentication with Firebase

Revised on 2021/4/29

Ionic v5.5.2

Angular v11.2.0

@angular/fire v6.1.4

firebase v8.3.3

登入認證方式

  • 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/Password登入

  • 練習範例

 

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

 

 

(3) 在頁面使用

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

練習: 登入頁面

Email/password登入

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

紀錄登入狀態?

取決於AuthState

登入認證連線至Firebase

登入頁面範例建立專案

cd loginApp
ionic g page login

1.建立空白專案

ionic start loginApp blank

2.新增所需頁面

HomePage
    已登入時,則至首頁
LoginPage

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

 

AuthService

    連結Firebase進行登入驗證

ionic g service services/auth

3.新增登入認證service

Firebase API提供變數: AuthState用以儲存登入狀態

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

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

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

firebase: v. 8.8.3

@angular/fire: v. 6.1.4

2021/5/13

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

複製反白區域

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

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

7.修改src/environments/environment.ts, 加入:

將上頁複製內容貼上

登入頁面範例安裝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
  ],
  ...略...
})

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

...
import { AngularFireAuth } from '@angular/fire/auth';
...

auth.service.ts片段

AngularFireAuth: 提供Firebase認證函式

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

...
export class AuthService {

  constructor(public auth: AngularFireAuth, /* 注意auth必須設定為public */
    public router: Router
    ) { }
  // 功能0. this.auth.authState 用來紀錄目前登入的使用者 或 未登入
  // 功能1. 登入(email登入)
  signIn(mail: string, password: string) {
    this.auth.signInWithEmailAndPassword(mail, password).then( uc => {
      console.log(uc.user);  // 印出目前登入使用者的資訊
      this.router.navigate(['home']); // 跳轉至首頁home
    }).catch(error => {
      /* 帳密驗證失敗 */
      console.log('帳密輸入錯誤', error); 
    });
  }

  // 功能2. 登出(所有登出)
  signOut(): Promise<any>  {
    return this.auth.signOut();
  }
}

auth.service.ts片段

登入頁面範例登入頁面(1/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

登入按鈕

帳號、密碼欄位

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

import { Storage } from '@ionic/storage';
import { AuthService } from './../service/auth.service';
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
...
export class LoginPage implements OnInit {
  email: string;
  password: string;

  constructor(public as: AuthService, public router: Router) { }
  
  ngOnInit() {
    this.as.auth.authState.subscribe(user => {
      console.log('HOME', user);
      if(user) {
        this.router.navigate(['home']);
      }
    });
  }

  signIn(email, passwd){
    this.as.signIn(email, passwd);
  }
}

login.page.ts 片段

登入,呼叫AuthService功能

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

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

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

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

方便與Firebase資料庫認證串連

登入頁面範例首頁 (1/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">
 歡迎 {{ email }}
</ion-content>

home.page.html

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

import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Storage } from '@ionic/storage';
import { AuthService } from '../service/auth.service';
...
export class HomePage {
  email: string;

  constructor(public as: AuthService, public router: Router) {
    as.auth.authState.subscribe(user=>{
      if(user) {
        console.log(user);
        this.email = user.email;
      } else {
        this.router.navigate(['login']);
      }
    });
  }

  signOut() {
    this.as.signOut();
  }
}

home.page.ts片段

註冊註冊服務 (1/7)

...
export class AuthService {

  constructor(public auth: AngularFireAuth, /* 注意auth必須設定為public */
    public router: Router
    ) { }
  ....
  // 功能3. 註冊新使用者
  registerUser(mail: string, password: string) {
    this.auth.createUserWithEmailAndPassword(mail, password).then(uc => {
      console.log(uc.user);  /* 註冊成功, 印出使用者 */
      this.router.navigate(['login']); /* 前往登入畫面 */
    }).catch(error => {
      console.log('帳密註冊失敗', error);
    });
  }
  ...
}

auth.service.ts片段

註冊註冊頁面 (2/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 {
  registerForm: FormGroup;  // 表單
  account = {    // 欄位:帳號/密碼/密碼驗證
    email: '',
    password: '',
    confirm: ''
  }
  formErrors = { // 錯誤訊息的空白物件
    email: '',       // 注意:每一個欄位名稱必須與 //
    password: '',    // registerForm建立時的欄位 //
    confirm: ''      // 名稱相同                //
  };
  errorMessages = { // 想要顯示在畫面上的「錯誤訊息」
    'email': {
      'required': '必填欄位',
      'email': '請照電子郵件格式填入'
    },
    'password': {
      'required': '必填欄位',
      'minlength': '密碼最少8碼'
    },
    'confirm': {
      'required': '必填欄位',
      'confirmedValidator': '重新輸入密碼不符'
    }
  };
  // ....未完,續下一頁...
}

register.page.ts片段(1/3)

ionic g page register

先建立註冊頁面

註冊註冊頁面 (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();
  }
  // 回到首頁
  home(){
    this.router.navigate(['home']);
  }
  //使用FormBuilder服務建立表單,並訂閱表單資料變動資料流
  private buildForm() {
    this.registerForm = this.builder.group({
      email: ['', [Validators.required, Validators.email]],
      password: ['', [Validators.required, Validators.minLength(8)]],
      confirm: ['', [Validators.required] ]
    }, {
      validator: 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) {
    // 若無registerForm則直接回傳
    if(!this.registerForm) return;
    
    const form = this.registerForm;
    for(const field in this.formErrors) {  // 走訪formErrors裡的每一個欄位
      this.formErrors[field] = '';  // 1. 先清空該欄位的錯誤訊息
      const control = this.registerForm.get(field); // 透過欄位名稱取得表單真正的欄位
      if (control && control.dirty && control.invalid) {
         // (存在   且    已編輯過    且  不正確)  => 代表出現錯誤
         // 取得錯誤訊息
         const messages = this.errorMessages[field]; // 取得該欄位所有的錯誤訊息
         for(const key in control.errors) { // 走訪該欄位所有錯誤的狀況
          this.formErrors[field] += messages[key];
         } 
      }
    }
  }
  // 註冊
  register(){
    const email = this.registerForm.get('email').value;
    const password = this.registerForm.get('password').value; 

    this.as.registerUser(email, password);
  }
}

register.page.ts片段(3/3)

export class RegisterPage implements OnInit {
  ...
  formErrors = { // 錯誤訊息的空白物件
    email: '',       // 注意:每一個欄位名稱必須與 //
    password: '',    // registerForm建立時的欄位 //
    confirm: ''      // 名稱相同                //
  };
  ...
}
import { FormGroup, ValidationErrors } from '@angular/forms';

export function ConfirmedValidator(controlName: string, matchingControlName: string) {
    return (formGroup: FormGroup) => {
        const control = formGroup.controls[controlName];     // 第一個欄位
        const matchingControl = formGroup.controls[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

註冊註冊頁面 (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>
  <form [formGroup]="registerForm">    <!-- formGroup屬性 綁定至 registerForm -->
    <ion-card>
      
      <ion-item>   <!--第一個欄位: 輸入區塊 -->
        <ion-label position="floating">Email Address</ion-label>
        <ion-input type="text" formControlName="email"></ion-input>
      </ion-item>
      
      <ion-item *ngIf="formErrors.email"> <!--第一個欄位: 錯誤訊息顯示區塊 -->
        <ion-text color="danger">
          <p>{{ formErrors.email }}</p>
        </ion-text>
      </ion-item>

      <!-- 第二、三欄位省略 -->
      
      <ion-button (click)="register()" expand="block" color="success" shape="round">
        註冊
      </ion-button>
    </ion-card>
  </form>

</ion-content>

register.page.html片段

註冊註冊頁面 (7/7)

<ion-header>
  <ion-toolbar>
    <ion-title>註冊</ion-title>
    <ion-buttons>
      <ion-icon name="home-outline" (click)="home()"></ion-icon>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content>
  <form [formGroup]="registerForm">
    <ion-card>
      <ion-item>
        <ion-label position="floating">Email Address</ion-label>
        <ion-input type="text" formControlName="email"></ion-input>
      </ion-item>
      <ion-item *ngIf="formErrors.email">
        <ion-text color="danger">
          <p>{{ formErrors.email }}</p>
        </ion-text>
      </ion-item>

      <ion-item>
        <ion-label position="floating">密碼</ion-label>
        <ion-input type="password" formControlName="password"></ion-input>
      </ion-item>

      <ion-item *ngIf="formErrors.password">
        <ion-text color="danger">
          <p>{{ formErrors.password }}</p>
        </ion-text>
      </ion-item>

      <ion-item>
        <ion-label position="floating">驗證密碼</ion-label>
        <ion-input type="password" formControlName="confirm"></ion-input>
      </ion-item>

      <ion-item *ngIf="formErrors.confirm">
        <ion-text color="danger">
          <p>{{ formErrors.confirm }}</p>
        </ion-text>
      </ion-item>
      <ion-button (click)="register()" expand="block" color="success" shape="round">
        註冊
      </ion-button>
    </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/7)

1. 建立authGuard服務

實作哪些介面? 選擇 CanActivate

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return true;
  }

}

guards/auth.guard.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 {
  registerForm: FormGroup;  // 表單
  account = {    // 欄位:帳號/密碼/密碼驗證
    email: '',
    password: '',
    confirm: ''
  }
  formErrors = { // 錯誤訊息的空白物件
    email: '',       // 注意:每一個欄位名稱必須與 //
    password: '',    // registerForm建立時的欄位 //
    confirm: ''      // 名稱相同                //
  };
  errorMessages = { // 想要顯示在畫面上的「錯誤訊息」
    'email': {
      'required': '必填欄位',
      'email': '請照電子郵件格式填入'
    },
    'password': {
      'required': '必填欄位',
      'minlength': '密碼最少8碼'
    },
    'confirm': {
      'required': '必填欄位',
      'confirmedValidator': '重新輸入密碼不符'
    }
  };
  // ....未完,續下一頁...
}

register.page.ts片段(1/3)

ionic g service services/authGuard

Route Guard路徑守衛 (1/7)

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應用程式設定