プログラミングの役立つ記事をお届けします

How to connect Cognito and PHP API

「Amazon Cognito seems convenient for user authentication, but how can I reuse my existing PHP system?」

Hello, I’m taka.

I think today’s article is probably a very useful article for those who are helpful.

I’m thinking about using Cognito for user authentication at the front end in the app I’m developing.
When you get the user’s data through the API on the server side , you have to verify that user.

In this article, we answer how a Cognito authenticated user can provide data via a PHP API.

System overview of this article

Since there was a good image in the official document, I will quote it as it is.

As you can see, the access token obtained by the authentication process from the Cognito user pool is verified in the back end to obtain data that is only allowed for this user.

Setting up a Cognito user pool

First of all, Cognito must be a user authentication mechanism, so let’s set up immediately.
Access Cognito’s screen and start with “User Pool Management”.

Access Cognito’s screen and start with “User Pool Management”.

Enter any pool name and select “Set according to the steps”.
sc 2019-10-04 16.42.32

User name tends to be forgotten at user login, so select “E-mail address and phone number”.
You don’t see a lot of username logins right now.

If you make this selection, be sure to turn on email for which standard attributes to select.
I was addicted to not logging in without turning this on. .

In addition, since it is stated that it cannot be changed after the pool is created, the specification needs to be considered when creating it.

For password strength, there will be no warnings, special characters and capital letters in the warning text. (I feel that Amazon.co.jp itself can only be in lower case.)
ON as well as user self-signup.

Use the default value until “App Client”.
“Add app client”.

Settings for application client. This is quite important.

“App client name” is an arbitrary character string
“Renew token expiration date (days)” is 30 days by default,
Since this is a web-based smartphone app that I’m trying to make this time, it’s 365 days.

For Facebook and Twitter apps, you can only log in for the first time on a smartphone.

Depending on requirements, the longest 3650 days may be acceptable.
Since “Generate client secret” is a browser-based application this time, turn it off.

Finally, “Create Pool” completes the creation of the user pool.

Implementation around Cognito user authentication with javascript

Creating a front app

I recommend Framework7 overwhelmingly for smartphone apps made with javascript.

https://kahoo.blog/app-developing-with-framework7/

This time, I will make it based on Framework7.

There is also a Framework7 Japanese site that I run.

https://framework7.jp/

Confirm login session

Let’s get into the main topic.
First, check whether you are currently logged in when you open the app UI, and if you are not logged in, display the login screen.

To check if you are logged in, use the following code:
Bring the pool ID and client ID of the user pool you created earlier.

// npm i amazon-cognito-identity-js してね♪
const AmazonCognitoIdentity = require('amazon-cognito-identity-js');

// Cognitoで作ったユーザープールIDとクライアントIDをセット
var data = {
  UserPoolId: 'ap-northeast-1_xxxxxxxxxx',
  ClientId: 'xxxxxxxxxxxxxxxxxxxxxxxx'
};
var CognitoUserPool = new AmazonCognitoIdentity.CognitoUserPool(data);

// 現在ログインしているかどうかはこの関数でOK
var cognitoUser = CognitoUserPool.getCurrentUser();

if (cognitoUser != null) {
  cognitoUser.getSession(function (err, session) {
    // ここに入ればログイン中処理に進める
  });
}else{
  // ログインしていないので、ログイン画面を開く
}

If you are not logged in, the login screen is generally displayed.
With Framework7, it’s easy to just arrange the parts prepared around here.

Prompt for a member registration page for those who have not yet registered.
Mock has opened the following member registration screen with the “Free member registration here” link.

Implementation of sign-up (member registration) processing

This is the mock screen for membership registration.

Enter the email address and password on the screen as shown above and click “Free Membership Registration” to implement the process of temporarily registering with Cognito.


// emailとpasswordを取得 const email = this.$el.find('input[name="email"]').val(); const password = this.$el.find('input[name="password"]').val(); var poolData = { UserPoolId: 'ap-northeast-1_xxxxxxxxx', ClientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx' }; var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData); var attributeList = []; // 仮登録処理(Cognitoから変えるとコールバック関数がキックされる) userPool.signUp(email, password, attributeList, null, function(err, result){ if (err) { console.error(err); return; } var cognitoUser = result.user; console.log('user name is ' + cognitoUser.getUsername()); // この時点で登録メールアドレスに確認コードのメールが飛んでいるはずなので // ここではプロンプトで入力を促す var code = window.prompt("確認コードを入力してください"); // 確認コードをCognitoの提供する関数に渡すと検証が行われる cognitoUser.confirmRegistration(code, true, function(err, result) { if (err) { alert(err); return; } // ここまで到達したら成功したのでログイン画面にする。 // ここではリロードでトップに戻す。 location.reload(); }); });

If you actually execute this, you will receive an email.

The current Cognito management screen is as follows.

If you pass this to confirmRegistration with the above code, it will be like this registration.
A user is added on the Cognito management screen, and the email confirmation is true.

Implementing login process

You will be able to log in once you have completed membership registration.

Implement the process on the login screen.

After entering your email address and password and pressing the login button,
Implement a process to log in to Cognito.

// emailとpasswordを取得
const username = this.$el.find('input[name="email"]').val();
const password = this.$el.find('input[name="password"]').val();

const authenticationData = {
  Username: username,
  Password: password
};
const authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);

var poolData = {
  UserPoolId: 'ap-northeast-1_xxxxxxxxx',
  ClientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
};

var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);

var userData = {
  Username : username,
  Pool : userPool
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);

// Cognitoの提供するログイン関数
cognitoUser.authenticateUser(authenticationDetails, {
  onSuccess: function (result) {
    /* ここに到達するとログインの完了 */

    // 以下のようにアクセストークンを取得できるが、
    // IDトークン・アクセストークン・更新トークンがlocalStorageに保存してある
    var accessToken = result.getAccessToken().getJwtToken();
    var idToken = result.idToken.jwtToken;
    console.log(accessToken);
    console.log(idToken);

    /* マイページの表示処理などはここ */
  },

  onFailure: function (err) {
    /* ログインの失敗 */

    // 失敗に応じて適切な案内を出す
    console.error(err);
  },

});

If you enter onSuccess, the login process is completed and the ID token, access token, and refresh token are stored in the local storage.

Access token verification with PHP API

The sign-in process of Cognito up to the above is quite rolling on the net,
We had a hard time because there wasn’t much server-side verification using an access token after login,
The result is the following code:

And before that, the code on the javascript side first.

Make an ajax request with the access token in the HTTP request header.
Basic authentication is done with the key Authorization, so here it is calledX-Authorization so that it doesn’t suffer.

var data = {
  UserPoolId: 'ap-northeast-1_xxxxxxxxx',
  ClientId: 'xxxxxxxxxxxxxxxxxxxxxxxx'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(data);

var cognitoUser = userPool.getCurrentUser();

// getSessionでは内部で更新トークンを使ってアクセストークンをリフレッシュしてる
cognitoUser.getSession(function (err, session) {
  window.session = session;
  if (err) {
    alert(err);
    return;
  }

  app.request.promise({
    url: "https://[yourapp.com]/api/yourapi.php",
    dataType: "json",
    headers: {
      "X-Authorization": session.getAccessToken().getJwtToken()
    },
  }).then((res) => {
    // API成功時の処理
    console.log(res);
  }).catch(() => {
    // API失敗時の処理
  });
});

The access token that can be obtained from Cognito is valid for 1 hour.
This is not very suitable for devices that have person authentication in the first place, such as smartphone apps.

Use the Renewal token with an expiration date of 365 days set in Coginito.

I asked AWS support about this, but if you get a token from getSession (), you can use the refresh token internally to get the latest access token.

Now, the code on the PHP side.

The point is
The access token (JsonWebToken) sent from the client is Base64 decoded and matched with the public key issued by Cognito.

Detailed explanation is uploaded in the official document.
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html

As a library this time, the following is used for Verify.
https://packagist.org/packages/firebase/php-jwt
The following is used for public key conversion.
https://packagist.org/packages/codercat/jwk-to-pem

Installed using composer.

use \Firebase\JWT\JWT;
use CoderCat\JWKToPEM\JWKConverter;

// HTTPリクエストヘッダーからアクセストークン(JsonWebToken)を取得します
$headers      = getallheaders();
$jwt = "";
foreach ($headers as $name => $value) {
  if ($name == "X-Authorization") {
    $jwt = $value;
    break;
  }
}

try {
  if ($jwt) {

    // Cognitoから公開鍵を取得します
    $url = "https://cognito-idp.{リージョン名}.amazonaws.com/{ユーザープールID}/.well-known/jwks.json";
    $jwks = file_get_contents($url);

    // JSON Web Tokenをデコードしてヘッダーのkidを見つけます
    $tks = explode('.', $jwt);
    if (count($tks) != 3) {
        throw new Exception('JWTのフォーマットがおかしいです');
    }
    list($headb64, $bodyb64, $cryptob64) = $tks;

    $jwt_header = json_decode(JWT::urlsafeB64Decode($headb64), true);
    if (empty($jwt_header["kid"])) {
        throw new Exception("JSON Web Tokenにkidがありません");
    }

    // JSON Web Keysをデコードしてjwtのkidと合致するjwkから公開鍵を取得します
    $publicKey = "";
    $jwks_data = json_decode($jwks, true);
    foreach ($jwks_data["keys"] as $jwk) {
        if ($jwk["kid"] == $jwt_header["kid"]) {
          $jwkConverter = new JWKConverter();
          // 公開鍵取得
          $publicKey = $jwkConverter->toPEM($jwk);
          break;
        }
    }
    if ( !$publicKey ) {
        throw new Exception("公開鍵が取得出来ません");
    }

    // JSON Web Tokenを公開鍵で検証もかねてデコードします。
    $decoded = JWT::decode($jwt, $publicKey, array('RS256'));

    if ( !$decoded ){
      throw new Exception("検証エラー");
    }

    /* ここまで到達したら検証OKなので本来のAPIのデータを渡してもOK */
    /* デコードデータにユーザーIDなどあるのでそれでデータを引っ張ることが出来る */
    $api_data = [];
    $data["data"] = $api_data;

    header('content-type: application/json; charset=utf-8');
    $json = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    echo $json;
  }
}catch(Exception $e){
  // 失敗した時のコード
  echo "errro";
}

If the process passes to the point where it can be safely decorated, it will be verified OK.
You can provide this user’s data with peace of mind.

Summary

It became quite a long article.
In application development using Cognito, if the back end uses PHP, I think that this will be the base.

Probably will be used in any environment.

If you find it useful, like this tweet! Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *