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
(1)啟用Firebase Authentication的Email認證服務
前往 https://firebase.google.com/
(2) 呼叫firebase提供的API撰寫「登入/註冊/登出」功能
(3) 在頁面使用
引入服務、叫用「登入/註冊/登出」功能
ionic g service services/auth
例:
進入專案後...
編輯要啟用的供應商
需符合各供應商的登入連線要求
直接輸入電子郵件與密碼
(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用以儲存登入狀態
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
複製反白區域
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, 加入:
將上頁複製內容貼上
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
],
...略...
})
...
import { AngularFireAuth } from '@angular/fire/auth';
...
auth.service.ts片段
AngularFireAuth: 提供Firebase認證函式
...
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片段
<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
登入按鈕
帳號、密碼欄位
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功能
export interface User {
uid: string;
email: string;
displayName: string;
photoURL: string;
emailVerified: boolean;
}
在src資料夾建立models/user.ts
此五個欄位是Firebase內建的五個欄位
方便與Firebase資料庫認證串連
<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
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片段
...
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片段
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
先建立註冊頁面
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)
....
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
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>
<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片段
<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
ionic g guard guards/auth
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
沒有足夠的會員資料欄位
需另外儲存於資料庫!
1. 點選登入
2. 彈出OAuth對話框
3. 收取後續存取資訊
4. 送出認證碼到伺服器
5. 認證碼換取後續存取權杖
6. google回傳後續存取權杖
7.伺服器確認登入
伺服器
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
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 --save
firebase: 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 10000
10.將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-plus
ID在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