🐈

Angular × Firebase Authentication ログイン機能の実装

2022/05/08に公開

シングルページアプリケーションを開発する際に Firebase Authentication を利用すると簡単にログイン機能を実装することができます。

今回は Angular のアプリにログイン機能を実装する方法についてまとめます。

動作環境

今回は以下のバージョンを利用。

  • node: v14.15.3
  • Angular: 13.3.0
  • Firebase: ^9.4.0
  • AngularFire: ^7.2.1

AngularFire の README に利用可能なバージョンが書いてあります。バージョンがずれていると動作しないので注意が必要です。

Firebase 側の設定

Firebase のコンソール画面を開いて Authentication メニューからログインプロバイダ「メール / パスワード」を有効にします。

アプリ側の設定

  1. Angular で Fireabse を扱うためのライブラリをインストール

    ng add @angular/fire
    

    ng add コマンドを使うことで設定を自動で行ってくれるので便利。

  2. angular.json を開いて scripts の下あたりに設定を追加

    "allowedCommonJsDependencies": [
    	"firebase",
    	"@firebase/database"
    ]
    
  3. 型定義ファイルをインストール

    npm i @types/angularfire --save-dev
    
  4. 環境変数をセット

    Firebaseのプロジェクトの設定 > マイアプリ > 構成 から firebaseConfig の値が app/environments/environment.ts に fireabse プロパティとしてセットされている事を確認。

    ng add コマンドを実行した際に書き込まれているはず。

    export const environment = {
    	production: false,
    	apiKey: ...
      authDomain: ...
      databaseUrRL: ... 
    
  5. モジュールを有効にする

    app/app.modules.ts で AngualrFire のモジュールを設定する。

    import { AngularFireModule } from '@angular/fire/compat';
    import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
    import { provideAuth, getAuth } from '@angular/fire/auth';
    
    imports: [
      // 略
      AngularFireModule.initializeApp(environment.firebase),
      provideFirebaseApp(() => initializeApp(environment.firebase)),
      provideAuth(() => getAuth()),
    ],
    

Auth Service の作成

Angular CLI でログイン処理用のサービスクラスを生成。

ng g service auth

src/app/auth.service.ts ができるのでログイン、ログアウト、状態取得の関数を実装。

import { Injectable, Optional } from '@angular/core';
import { signInWithEmailAndPassword, signOut } from 'firebase/auth';
import { Auth } from '@angular/fire/auth';
import { authState } from 'rxfire/auth';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  constructor(@Optional() public auth: Auth) {}

  login(email: string, password: string) {
    return signInWithEmailAndPassword(this.auth, email, password);
  }

  logout() {
    return signOut(this.auth);
  }

  getAuthState() {
    return authState(this.auth);
  }
}

Auth Guard の作成

画面遷移の前にログイン判定を行いたいので guard を生成。

ng g guard guard/auth

src/app/guard/auth.guard.ts ができるのでログイン済みでなければログイン画面にリダイレクトさせるための処理を実装。

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  RouterStateSnapshot,
  UrlTree,
} from '@angular/router';
import { Observable, of, pipe, mergeMap } from 'rxjs';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';

@Injectable({
  providedIn: 'root',
})
export class AuthGuard implements CanActivate {
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ):
    | Observable<boolean | UrlTree>
    | Promise<boolean | UrlTree>
    | boolean
    | UrlTree {
    return this.authService
      .getAuthState()
      .pipe(
        mergeMap((user) =>
          user ? of(true) : this.router.navigateByUrl('/login')
        )
      );
  }
}

ルーティング設定

作成した guard をルーティングで利用することでログイン判定を行う。
今回はパスが admin or detail/:id の場合にログイン判定する。
src/app/app-routing.module.tscanActivate を使うことで guard を利用できる。

import { AuthGuard } from './guard/auth.guard';

const routes: Routes = [
  { path: '', component: FormComponent },
  { path: 'login', component: LoginComponent },
  {
    path: 'admin',
    component: ListComponent,
    canActivate: [AuthGuard],
  },
  {
    path: 'detail/:id',
    component: DetailComponent,
    canActivate: [AuthGuard],
  },
];

@NgModule({
  declarations: [],
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}

コンポーネント作成

ログイン画面のフォームとログイン状態の有無に応じて表示を切り替えるためのヘッダーコンポーネントを作成します。

ログインフォーム

html ファイルを編集。

<h2>ログイン</h2>
<div class="container-sm">
  <form #form="ngForm" (ngSubmit)="login(form)">
    <div class="col-md-6 mx-auto">
      <div class="mb-3">
        <input
          ngModel
          type="email"
          name="email"
          class="form-control"
          placeholder="メールアドレスを入力"
        />
      </div>
      <div class="mb-3">
        <input
          ngModel
          type="password"
          name="password"
          class="form-control"
          placeholder="パスワードを入力"
        />
      </div>
      <div class="text-center">
        <button type="submit" class="btn btn-primary">ログイン</button>
      </div>
    </div>
  </form>
</div>

ブラウザでの表示はこんな感じ。

ts ファイルを編集。ログイン認証が成功すると管理画面に遷移させる。

import { OnInit, Component } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Router } from '@angular/router';
import { signInWithEmailAndPassword } from 'firebase/auth';
import { AuthService } from '../auth.service';

@Component({
  selector: 'ia-list',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.scss'],
})
export class LoginComponent implements OnInit {
  constructor(private authService: AuthService, private router: Router) {}

  ngOnInit(): void {}

  login(form: NgForm): void {
    const { email, password } = form.value;
    signInWithEmailAndPassword(this.authService.auth, email, password)
      .then(() => this.router.navigateByUrl('/admin'))
      .catch((error) => alert(error));
  }
}

ヘッダー

html ファイルを編集。

<nav class="navbar navbar-light bg-light">
  <div class="container">
    <div>
      <h1 class="fs-3 mx-auto mt-3">{{ title }}</h1>
    </div>
    <ul class="nav justify-content-end" *ngIf="isAdmin">
      <li class="nav-item">
        <button
          type="button"
          (click)="logout()"
          class="btn btn-outline-secondary"
        >
          ログアウト
        </button>
      </li>
    </ul>
  </div>
</nav>

ブラウザではログイン時にログアウトボタンが表示される。

未ログイン時は表示なし。

ts ファイルではログイン状態を取得して isAdmin に真偽値を代入する。
ログアウトボタンを押すと signOut してログイン画面へ遷移する。

import { Component, OnInit } from '@angular/core';
import { signOut } from 'firebase/auth';
import { Router } from '@angular/router';
import { AuthService } from '../auth.service';

@Component({
  selector: 'ia-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.scss'],
})
export class HeaderComponent implements OnInit {
  title = 'Your App Title';
  isAdmin: boolean = false;

  constructor(public authService: AuthService, private router: Router) {}

  ngOnInit(): void {
    this.authService.getAuthState().subscribe((user) => {
      if (user) this.isAdmin = true;
      else this.isAdmin = false;
    });
  }

  logout() {
    signOut(this.authService.auth).then(() =>
      this.router.navigateByUrl('/login')
    );
  }
} 

まとめ

Firebase Authentication を利用して Angular のアプリにログイン機能を実装する方法についてまとめました。

サクッとログイン機能を作りたい場面はよくあると思うので今回の記事が参考になれば幸いです。

Discussion