尝试使用Angular和Firebase进行用户认证
这篇文章是Hands Lab 2017年的第10天文章。
大家好,这是第二次登场的手部实验室圣诞日历,我是大木志操。
这次我要介绍一下Firebase的实践主题。
当谈及Firebase时,大家可能都知道它作为实时数据库的知名度很高,但其实它还可以轻松构建用户认证基础设施。
在本文中,我将借助Angular官方的Firebase库AngularFire,为Angular应用实现用户认证功能。
这次要做的东西
在本文中,我們將使用Angular + AngularFire來創建下一個畫面。
-
- ログイン画面
-
- サインアップ画面
- トップページ(ユーザー情報)
另外,由于Firebase Authentication没有设置用户属性的功能,我希望同时在Cloud Firestore中进行用户信息管理。
前提 tí) – premise/condition/requisite
Note: The given word “前提” is already a Chinese word, so there is no need for paraphrasing.
- Firebaseのアカウントは取得済み(無料枠で可)
本次使用的环境
$ ng -v
...
Angular CLI: 1.5.4
Node: 9.2.0
OS: darwin x64
Angular: 5.0.3
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router
@angular/cli: 1.5.4
@angular/flex-layout: 2.0.0-beta.10-4905443
@angular-devkit/build-optimizer: 0.0.33
@angular-devkit/core: 0.0.21
@angular-devkit/schematics: 0.0.37
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.8.4
@schematics/angular: 0.1.7
typescript: 2.4.2
webpack: 3.8.1
步骤
创建 Firebase 项目。
在Firebase控制台的首页上,点击“添加项目”,然后添加项目。

Firebase项目配置
身份验证
接下来,从左侧的“开发”菜单中选择“身份验证”,再从“用户”选项卡的登录方式设置按钮中进行登录提供商的设置。

本次我们尝试启用了通过邮件/密码登录和Google账号登录这两个选项。

最后,在屏幕右上方点击”网页设置”,复制并保存以下展示的代码片段的这一部分。
apiKey: '<your-key>',
authDomain: '<your-project-authdomain>',
databaseURL: '<your-database-URL>',
projectId: '<your-project-id>',
storageBucket: '<your-storage-bucket>',
messagingSenderId: '<your-messaging-sender-id>'
数据库(云 Firestore)
接下来,从屏幕左侧的「DEVELOP」菜单中选择数据库,然后点击「试用FIRESTORE Beta版本」。

在显示选择安全规则的界面上,勾选“以测试模式启动”并点击“启用”。

以上就是Firebase的设置完成了。
使用Angular CLI创建模板
使用angular-cli创建应用程序的模板。
之后,计划添加一个根据是否经过身份验证来进行路由处理的功能,所以要使用–routing选项来运行。
$ ng new angular-firebase-auth --routing
当命令执行完毕后,将项目目录切换到当前位置。
$ cd angular-firebase-auth
安装所需的软件包。
我会安装Firebase和AngularFire。
$ npm install firebase angularfire2 --save
这次使用的是下一个版本。
-
- angularfire2@5.0.0-rc.4
- firebase@4.6.2
项目的设置
我将进行配置,以便在项目中使用AngularFire。
将Firebase的设置添加到/src/environments/environment.ts文件中,该文件定义了环境变量。
apiKey、authDomain、ProjectId是从Firebase项目创建过程中复制的内容。
export const environment = {
production: false,
firebase: {
apiKey: '<your-key>',
authDomain: '<your-project-authdomain>',
databaseURL: '<your-database-URL>',
projectId: '<your-project-id>',
messagingSenderId: '<your-messaging-sender-id>'
}
};
接下来,我们将编辑/src/app/app.module.ts文件如下。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
// 以下追加した項目
import { ReactiveFormsModule } from '@angular/forms';
import { AngularFireModule } from 'angularfire2';
import { AngularFireAuthModule } from 'angularfire2/auth';
import { AngularFirestoreModule } from 'angularfire2/firestore';
import { environment } from './../environments/environment';
import { AuthService } from './services/auth.service';
import { AuthGuard } from './guard/auth.guard';
import { UserLoginComponent } from './pages/user-login/user-login.component';
import { UserSignupComponent } from './pages/user-signup/user-signup.component';
import { UserInfoComponent } from './pages/user-info/user-info.component';
@NgModule({
declarations: [
AppComponent,
UserLoginComponent,
UserSignupComponent,
UserInfoComponent
],
imports: [
BrowserModule,
AppRoutingModule,
ReactiveFormsModule, // 追加
AngularFireModule.initializeApp(environment.firebase), // 追加
AngularFireAuthModule, // 追加
AngularFirestoreModule // 追加
],
providers: [
AuthService,
AuthGuard
],
bootstrap: [AppComponent]
})
export class AppModule { }
除了主要的AngularFireModule外,我們還導入了AngularFireAuthModule以使用身份驗證功能。這還包括稍後將添加的服務和組件的導入,我們將在後面詳細介紹。
用户模型的定义
在前端端定义保存到Firestore的用户信息的类型定义。
保存到Firebase Authentication的是认证信息,保存到Firestore的是用户信息,这两者通过UID关联起来。
我们决定将模型(interface)放在app/models目录中。
执行以下命令创建接口。
$ ng g interface models/user
在/src/models/user.ts中定义属 性如下。
export interface User {
uid: string;
email: string;
displayName?: string;
photoURL?: string;
profile?: string;
}
UID和电子邮件地址是必需的项目,其他的是可选的项目。
如果使用Google帐号登录,我们将稍后进行实现,从Google注册信息中获取displayName和photoURL。
创建用户认证服务
用户认证处理将被分解为服务。我决定将这个服务放置在app/services目录中。
执行以下命令创建认证服务的模板。
ng g service services/auth
将/src/services/auth.service.ts按照以下方式进行编辑。
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
// 以下追加したもの
import { AngularFireAuth } from 'angularfire2/auth';
import { AngularFirestore, AngularFirestoreDocument } from 'angularfire2/firestore';
import * as firebase from 'firebase/app';
import { Observable } from 'rxjs/Observable';
import { switchMap } from 'rxjs/operators';
import { User } from './../models/user';
@Injectable()
export class AuthService {
user: Observable<User | null>;
constructor(
private router: Router,
private afAuth: AngularFireAuth,
private afStore: AngularFirestore
) {
this.user = this.afAuth.authState
.switchMap(user => {
if (user) {
return this.afStore.doc<User>(`users/${user.uid}`).valueChanges();
} else {
return Observable.of(null);
}
});
}
signUp(email: string, password: string) {
return this.afAuth.auth.createUserWithEmailAndPassword(email, password)
.then(user => {
return console.log(user) || this.updateUserData(user);
})
.catch(err => console.log(err));
}
login(email: string, password: string): Promise<any> {
return this.afAuth.auth.signInWithEmailAndPassword(email, password)
.then(user => {
return console.log(user) || this.updateUserData(user);
})
.catch(err => console.log(err));
}
googleLogin() {
const provider = new firebase.auth.GoogleAuthProvider();
return this.oAuthLogin(provider);
}
logout() {
this.afAuth.auth.signOut()
.then(() => {
this.router.navigate(['/login']);
});
}
private oAuthLogin(provider) {
return this.afAuth.auth.signInWithPopup(provider)
.then(credential => {
console.log(credential.user);
return this.updateUserData(credential.user);
})
.catch(err => console.log(err));
}
private updateUserData(user: User) {
const docUser: AngularFirestoreDocument<User> = this.afStore.doc(`users/${user.uid}`);
const data: User = {
uid: user.uid,
email: user.email,
displayName: user.displayName || '',
photoURL: user.photoURL || '',
profile: user.profile || ''
};
return docUser.set(data);
}
}
创建路由器保护
创建一个用于判断用户登录状态的守卫。
首先,执行以下命令创建guard的模板。
$ ng g guard guard/auth
将app/guard/auth.guard.ts进行以下修改。
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs/Observable';
// 以下追加したもの
import { Router } from '@angular/router';
import { AngularFireAuth } from 'angularfire2/auth';
import { map, take, tap } from 'rxjs/operators';
import { AuthService } from './../services/auth.service';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private router: Router,
private auth: AuthService
) {}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
return this.auth.user.pipe(
take(1),
map(user => !!user), // userが取得できた場合はtrueを返す
tap(loggedIn => {
if (!loggedIn) {
this.router.navigate(['/loggin']);
}
})
);
}
}
在每个方法中,我们使用AngularFire提供的身份验证API来执行操作。
结果可以通过Promise来接收,因此实现也会变得相对轻松。
另外,在执行注册和登录相关操作时,我们也会将用户信息写入数据库中。
创建登录组件 de
我們將創建一個顯示登入頁面的組件。
执行以下命令以创建组件的模板。
$ ng g component pages/user-login
班级一方面
import { Component, OnInit } from '@angular/core';
// 以下追加したもの
import { ReactiveFormsModule, FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from './../../services/auth.service';
@Component({
selector: 'app-user-login',
templateUrl: './user-login.component.html',
styleUrls: ['./user-login.component.css']
})
export class UserLoginComponent implements OnInit {
loginForm: FormGroup;
constructor(
private router: Router,
private fb: FormBuilder,
private auth: AuthService
) {
this.auth.user.subscribe(user => {
if (user !== null) {
this.router.navigate(['/']);
}
});
}
ngOnInit() {
this.loginForm = this.fb.group({
'email': ['', [Validators.required, Validators.email]],
'password': ['', [Validators.required]]
});
}
login() {
const email = this.loginForm.get('email').value;
const password = this.loginForm.get('password').value;
this.auth.login(email, password)
.then(() => {
this.router.navigate(['/']);
});
}
googleLogin() {
this.auth.googleLogin()
.then(() => {
this.router.navigate(['/']);
});
}
}
模板方面
<h1>ログイン</h1>
<button (click)="googleLogin()">Googleでログイン</button>
<button [routerLink]="['/signup']">新規登録</button>
<form [formGroup]="loginForm" (ngSubmit)="login()">
<label for="email">Email</label>
<input type="email" class="input" formControlName="email" required >
<label for="password">Password</label>
<input type="password" class="input" formControlName="password" required >
<button type="submit" class="button">ログイン</button>
</form>
创建注册组件
我們將創建一個用於用戶註冊的組件。
這與登錄相同。
$ ng g component pages/user-singup
大部分与登录相同,因此已略去部分内容。
课堂方面
...
signupForm: FormGroup;
constructor(
private router: Router,
private fb: FormBuilder,
private auth: AuthService
) { }
ngOnInit() {
this.signupForm = this.fb.group({
'email': ['', [Validators.required, Validators.email]],
'password': ['', [Validators.required]]
});
}
signup() {
const email = this.signupForm.get('email').value;
const password = this.signupForm.get('password').value;
this.auth.signUp(email, password)
.then((x) => {
this.router.navigate(['/']);
});
}
}
模板端
<h1>登録</h1>
<form [formGroup]="signupForm" (ngSubmit)="signup()">
<label for="email">Email</label>
<input type="email" class="input" formControlName="email" required >
<label for="password">Password</label>
<input type="password" class="input" formControlName="password" required >
<button type="submit" class="button">登録</button>
</form>
创建用户信息组件
在登录后创建一个组件来显示用户信息。
在下文中的路由设置中,将访问路由重定向到该组件。
这也是与注册和登录一样的步骤。
$ ng g component pages/user-info
课堂一方
import { Component, OnInit } from '@angular/core';
// 以下追加したもの
import { AuthService } from './../../services/auth.service';
@Component({
selector: 'app-user-info',
templateUrl: './user-info.component.html',
styleUrls: ['./user-info.component.css']
})
export class UserInfoComponent implements OnInit {
constructor(public auth: AuthService) { }
ngOnInit() {
this.auth.user.subscribe(user => {
console.log(user);
});
}
logout() {
this.auth.logout();
}
}
模板方面
<div *ngIf="auth.user | async as user">
<h1>ようこそ {{user.displayName}}</h1>
</div>
<button (click)="logout()">ログアウト</button>
<div *ngIf="auth.user | async as user">
<h2>ユーザー情報</h2>
<p>プロフィール画像:</p>
<img [src]="user.photoURL" style="width: 150px">
<p>UID: {{user.uid}}</p>
<p>名前: {{user.displayName}}</p>
<p>Email: {{user.email}}</p>
<p>プロフィール: {{user.profile}}</p>
</div>
设置路由
我們將在路由中添加在前一步驟中創建的user-login和user-info組件。
可能在創建項目時使用了–routing選項,所以在app文件夾下應該已經創建了一個名為app-routing.module.ts的文件。
将src/app/app-routing.module.ts文件进行如下编辑。
import { NgModule } from '@angular/core';
import { Routes, RouterModule, CanActivate } from '@angular/router';
// 以下追加したもの
import { UserLoginComponent } from './pages/user-login/user-login.component';
import { UserSignupComponent } from './pages/user-signup/user-signup.component';
import { UserInfoComponent } from './pages/user-info/user-info.component';
import { AuthGuard } from './guard/auth.guard';
const routes: Routes = [
{ path: '', redirectTo: '/userinfo', pathMatch: 'full' },
{ path: 'userinfo', component: UserInfoComponent, canActivate: [AuthGuard] },
{ path: 'login', component: UserLoginComponent },
{ path: 'signup', component: UserSignupComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
如果有访问路径,设置重定向到/userinfo。
此外,使用Guard的canActivate进行配置,限制只有经过身份验证才能访问的设置。
确认
我們在開發伺服器上啟動Angular應用程式,並驗證我們所建立的內容。
$ ng serve
从浏览器访问 http://localhost:4200。
如果显示登录页面,请点击”通过Google登录”并选择要使用的Google帐户。
成功登录后,我想会显示用户信息如下。

此外,我没有准备UI,但我想确认一下是否可以获取“个人资料”的值。
我想从Firebase管理界面尝试编辑登录用户的文档中的个人资料。


请注意
本次实施仅是进行了一个初步的尝试,所以对于表单验证、从库中获取响应的错误处理以及Firebase的权限设置等方面都进行了相当粗糙的处理。我认为在实际使用时,需要重新审视这些方面。
结束
以前,我使用AWS Cognito在Angular应用程序中实现了用户身份验证功能。但是,在配置身份验证基础设施和实现Angular方面,我觉得无论是哪种方式,使用Firebase都更方便简单。
在AWS Cognito中,可以默认设置用户属性和分组等功能,但在Firebase中,需要与Firestore结合使用。这一点需要注意。
实际构建应用程序时,除了用户身份验证,还需要后端API、数据库和静态文件托管等功能。根据所需的功能进行选择,我认为使用Angular + AngularFire的组合可以轻松构建SPA。
根据“Angular&Firebaseを使ってがっつりサーバーレスなWEBサービスを開発・運用したノウハウ”这篇文章,实际上,alclimb先生提出了不需要服务器端的观点。文章利用了“Angular”和“Firebase”来实现完全无服务器的Web服务。
明天是Hands Lab 2017年圣诞日历的第11天,我们有幸再次邀请到@watarukura先生分享✨
请参考一下
1. 安装和设置 – angularfire2
通过Angular2学习Firebase入门 – HTML5 Experts.jp