世界最大級のクラウドサービスとして有名なAWS。とても多機能で色々なことができますが、使いこなすには難易度が高いサービスでもあります。
スマートフォンアプリ開発をされている方の中には、バックエンド(サーバ)側の知識にあまり馴染みが無いという方も多いのではないでしょうか。そういった方におすすめしたいのが、AWS Mobile Hubです。AWSの中でもモバイルアプリ開発に利用できる機能をまとめて提供しているサービスになります。
本連載では、AWS Mobile Hubの各種機能をMonacaアプリに組み込む方法を紹介します。

AWS Mobile Hubで提供される機能について

AWS Mobile Hubでは以下の機能が提供されます。

  • 認証
    Amazon Cognitoを利用
  • ストレージ
    Amazon Simple Service Storage(S3)を利用
  • サーバレス機能
    AWS Lambda & API Gatewayを利用
  • データベース
    Amazon DynamoDBを利用
  • ボット
    Amazon Lexを利用
  • 実機テスト
    AWS Device Farmを利用
  • Webサイトホスティング
    Amazon S3 & Cloudfrontを利用
  • 分析
    Amazon Pinpointを利用
  • SMS/メール/プッシュ通知
    Amazon Pinpointを利用
  • A/B テスト
    Amazon Pinpointを利用

いずれも既存のAWSの機能を使っています。それらを一つ一つ設定するのは面倒なのですが、AWS Mobile Hubを使うことで設定の手間を減らせるようになっています。

認証機能について

今回紹介する認証機能は以下の要件を満たしています。本記事で作成するサンプルアプリでは、基本となるID/パスワード認証とメールアドレス検証の実装について紹介します。

  • 条件の自由な設定(文字数、含む文字の種類)
  • SMS、メールでの認証コード送信
  • セッション
  • 二段階認証アプリ対応
  • Facebook/Google/Twitter認証

前提条件

この記事では、まずAWSのアカウントは持っていることとします。また、MonacaアプリはMonaca CLIを使ってローカルコンピュータ上で開発します。Monaca CLIを利用するのでNode.jsもインストールされていることとします。

インストール

AWS Mobile HubのCLIツールをインストールします。

$ npm install -g awsmobile-cli

Monacaアプリの作成

Monaca CLIを使ってアプリを作ります。テンプレートはなんでも構いませんが、ここでは「Onsen UI > Onsen UI V2 JS Minimum」を選択します。

$ monaca create AWSHubDemo

wwwディレクトリに移動し、srcディレクトリを作成しておいてください。

$ cd AWSHubDemo/www
$ mkdir src

AWS Mobile Hubでプロジェクトを作る

AWS Mobile Hubのコンソールにサインインし、「Create Project」からウィザードに従ってプロジェクトを作成します。

プラットフォームは「Web」を選びます。
その際、「Enable web hosting with your app(ホスティングを有効にする)」のチェックは外しておきましょう。

ウィザードを進めるとコマンドが表示されますので、一番最後のコマンドをMonacaプロジェクトのwwwディレクトリで実行します。

$ awsmobile init xxxxxxxxxxxx

実行すると、初期設定項目の入力を求められますが、すべてデフォルトのままEnterを押していきます。
すると、以下のようにキーの入力がが求められます。

Please enter the access key of the newly created user:
? accessKeyId:  (<YOUR_ACCESS_KEY_ID>)

Enterを押すとブラウザで AWS IAM のページが開くので、「プログラムによるアクセス」にチェックが入った状態で次に進みます。

AWSMobileHub_FullAccess 権限を割り当てて次へ進みます。

完了画面に表示される「アクセスキーID」と「シークレットアクセスキー」をコマンドラインから入力してください。

Please enter the access key of the newly created user:
? accessKeyId:  xxxxxxxxxxx
? secretAccessKey:  xxxxxxxxxxxxxxxxxxxxxxxxxxx

srcディレクトリ内に aws-exports.js というファイルができていれば完了です。

認証機能の追加

AWS Mobile Hubでプロジェクトができたら、「User Sign-in」 を追加します。

認証はEmail and Passwordとします。パスワード長や含むべき文字種なども指定できます。

作成後、Action > Edit in Cognito を選択します。

サイドメニューの中から「MFAそして確認」をクリックします。
「多要素認証(MFA)を有効にしますか?」については必須または省略可能とします。

「第2の要素」は「SMSテキストメッセージ」または「時間ベースのワンタイムパスワード」のどちらかを選択します。
SMSテキストメッセージは、何通か送るとAmazon SNSの制限に引っかかってしまうので、今回は「時間ベースのワンタイムパスワード」を選択します。

認証機能の反映

バックエンドに認証機能を追加したら、ローカルのプロジェクトを最新の状態に更新します。Monacaプロジェクトのwwwディレクトリ内で、以下のコマンドを実行すると認証機能が使えるようになります。

$ awsmobile pull

JavaScriptの実装と変換

gulpを使ってソースの変換を行いますので、npm install コマンドで以下のライブラリをインストールしてください。

  • gulp
  • browserify
  • babelify
  • babel-register
  • babel-preset-es2015
  • vinyl-source-stream

src/app.js を作成し、AWSのJavaScriptライブラリ「AWS Amplify」から利用したい機能を取り出します。今回は認証機能用のモジュール「Auth」を取得します。

src/app.js

import Amplify, { Auth } from 'aws-amplify';
import aws_exports from './aws-exports.js';
Amplify.configure(aws_exports);

ons.ready(function() {
  window.Auth = Auth;
});

このファイルのままではMonacaから使えないので、ソースを変換します。今回はgulpを使います。wwwの中に gulpfile.babel.js を作成します。

gulpfile.babel.js

import gulp from 'gulp';
import browserify from "browserify";
import babelify from "babelify";
import source from "vinyl-source-stream";

gulp.task("default", () => {
  browserify({
      entries: ["./src/app.js"]
    })
    .transform(babelify, {presets: ['es2015']})
    .bundle()
    .pipe(source("app.js"))
    .pipe(gulp.dest("./js"));
});

gulp.task('watch', function(){
  gulp.watch('./src/*.js', ['default']);
});

さらに www 以下に .babelrc を作成します。これでES2015の構文が使えます。

.babelrc

{
  "presets": ["es2015"]
}

そしてgulpコマンドを実行します。完了すると、変換されたソースファイルが js ディレクトリ内に作られているはずです。

$ gulp

各画面の実装

サインアップ画面

まずはサインアップする画面を作ります。
サインアップの流れは、ユーザ情報登録後、メールにてコードが届きます。それをAWS Mobile Hubに送信するとアカウントの確認が取れたことになります。

HTMLはOnsen UIを使って実装しています。注意点として、電話番号は国番号付きのダッシュ記号(-)なしで入力する必要があります。

signup.html

<ons-page>
  <ons-toolbar>
    <div class="center">Sign In</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 30px;">
    <p>
      <ons-input id="username" modifier="underbar" placeholder="Username" float></ons-input>
    </p>
    <p>
      <ons-input id="email" modifier="underbar" placeholder="Email" float></ons-input>
    </p>
    <p>
      <ons-input id="phone_number" modifier="underbar" placeholder="+81801234567" float></ons-input>
    </p>
    <p>
      <ons-input id="password" modifier="underbar" type="password" placeholder="Password" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="sign_up_button">Sign up</ons-button> or 
      <ons-button modifier="quiet" id="to_sign_in">Sign in</ons-button>
    </p>
  </div>    
  <script>
    // 初期化時に実行される処理
    ons.getScriptPage().onInit = function() {
      this.querySelector('#sign_up_button').onclick = (e) => {
        const username = this.querySelector('#username').value;
        const password = this.querySelector('#password').value;
        const email = this.querySelector('#email').value;
        const phone_number = this.querySelector('#phone_number').value;
        // サインアップ処理
        Auth.signUp({
          username,
          password,
          attributes: {
            email,
            phone_number
          },
          validationData: []
        })
        .then(data => {
          // 確認コード入力画面に遷移
          document.getElementById('nav').pushPage('code.html', {
            data: {
              user: data.user
            }
          })
        })
        .catch(err => console.log(err));
      };

      this.querySelector('#to_sign_in').onclick = (e) => {
        document.getElementById('nav').pushPage(`signin.html`);
      }
    };
  </script>
</ons-page>

Auth.signUp でユーザ作成処理を行います。ユーザ作成が完了したらコード入力を行うための画面(code.html)に遷移します。この時、ユーザ情報をパラメータとして次画面に渡します。

ここまでの処理を実行すると、AWS上にステータスが未確認(UNCONFIRMED)のユーザが作成されます。

コード入力画面

次に、ユーザ宛にメールで送信されたコードを入力する画面を作ります。

code.html

<ons-page>
  <ons-toolbar>
    <div class="left"><ons-back-button>Back</ons-back-button></div>
    <div class="center">Input Code</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 30px;">
    <p>
      <ons-input id="code" modifier="underbar" placeholder="999999" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="confirm">Input code</ons-button>
    </p>
  </div>    
  <script>
  // 初期化時に実行される処理
    ons.getScriptPage().onInit = function() {
      this.querySelector('#confirm').onclick = (e) => {
        const user = this.data.user;
        const code = this.querySelector('#code').value;
        // 認証コードの確認
        Auth.confirmSignUp(user.username, code)
          .then(data => {
            if (data === 'SUCCESS') {
              localStorage.setItem('username', username);
              document.getElementById('nav').pushPage('signin.html', {
                data: {
                  username: user.username
                }
              });
            }
          })
          .catch(err => console.log(err));
      }
    };
  </script>
</ons-page>

コードを確認するには、ユーザ名とコードを引数として、Auth.confirmSignUp メソッドを呼び出します。確認処理が完了すると SUCCESS という文字が返ってきますので、つづいてサインイン画面(signin.html)に遷移します。

サインイン画面

サインイン画面は以下のようになります。

signin.html

<ons-page>
  <ons-toolbar>
    <div class="left"><ons-back-button>Back</ons-back-button></div>
    <div class="center">Sign up</div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 30px;">
    <p>
      <ons-input id="username" modifier="underbar" placeholder="Username" float></ons-input>
    </p>
    <p>
      <ons-input id="password" modifier="underbar" type="password" placeholder="Password" float></ons-input>
    </p>
    <p style="margin-top: 30px;">
      <ons-button id="sign_in_button">Sign in</ons-button>
    </p>
  </div>    
  <script>
    // 画面を表示した時に実行される処理
    ons.getScriptPage().onShow = function() {
      // ユーザ名を取得
      // 以前入力した情報をlocalStorageに入れてあります
      const username = this.data.username || localStorage.getItem('username');
      if (username)
        this.querySelector('#username').value = username;
    };

    // 初期化時に実行される処理
    ons.getScriptPage().onInit = function() {
      // サインインボタンを押した時の処理
      this.querySelector('#sign_in_button').onclick = (e) => {
        const username = this.querySelector('#username').value;
        const password = this.querySelector('#password').value;
        // サインイン処理
        Auth.signIn(username, password)
          .then(user => {
            localStorage.setItem('username', user.username);
            document.getElementById('nav').pushPage('info.html');
          })
        .catch(err => console.log(err));
      };
    };
  </script>
</ons-page>

ユーザ名とパスワードを引数として、Auth.signIn メソッドを呼び出すとサインインが実行されます。

ユーザ情報表示画面

サインイン完了後、ユーザ情報を表示する画面に遷移します。

info.html

<ons-page id="info">
  <ons-toolbar>
    <div class="center">Your info</div>
    <div class="right">
      <ons-icon icon="fa-sign-out" id="logout"></ons-icon>
    </div>
  </ons-toolbar>
  <div style="text-align: center; margin-top: 30px;">
    <h1>Welcome, <span id="username_label"></span></h1>
  </div>    
  <script>
    ons.getScriptPage().onShow = function() {
      Auth.currentSession()
        .then(session => {
          // セッション情報はJSON Web Token形式
          const user = JSON.parse(window.atob(session.accessToken.jwtToken.split('.')[1]));
          this.querySelector('#username_label').innerHTML = user.username;
        }, err => console.log(err));
    };
    ons.getScriptPage().onInit = function() {
      this.querySelector('#logout').onclick = (e) => {
        // サインアウト処理
        Auth.signOut()
          .then(() => document.getElementById('nav').resetToPage('signup.html'))
      }
    };
  </script>
</ons-page>

Auth.currentSession メソッドでセッション情報を取得すると、その中にユーザ名が含まれています。セッションデータはJSON Web Token形式になっています。ドット(.)繋ぎの文字列になっているので、ドットで分割して1つ目のデータをBase64デコードすれば、ユーザ名を取り出せます。

ログアウト処理は Auth.signOut メソッドで行えます。

基本的な機能は以上となります。その他のAPI詳細はAuthenticationにて確認できます。

まとめ

AWS Mobile Hubにはさまざまな機能が含まれていて、必要に応じて各機能を組み合わせて利用できます。

日本語の情報などはまだあまりなく、最初の敷居は高いですが、AWSというグローバルなクラウド環境を使うことでスケールアップしやすいアプリを作れるのは魅力的ですね。

今回のコードはgoofmint/Monaca_AWS_Mobile_Hub_Demoにアップロードしてあります。実装時の参考にしてください(AWSの認証キーなどが必要なので、そのままでは動作しません)。

Get Started - AWS Mobile