JavaScript
AWS
aws-sdk
cognito

JavaScriptで、Cognitoコード認証フローを実装したまとめ

前書き

ユーザ認証のCognitoを使って、ユーザ認証を実装してみました。
今回はそのまとめです。
実装したフローは、いろんなサービスでよくある以下のフローです。

  1. サインアップ
  2. 認証コードとURLが届く
  3. URL先の認証コード入力画面でコードを入力する

上記のフローを実装して見たので、まとめます。

実装

Cognitoの設定

まずはじめに、Cognito側でメッセージがコードであることを確認します。
スクリーンショット 2017-10-18 23.37.09.png

あらかじめ、ユーザープールIDとクライアントIDは控えておきましょう。

コード認証実装

AWS-SDK、およびCognitoを利用するに当たって、以下のライブラリが必要なのでnpmかyarnでインストールします。

# npm
npm install aws-sdk
npm install amazon-cognito-identity-js
# yarn
yarn add aws-sdk
yarn add amazon-cognito-identity-js

2個目は、JavaScript上でCognitoを使いやすくしてくれるライブラリです。

Github

では早速、最初の設定部分と実コード部分を実装していきます。
新規に、util的な扱いとしてcognito.jsを作成します。
長くなるので分けて解説し、最後にまとめて記載します。

初期設定

// src/utils/cognito.js

import AWS from 'aws-sdk';
import {
  CognitoUserPool,
  CognitoUser
} from 'amazon-cognito-identity-js';

import * as aws from './../config/aws';

AWS.config.region = aws.AWS_REGION;

AWS.config.update({
  accessKeyId: aws.AWS_ACCESS_KEY,
  secretAccessKey: aws.AWS_SECRET_ACCESS_KEY
});

const poolData = {
  UserPoolId: aws.AWS_COGNITO_USER_POOL_ID,
  ClientId: aws.AWS_COGNITO_APP_CLIENT_ID
};

const userPool = new CognitoUserPool(poolData);

AWSのクレデンシャルをAWS.config.updateで設定し
poolDataという形で、プールIDとクライアントIDを設定し、CognitoUserPool関数に渡して
ユーザプールを作成します。
初期設定はこれだけです。

1. サインアップ

// Signup
export const signup = (formValue) => {
  return new Promise((resolve, reject) => {
    const attributeList = [
      {
        Name: 'email',
        Value: formValue.email
      }
    ];

    userPool.signUp(formValue.email, formValue.password, attributeList, null, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};

初期設定で作成したuserPoolのsignUpを利用します。
第1引数にemail、第2引数にパスワードを渡して第3引数にはユーザ属性(基本的にemailだけですかね)の配列を渡します。
第4引数は公式をみても、nullを渡していたのでnullを渡します。
第5引数がコールバックになります。
ちなみに、カスタム属性を利用する場合は、Name部が以下になります。

// firstNameがカスタム属性の場合
{
  Name: 'custom:firstName',
  Value: formValue.firstName
}

これで、指定したemailにCognitoから認証コードを乗せたメールが届きます。

2. 認証コードとURLが届く

デフォルトでは、認証コードのみが届きます。
コードさえあれば、認証は可能ですが
やはりメールに認証コード入力画面へのURLが欲しいと思います。
ここは、別の記事で細かく解説したいですが、Lambdaの作成が必要になります。
作成するLambdaの中身は以下です。

// LambdaHandler
module.exports.sendVerifyMessage = (event, context, callback) => {

  // 指定のUserPoolIdとリクエストのUserPoolIdが同じ
  if (event.userPoolId === process.env.USER_POOL_ID) {
    // トリガーがカスタムメッセージ
      if (event.triggerSource === 'CustomMessage_SignUp') {
        // 実処理
        // 件名
        event.response.emailSubject = "件名";
        // 本文
        event.response.emailMessage = "本文";
      }
  }

  // Return result to Cognito
  context.done(null, event);
};

Cognitoが呼び出した際に、eventの中にサインアップ情報が含まれています。
event.response.emailSubjectに件名をemailMessageに本文を入れて返すことでメールが送信されます。
HTMLメールの利用ができるので、簡単にテンプレートを作成して入れるといいです。
eventから取得できる関連するパラメータは以下の通りです。

// メールアドレス
event.userName
// 認証コード
event.request.codeParameter
// そのほかカスタム属性
event.request.userAttributes['custom:hogehoge']

入力画面で利用するため、URLの最後にemailをパラメータとして渡す必要があります。
作成したLambdaはトリガー設定のカスタムメッセージへ設定します。

スクリーンショット 2017-10-19 0.32.41.png

URL先の認証コード入力画面でコードを入力する

認証コード入力画面では、基本的に2回の処理が必要です。

  1. パラメータで飛んで来たemailが正しいメールかどうか
  2. 認証コードで認証する

それぞれを実装すると以下の形になります。

1. パラメータで飛んで来たemailが正しいメールかどうか

export const checkUser = ((email) => {
  return new Promise((resolve, reject) => {
    if (!email) {
      reject('Reject');
    }
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);

    if (!cognitoUser) {
      reject('Reject');
    }

    resolve(cognitoUser);
  });
});

パラメータで取得したemailとuserPoolを利用してcognitoUserを利用します。
存在しないユーザの場合はrejectされます。

2. 認証コードで認証する

export const activateUser = ((email, code) => {
  return new Promise((resolve, reject) => {
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);
    cognitoUser.confirmRegistration(code, true, (err) => {
      if (err) {
        console.log(err);
        reject(err);
      } else {
        // 成功
        resolve();
      }
    });
  });
});

cognitoUserのconfirmRegistrationを利用します。
ここでまたuserDataを利用するので、最初のcheckUserで作成したcognitoUserを再利用してもいいかもしれません。

まとめ

ここまでで作成したソースを記載してまとめとします。

import AWS from 'aws-sdk';
import {
  CognitoUserPool,
  CognitoUser
} from 'amazon-cognito-identity-js';

AWS.config.region = aws.AWS_REGION;

AWS.config.update({
  accessKeyId: aws.AWS_ACCESS_KEY,
  secretAccessKey: aws.AWS_SECRET_ACCESS_KEY
});

const poolData = {
  UserPoolId: aws.AWS_COGNITO_USER_POOL_ID,
  ClientId: aws.AWS_COGNITO_APP_CLIENT_ID
};

const userPool = new CognitoUserPool(poolData);


// Signup
export const signup = (formValue) => {
  return new Promise((resolve, reject) => {
    const attributeList = [
      {
        Name: 'email',
        Value: formValue.email
      }
    ];

    userPool.signUp(formValue.email, formValue.password, attributeList, null, (err) => {
      if (err) {
        reject(err);
      }
      resolve();
    });
  });
};

// User Check
export const checkUser = ((email) => {
  return new Promise((resolve, reject) => {
    if (!email) {
      reject('Reject');
    }
    const userData = {
      Username: email,
      Pool: userPool
    };

    const cognitoUser = new CognitoUser(userData);

    if (!cognitoUser) {
      reject('Reject');
    }

    resolve(cognitoUser);
  });
});

// Code Activate
export const activateUser = ((cognitoUser, code) => {
  return new Promise((resolve, reject) => {
    cognitoUser.confirmRegistration(code, true, (err) => {
      if (err) {
        console.log(err);
        reject(err);
      } else {
        // 成功
        resolve();
      }
    });
  });
});

後書き

早口な説明になりましたが、
AWSのSDKがなかなか充実しているので、思ったフローが簡単に実装できました。
サービスのユーザの認証は、
ユーザがサービスの印象を感じる部分でもあるので、しっかり実装していきたいですね。