Edited at

Angular+Firebase Authenticationで認証機能を導入する

More than 1 year has passed since last update.


この記事はAngular+Firebaseでチャットアプリを作るのエントリーです。

前記事:AngularのRxJSを使ってデータの受け渡しをする

次記事:Angularのルーティング設定(応用編)



この記事で行うこと

前回の記事ではRxJSの使い方を学習しました。

前回作成したsessionサービスを、Firebase Authenticationを使ってサーバーサイドのユーザー管理システムと連携させます。


Firebase Authenticationとは

Firebase Authenticationは、Firebaseが提供するマルチプラットフォームログイン機能です。

自分で実装するとかなり面倒なユーザー管理機能ですが、Firebase Authenticationはその機能を無料で利用することができ、かつユーザーIDのデバイス間共有も容易にしてくれる優れものです。

認証はある程度類型化された機能ではありますが、少しでも注意を怠るとセキュリティ障害を引き起こします。スタートアップや小さいプロダクトの場合、できるだけその商品のMVP(Minimum Viable Product)に焦点を絞って開発したいのですが、認証機能は手を抜くわけにはいかないので、そこに時間と資金が取られてしまいます。

Firebase Authenticationは、メール認証やOAuth認証といった現代のニーズに即した認証機能を保有し、かつサーバーサイドの安全性を担保してくれるので、プロダクトマネジメントの視点からも採用を考慮すべきサービスだと思います。


Firebase Authenticationでできること

機能
概要
本記事での使用

FirebaseUI Auth
Firebase謹製のUIパッケージ。必要な機能を全部のせできる。
×

メール&パスワード認証
メールアドレスとパスワードによる認証

フェデレーション ID プロバイダとの統合
OAuth認証方式で、Facebookやtwitterなどの外部サービスとのID連携を利用できる
×

電話番号認証
スマートフォンにSMSを送信してユーザーを認証する
×

カスタム認証
アプリの既存のログインシステムと連携して、RTDBなどのFirebase製品を利用する
×

匿名認証
ゲスト用認証
×


参考

公式ドキュメント


実装内容


Firebase Authenticationの設定をする


ログイン方法を設定する

開発に取り掛かる前に、FirebaseのコンソールからFirebase Authenticationを有効化しておきます。

NgChat – Authentication – Firebase console.png

本アプリケーションでは「メール&パスワード認証」を利用します。

「メール / パスワード」を有効にして保存してください。

NgChat – Authentication – Firebase console.png


テンプレートを設定する

次に、メールアドレスのテンプレートを変更します。

タブの中にある「テンプレート」を開き、画面左下にある「テンプレート言語」から「Japanese」を選択します。

NgChat – Authentication – Firebase console (1).png

「パスワードの再設定」「メールアドレスの変更」については文言の変更ができますが、「メールアドレスの確認」は文言変更ができません。

もし返信用のドメインを変更したい場合は、鉛筆マークを押した先にある編集画面から「ドメインをカスタマイズ」を選択してください。

※変更したドメインは、すべてのテンプレートに反映されます。

これでコンソール側の設定は完了です。

次はアプリケーション側の実装に入っていきます。


AngularFireAuthをsessionサービスに組み込む

まず、前回作成したsessionサービスに、AngularFireAuthをDIします。


src/core/service/session.service.ts

import { AngularFireAuth } from 'angularfire2/auth'; // 追加

import * as firebase from 'firebase/app'; // 追加

// ~~~ 省略 ~~~

constructor(private router: Router,
private afAuth: AngularFireAuth) { } // 追加


AngularFireAuthはCoreモジュールですでにimportしているので、コンポーネント内の記述だけで問題ありません。

参考: AngularのNgModuleを使って、アプリの構成を管理する

このAngularFireAuthのメソッドであるcreateUserWithEmailAndPassword()と、その返り値にあるfirebase.Userが持つメソッドsendEmailVerification()を使って、アカウント作成用のメソッドを作成します。

その際、メールアドレスとパスワードを引数に渡す必要があるので、新しくPasswordクラスを作成します。


src/class/chat.ts

export class Password { // 追加

email: string;
password: string;
password_confirmation: string;

constructor() {
this.email = '';
this.password = '';
this.password_confirmation = '';
}

reset(): void {
this.email = '';
this.password = '';
this.password_confirmation = '';
}
}



src/core/service/session.service.ts

  // アカウント作成

signup(account: Password): void { // 追加
this.afAuth
.auth
.createUserWithEmailAndPassword(account.email, account.password) // アカウント作成
.then( auth => auth.user.sendEmailVerification()) // メールアドレス確認
.then(() => alert('メールアドレス確認メールを送信しました。'))
.catch( err => {
console.log(err);
alert('アカウントの作成に失敗しました。\n' + err)
})
}

createUserWithEmailAndPassword()でアカウントを作成することはできますが、それだけではアカウントを有効にすることができません。sendEmailVerification()を設定することで、前述したテンプレートの内容を登録したメールアドレス宛に送信し、メールアドレスの実在を確認することができます。

次に、前回作成したlogin()メソッドとlogout()メソッドにAngularFireAuthsignInWithEmailAndPassword()signOut()を追加します。


src/core/service/session.service.ts

  login(account: Password): void { // 変更

this.afAuth
.auth
.signInWithEmailAndPassword(account.email, account.password)
.then(auth => {
// メールアドレス確認が済んでいるかどうか
if (!auth.user.emailVerified) {
this.afAuth.auth.signOut();
return Promise.reject('メールアドレスが確認できていません。');
} else {
this.session.login = true;
this.sessionSubject.next(this.session);
return this.router.navigate(['/']);
}
})
.then(() => alert('ログインしました。'))
.catch( err => {
console.log(err);
alert('ログインに失敗しました。\n' + err);
})
}

logout(): void {// 変更
this.afAuth
.auth
.signOut()
.then(() => {
this.sessionSubject.next(this.session.reset());
return this.router.navigate(['/account/login']);
}).then(() => alert('ログアウトしました。'))
.catch( err => {
console.log(err);
alert('ログアウトに失敗しました。\n' + err);
})
}


ここで使用したAngularFireAuthのメソッドをまとめておきます。

メソッド名
機能
戻り値

createUserWithEmailAndPassword(email, password)
メールアドレスとパスワードでアカウントを作成する
firebase.auth.UserCredential

sendEmailVerification(actionCodeSettings)
メールアドレス有効化メールを送信する
void

signInWithEmailAndPassword(email, password)
メールアドレスとパスワードでログインする
firebase.auth.UserCredential

signOut()
ログアウトする
void


参考

公式ドキュメント


ログイン画面に認証機能を実装する


サインアップ画面の実装

前項で作成したsessionサービスのメソッドをビューに設置していきます。

サインアップ画面の実装にかかる前に、今回はFormModuleを使用するため、account.module.tsにSharedモジュールをimportしておきます。


src/account/account.module.ts

// CommonModuleを削除

import { SharedModule } from '../shared/shared.module'; // 追加
import { RouterModule, Routes } from '@angular/router';

// ~~~ 省略 ~~~

@NgModule({
imports: [
// CommonModuleを削除
SharedModule, // 追加
RouterModule.forChild(appRoutes),
],


これでFormModuleが使えるようになりました。

次に、sign-up.component.tsSessionServiceをDIし、submitSignUp()メソッドを追加します。


src/account/sign-up/sign-up.component.ts

import { Component, OnInit } from '@angular/core';

import { Password } from '../../class/chat'; // 追加
import { SessionService } from '../../core/service/session.service'; // 追加

@Component({
selector: 'app-sign-up',
templateUrl: './sign-up.component.html',
styleUrls: ['./sign-up.component.css']
})
export class SignUpComponent implements OnInit {

public account = new Password(); // 追加

constructor(private session: SessionService) { } // 追加

ngOnInit() {
}

// アカウント作成
submitSignUp(e: Event): void { // 追加
e.preventDefault();
// パスワード確認
if(this.account.password !== this.account.password_confirmation) {
alert('パスワードが異なります。');
return;
}
this.session.signup(this.account);
this.account.reset();
}

}


ここではパスワード確認のバリデーションをsubmitSignUp()内部で行っていますが、カスタムディレクティブを使ってテンプレート側で制御することもできます。

※本記事では取り上げません。機会があれば書きます。

さらに、テンプレート側の実装を行います。


src/account/sign-up/sign-up.component.html

<div class="page">

<section class="card">
<form class="form-signup" (ngSubmit)="submitSignUp($event)" #signUpForm="ngForm"><!-- 追加 -->
<h2 class="form-signup-heading">アカウント作成</h2>
<label for="inputEmail" class="sr-only">
Email address
</label>
<input type="email" name="email"
id="inputEmail" class="form-control"
placeholder="Email address"
[(ngModel)]="account.email"
#email="ngModel"
autocomplete="username email" required autofocus><!-- #email, name, autocompleteを追加 -->
<label for="inputPassword" class="sr-only">
Password
</label>
<input type="password" name="password"
id="inputPassword" class="form-control"
placeholder="Password"
[(ngModel)]="account.password"
#password="ngModel"
autocomplete="new-password" required><!-- #password, name, autocompleteを追加 -->
<label for="confirmationPassword" class="sr-only">
Password
</label>
<input type="password" name="password_confirmation"
id="confirmationPassword" class="form-control"
placeholder="Password Confirmation"
[(ngModel)]="account.password_confirmation"
#password_confirmation="ngModel"
autocomplete="new-password" required><!-- #password_confirmation, name, autocompleteを追加 -->
<button class="btn btn-lg btn-success btn-block"
type="submit" [disabled]="!signUpForm.form.valid">
SIGN UP
</button><!-- clickイベントを削除し、disabledを追加 -->
</form>
<a routerLink="/account/login"class="signup-link">
ログインする
</a>
</section>
</div>

ngFormngModelFormModuleのビルトインディレクティブです。

#signUpFormなどのテンプレート参照変数を使って、フォーム内の値を参照し、ログインボタンのバリデーションに利用しています。

#email#password#password_confirmationのいづれか1つの値がvalidでない場合、[disabled]が有効になってボタンをクリックできなくなります。

では、この状態でng serveを実行し、挙動を確認します。

スクリーンショット 2018-03-07 14.03.13.png.png

送信されたメールアドレスから、メールアドレス有効化のリンクをクリックします。

https   ngchat 5186e firebaseapp com __ auth action mode verifyEmail oobCode Z0DtV5_9V3nyXPO8ZfEBRk j7 GQ4wmcH8VJrpTP8BoAAAFh_td_Wg apiKey AIzaSyBH9h5ZR21mRTTYs _wYy55UGUEugSPma0.png

この有効化完了画面は、Firebaseによって用意されたテンプレートを使用しています。

もしこの画面を変更したい場合は、firebaseコンソール画面のテンプレート編集から、「アクション URL をカスタマイズ」を選択して任意のURLを設定します。


ログイン画面の実装

サインアップ画面同様、ログイン画面の実装を行います。


src/account/login/login.component.ts

import { Component, OnInit } from '@angular/core';

import { SessionService } from '../../core/service/session.service';
import { Password } from '../../class/chat'; // 追加

@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
export class LoginComponent implements OnInit {

public account = new Password(); // 追加

constructor(private sessionService: SessionService) { }

ngOnInit() {
}

submitLogin(e: Event) {
e.preventDefault();
this.sessionService.login(this.account); // 変更
}

}



src/account/login/login.component.html

<div class="page">

<section class="card">
<form class="form-login" (ngSubmit)="submitLogin($event)" #loginForm="ngForm"><!-- 追加 -->
<h2 class="form-login-heading">
ログイン
</h2>
<label for="inputEmail" class="sr-only">
Email address
</label>
<input type="email" name="email"
id="inputEmail" class="form-control"
placeholder="Email address"
[(ngModel)]="account.email"
#email="ngModel"
autocomplete="username email" required autofocus><!-- #email, name, autocompleteを追加 -->
<label for="inputPassword" class="sr-only">
Password
</label>
<input type="password" name="password"
id="inputPassword" class="form-control"
placeholder="Password"
[(ngModel)]="account.password"
#password="ngModel"
autocomplete="new-password" required><!-- #password, name, autocompleteを追加 -->
<button class="btn btn-lg btn-info btn-block" type="submit"
[disabled]="!loginForm.form.valid">
LOGIN
</button><!-- clickイベントを削除し、disabledを追加 -->
</form>
<a routerLink="/account/sign-up" class="login-link">
アカウントを作る
</a>
</section>
</div>

先ほど作成したアカウントを使って、ログインしてみます。

スクリーンショット 2018-03-07 14.24.38.png.png

正しいアカウントでログインを行うと、「ログインしました。」というアラートが表示され、チャット画面に遷移するようになります。

もしこの時、間違ったアカウントで登録しようとすると、次のように表示されます。

スクリーンショット 2018-03-07 14.31.53.png.png

認証先のFirebaseが送信されたemail、パスワードを評価し、その結果を返してくれます。

パスワード認証にかかるエラーレスポンスは次の通りです。


サインアップ時のエラーレスポンス

エラーコード
概要

auth/email-already-in-use
メールアドレスがすでに使用されている

auth/invalid-email
メールアドレスの形式が正しくない

auth/operation-not-allowed
メールアドレス/パスワード方式が有効化されていない

auth/weak-password
パスワードが弱すぎる


ログイン時のエラーレスポンス

エラーコード
概要

auth/invalid-email
メールアドレスの形式が正しくない

auth/user-disabled
ユーザーが無効化されている

auth/user-not-found
ユーザーが見つからない

auth/wrong-password
パスワードが間違っている

参考:公式ドキュメント

これで認証機能のベースは整いました。

ただ、今の段階ではたとえログインをしていなくても直接URLを入力すると、チャット画面に遷移することができてしまいます。

これでは認証機能として意味がありませんので、次回はルーティングのガード設定を行っていきます。


ソースコード

この時点でのソースコード

※firebaseのapiKeyは削除しているので、試すときは自身で作成したapiKeyを入れてください。