IonicでCognito認証してみた

2021.07.01

こんにちは、森田です。

Cognitoの認証機能を試すべく、Ionic(Angular)を用いて行っていきます。

Cognitoとは?

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/Untitled.png

引用 : 20200630 AWS BlackBelt Amazon Cognito

モバイル・Webアプリ向けのユーザ認証をマネージドで提供しているAWSサービスです。

直接メールアドレスなどでのサインインはもちろんのこと、Google、Facebook、Amazon などのソーシャル ID プロバイダーや、エンタープライズ ID プロバイダーを通してもサインインすることができます。

AWS リソースのアクセスコントロールをユーザに対して設定もできますので、ログインユーザのみコンテンツを配布するというようなことも容易に行えます。

また、50,000MAUまで無料で使用できるので、小規模なサービスとかであれば無料で使えそうです。

環境

  • Ionic 5.4.16
  • Angular 12.0.5
  • npm 7.15.1
  • amazon-cognito-identity-js 5.0.3

やってみた

Cognito ユーザプールの作成

今回はCognitoのユーザプールを使用して認証を行います。

コンソールでユーザプールを作成していきます。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-06-29_3.43.41.png

メールアドレスを利用してサインアップを行いますので、標準属性にemail を指定します。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-06-29_3.44.39-scaled.jpg

続いてアプリクライアントを追加します。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-06-29_3.45.41.png

名前を設定し、アプリクライアントの作成を行います。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-07-01_11.03.12.png

あとは、デフォルトで進めていき、ユーザプールの作成を完了させます。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-07-01_11.04.19.png

Ionic アプリケーションの作成

今回は、blank テンプレートを使用して作成していきます。

ionic start cognito-demo blank

アプリケーションのディレクトリに移動し、ログイン、サインアップ、認証コードの入力ページをそれぞれ作成します。

ionic g page login
ionic g page sign-up
ionic g page confirm

cognitoの処理用にサービスそ作成します。

ionic g service cognito-service

sdkを追加します。

npm install --save amazon-cognito-identity-js

CognitoUserPoolオブジェクトを作成し、オブジェクトのAttributeでCognitoの操作を行います。 プールIDとアプリクライアントIDが必要となりますので、環境変数から読み込みます。

cognito-service.service.ts

import { Injectable } from '@angular/core';
import * as AWSCognito from "amazon-cognito-identity-js";
import { environment } from '../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class CognitoServiceService {
  _POOL_DATA = environment.cognito_data;

  //サインアップ用の関数
  signUp(email, password) {
    return new Promise((resolved, reject) => {
      const userPool = new AWSCognito.CognitoUserPool(this._POOL_DATA);

      let userAttribute = [];
      userAttribute.push(
        new AWSCognito.CognitoUserAttribute({ Name: "email", Value: email })
      );

      userPool.signUp(email, password, userAttribute, null, function(err, result) {
        if (err) {
          reject(err);
        } else {
          resolved(result);
        }
      });
    });
  }

  //メールアドレス確認用の関数
  confirmUser(verificationCode, userName) {
    return new Promise((resolved, reject) => {
      const userPool = new AWSCognito.CognitoUserPool(this._POOL_DATA);

      const cognitoUser = new AWSCognito.CognitoUser({
        Username: userName,
        Pool: userPool
      });

      cognitoUser.confirmRegistration(verificationCode, true, function(err, result) {
        if (err) {
          reject(err);
        } else {
          resolved(result);
        }
      });
    });
  }

  //認証用の関数
  authenticate(email, password) {
    return new Promise((resolved, reject) => {
      const userPool = new AWSCognito.CognitoUserPool(this._POOL_DATA);

      const authDetails = new AWSCognito.AuthenticationDetails({
        Username: email,
        Password: password
      });

      const cognitoUser = new AWSCognito.CognitoUser({
        Username: email,
        Pool: userPool
      });

      cognitoUser.authenticateUser(authDetails, {
        onSuccess: result => {
          resolved(result);
        },
        onFailure: err => {
          reject(err);
        },
        newPasswordRequired: userAttributes => {

          userAttributes.email = email;
          delete userAttributes.email_verified;

          cognitoUser.completeNewPasswordChallenge(password, userAttributes, {
            onSuccess: function(result) {},
            onFailure: function(error) {
              reject(error);
            }
          });
        }
      });
    });
  }

}

ログインページ、サインアップページにそれぞれ処理を追加します。

login.page.ts

import { Component, OnInit } from '@angular/core';
import { CognitoServiceService} from "../cognito-service.service";
import { SignUpPage } from "../sign-up/sign-up.page";
import {AlertController } from "@ionic/angular";

@Component({
  selector: 'app-login',
  templateUrl: './login.page.html',
  styleUrls: ['./login.page.scss'],
})
export class LoginPage  {

  email: string;
  password: string;
  signUpPage = SignUpPage;

  constructor(
    public CognitoSerive:CognitoServiceService,
    public alertController: AlertController
    ) {
  }

  login(){
    this.CognitoSerive.authenticate(this.email, this.password)
    .then(res =>{
      console.log(res);
      this.login_alert("ログインに成功しました");
    }, err =>{
      console.log(err);
      this.login_alert("ログインに失敗しました");
    });
  }

  async login_alert(header) {
    const alert = await this.alertController.create({
      cssClass: 'my-custom-class',
      header: header,
      buttons: ['OK']
    });

    await alert.present();
  }

}

sign-up.page.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CognitoServiceService} from "../cognito-service.service";
import { AlertController } from "@ionic/angular";

@Component({
  selector: 'app-sign-up',
  templateUrl: './sign-up.page.html',
  styleUrls: ['./sign-up.page.scss'],
})
export class SignUpPage  {

  email: string;
  password: string;

  constructor(
    public alertController: AlertController,
    public router: Router,
    public CognitoService: CognitoServiceService
  ) {}

  async register() {
    this.CognitoService.signUp(this.email, this.password).then(
      res => {
        localStorage.setItem('mail', this.email);  
        this.router.navigateByUrl('/confirm');
      },
      err => {
        console.log(err);
        this.presentAlert(err["message"]);
      }
    );
  }

  async presentAlert(alert_message) {
    const alert = await this.alertController.create({
      cssClass: 'my-custom-class',
      header: 'エラーが発生しました',
      message: alert_message,
      buttons: ['OK']
    });

    await alert.present();
  }
}

confirm.page.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { CognitoServiceService} from "../cognito-service.service";
import {AlertController } from "@ionic/angular";

@Component({
  selector: 'app-confirm',
  templateUrl: './confirm.page.html',
  styleUrls: ['./confirm.page.scss'],
})
export class ConfirmPage {
  code : string;
  constructor(
    public alertController: AlertController,
    public router: Router,
    public CognitoService: CognitoServiceService,
  ) { }

  func(){
    const mail = localStorage.getItem('mail');
    console.log(this.code);
    this.verifyUser(String(this.code), mail);
  }

  verifyUser(verificationCode ,mail) {
    this.CognitoService.confirmUser(verificationCode, mail).then(
      res => {
        console.log(res);
        this.auth_verify();
        this.router.navigateByUrl('/login');
      },
      err => {
        alert(err.message);
      }
    );
  }

  async auth_verify() {
    const alert = await this.alertController.create({
      cssClass: 'my-custom-class',
      header: '認証されました',
      buttons: ['OK']
    });

    await alert.present();
  }

}

HTMLなどについては、GitHubに載せてますのでそちらから参照ください。

アプリケーションの動作確認

まずは、サインアップページで登録を行います。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-07-01_10.00.30.png

すると、登録したメールアドレスにコードが送られてきますので、このコードを入力しメールアドレスの認証を行います。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/Untitled-1.png

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/2021-07-01_10.10.18.png

その後、ログインページより登録したメールアドレスとパスワードでログインが可能となります。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/6540eadf1cfac003bb347faa4f0a0a72.png

コンソールでも登録したユーザが確認できます。

https://cdn-ssl-devio-img.classmethod.jp/wp-content/uploads/2021/07/Untitled-2.png

最後に

今回は、Ionicを用いてCognito認証を試してみましたが、 認証時にIDトークンも取得していますので、このIDトークンを利用してLambdaなどAWSリソースへアクセスも行ってみたいです。

参考

https://github.com/aws-amplify/amplify-js/tree/master/packages/amazon-cognito-identity-js

https://dev.classmethod.jp/articles/sign-up-and-sign-in-by-cognito-with-awscli/

https://d1.awsstatic.com/webinars/jp/pdf/services/20200630_AWS_BlackBelt_Amazon Cognito.pdf

https://shamique.medium.com/aws-cognito-service-in-ionic-b234f21c27ef

https://aws.amazon.com/jp/blogs/mobile/user-sign-in-and-sign-up-for-ionic-mobile-apps-with-amazon-cognito/