NCMBとFramework7を使った認証コンポーネントの紹介と使い方

NCMBはモバイルアプリ開発におけるバックエンド機能（認証、データストア、ファイルストア、プッシュ通知など）を提供しています。バックエンドなのでAPIベースで利用するのが基本で、UI（アプリ側）は各自で開発する仕組みになっています。

現在、数多くのアプリが存在し、その中には定番とも言える機能があります。そうした定番機能を各フレームワークごとに実装しておくことで、再利用性高くNCMBが利用できるかと思います。

今回はFramework7で作った認証コンポーネントを紹介します。Monacaアプリでも利用可能です。

## UIについて

コンポーネントは1つのHTMLだけで実装されているのが特徴です。そのため、基本的には以下の方法で導入・利用ができます。

1. NCMB SDKの読み込み
1. NCMBのキーの取得
1. NCMBの初期化
1. ルーティングの設定
1. 認証UI（HTML）を配置

用意されている画面（機能）は次の通りです。

### 新規登録機能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/851bc583-2f13-ecc6-fc6b-eec2858d4693.png)

新規登録はメールアドレスを入力し、そのメールに届いたURLに沿って行います。

### ログイン機能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/1a6ae303-b49f-e8b8-7e3b-8433dcb628bc.png)


登録したメールアドレスとパスワードを使ってログインを行います。

### パスワードリマインダー機能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/e2f5fac9-776d-0a9e-4515-9704b4e11f85.png)


パスワードを忘れたユーザー向けにパスワードリマインダーを送信します。

### 必要なライブラリ・SDKの読み込み

今回利用しているライブラリ・SDKは次の通りです。

- NCMB JavaScript SDK

これらを `www/index.html` で読み込みます。

```html
<script src="js/ncmb.min.js"></script>
```

### 必要なキー・トークンの取得

NCMBのアプリケーションキーとクライアントキーを取得します。

- [ニフクラ mobile backend](https://mbaas.nifcloud.com/)

### NCMBの初期化

`www/js/app.js` にてNCMBを初期化します。今回は `www/js/config.json` というファイルにキーを記述しているので、以下のように読み込みを行っています。

```js
const $ = Dom7;
(async () => {
  const device = Framework7.getDevice();
	// 設定ファイルの読み込み
  const config = await (await fetch('./js/config.json')).json();
	// NCMBの初期化
  window.ncmb = new NCMB(config.applicationKey, config.clientKey);

  // Framework7の初期化
  window.app = new Framework7({
    name: 'NCMB Notice',
    theme: 'auto',
    el: '#app',
    id: 'com.nifcloud.mbaas.map',
    store: store,
    routes: routes,
    input: {
      scrollIntoViewOnFocus: device.cordova && !device.electron,
      scrollIntoViewCentered: device.cordova && !device.electron,
    },
    statusbar: {
      iosOverlaysWebView: true,
      androidOverlaysWebView: false,
    },
    on: {
      init: function () {
        if (this.device.cordova) {
          cordovaApp.init(this);
        }
      },
    },
  });
})();
```

### ルーティングの設定

今回は最低限のルーティングを設定しています（ `www/js/routes.js` ）。 `/auth` で認証コンポーネントを表示します。

```js
const routes = [
  {
    path: '/',
    url: './index.html',
  },
  {
    path: '/auth',
    componentUrl: './pages/auth.html',
  },
  // Default route (404 page). MUST BE THE LAST
  {
    path: '(.*)',
    url: './pages/404.html',
  },
];
```

`www/index.html` で `/auth` を最初に表示します。

```html
<div id="app">
	<!-- Your main view, should have "view-main" class -->
	<div class="view view-init safe-areas" data-url="/auth">
	</div>
</div>
```

### 認証UI（HTML）を配置

後は [auth.html](https://raw.githubusercontent.com/NCMBMania/framework7_auth_component/main/www/pages/auth.html) をダウンロードして、`www/pages/map.html` に配置するだけです。

## 認証の設定

NCMBの管理画面でメールアドレス認証を有効にします。また、ID認証は無効にします。これでメールアドレス認証のみが使えるようになります。

![FireShot Capture 071 - 20220218140600 ニフクラ mobile backend - console.mbaas.nifcloud.com.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/24e0e57e-b574-404c-841f-7fecc1f8cadd.jpeg)


パスワードを忘れた場合、メールアドレス認証が有効でないとパスワード再設定用のURLが送信できません。そのため、メールアドレス認証にしておく方が後々の運用上楽になるでしょう。


## 認証コンポーネントについて

認証コンポーネントの内容は次のようになります。実装はコメントを参照してください。

```html
<template>
  <div class="page">
    <div class="navbar">
      <div class="navbar-bg"></div>
      <div class="navbar-inner sliding">
        <div class="title">認証</div>
      </div>
    </div>
    <div class="page-content">
      <p class="segmented">
        <button class="button ${ viewType !== 'signup' && 'button-disabled'}" @click=${() => changeView('signup')}>新規登録</button>
        <button class="button ${ viewType !== 'login' && 'button-disabled'}" @click=${() => changeView('login')}>ログイン</button>
      </p>
      <div class="list">
        <ul>
          <li class="item-content item-input">
            <div class="item-inner">
              <div class="item-title item-label">メールアドレス</div>
              <div class="item-input-wrap">
                <input type="email" name="email" required placeholder="メールアドレスを入力してください" />
              </div>
            </div>
          </li>
          ${ viewType === 'login' && $h`
            <li class="item-content item-input">
              <div class="item-inner">
                <div class="item-title item-label">パスワード</div>
                <div class="item-input-wrap">
                  <input type="password" name="password" placeholder="パスワード"/>
                </div>
              </div>
            </li>
          `}
        </ul>
      </div>
      <div class="list">
        <ul>
          <li>
            ${ viewType === 'signup' && $h`
              <a href="#" class="item-link list-button login-button" @click=${signUp}>新規登録</a>
            `}
            ${ viewType === 'login' && $h`
              <a href="#" class="item-link list-button login-button" @click=${login}>ログイン</a>
            `}
            ${ viewType === 'reminder' && $h`
            <a href="#" class="item-link list-button login-button" @click=${reminder}>パスワード再設定メールを送信</a>
            `}
          </li>
        </ul>
        <div class="block-footer">
          <a href="#" @click=${() => changeView('reminder')}>パスワードを忘れた場合はこちら</a></div>
      </div>
    </div>
  </div>
</template>
<style>
  .button-disabled {
    color: grey;
  }
</style>
<script>
  export default function (props, {$f7, $f7router, $update }) {
    // NCMBが初期化されているかチェックします
    if (typeof ncmb === 'undefined') throw 'NCMBが初期化されていません';
    // ログイン状態を取得して処理分けします
    const currentUser = ncmb.User.getCurrentUser();
    if (currentUser) {
      // すでにログインしている場合は戻るなど
      // $f7router.back(); // 戻る場合
      return;
    }
    let viewType = 'signup'; // 表示分け用
    // 表示を切り替える関数
    const changeView = (type) => {
      viewType = type;
      $('[name="email"]').focus();
      $update();
    };
    // 新規登録用関数
    const signUp = () => {
      // メールアドレスの入力チェック
      const email = $('[name="email"]').val();
      if (email.trim() === '') {
        app.dialog.alert('メールアドレスが入力されていません', '新規登録エラー');
        return;
      }
      // 新規登録用メールを送信
      ncmb.User.requestSignUpEmail(email);
      app.dialog.alert(`${email}宛に<br />新規登録用URLを送信しました。<br />メールボックスを確認してください。`)
    };

    // ログイン用関数
    const login = async () => {
      // メールアドレスとパスワードの入力チェック
      const email = $('[name="email"]').val();
      const password = $('[name="password"]').val();
      if (email.trim() === '') {
        app.dialog.alert('メールアドレスが入力されていません', 'ログインエラー');
        return;
      }
      if (password.trim() === '') {
        app.dialog.alert('パスワードが入力されていません', 'ログインエラー');
        return;
      }
      try {
        // ログイン処理
        await ncmb.User.loginWithMailAddress(email, password);
        app.dialog.alert('ログインしました', 'ログイン成功');
        // $f7router.back(); // 戻る場合
      } catch (e) {
        // エラーの場合はログインエラー
        app.dialog.alert('メールアドレスとパスワードを確認してください', 'ログインエラー');
      }
    };

    // パスワードリマインダー用関数
    const reminder = () => {
      const email = $('[name="email"]').val();
      if (email.trim() === '') {
        app.dialog.alert('メールアドレスが入力されていません', '入力エラー');
        return;
      }
      // パスワードリマインダー送信
      const user = new ncmb.User;
      user.mailAddress = email;
      user.requestPasswordReset();
      app.dialog.alert(`${email}宛に<br />パスワード再設定用URLを送信しました。<br />メールボックスを確認してください。`)
    };
    return $render;
  }
</script>
```

## カスタマイズポイント

このコンポーネントでは認証後のフローは記述していません。認証後に前の画面に戻る場合には `$7router.back()` を使ってください。これはすでに認証している場合にも使えます。

## まとめ

認証コンポーネントを使えば、ログインや新規登録を行うといったよくある実装が簡単に実現できます。ぜひお試しください。

[mBaaSでサーバー開発不要！ | ニフクラ mobile backend](https://mbaas.nifcloud.com/)