Philipp Guttmann, LL. B.

Tutorial: Valid PHP endpoint for Alexa Skill on own website

This tutorial explains how to create a valid PHP endpoint for an Alexa Skill, which satisfies all security requirements of Amazon. A simply response code in JSON is also shown.

In this tutorial I show you step by step how to create a PHP endpoint on your own website which responses a JSON format and satisfies the security requirements of Amazons Alexa Skill. At the end of the article is the full code.

Introduction

Your PHP endpoint file should start with getting the JSON Input of Amazon and the HTTP_SIGNATURECERTCHAINURL, which you get from the $_SERVER variable. Also you have to set the default timezone:

$input = file_get_contents('php://input'); $post = json_decode($input); date_default_timezone_set('UTC'); $SignatureCertChainUrl = $_SERVER['HTTP_SIGNATURECERTCHAINURL'];

First security check

In den first security check (first if clause) you have to validate the applicationId, the request timestamp and the HTTP_SIGNATURECERTCHAINURL hereafter:

Check the applicationId

'amzn1.ask.skill.Your skill id' == $post->session->application->applicationId

Add your skill applicationId in the red colored area. You can find this ID under the name of your Skill in the overview of Amazon Alexa Skills in the Amazon Developer area.

Check the request timestamp

$post->request->timestamp > date('Y-m-d\TH:i:s\Z', time()-150)

Check the HTTP_SIGNATURECERTCHAINURL

preg_match('/https:\/\/s3\.amazonaws\.com(:433)?\/echo\.api\//', $SignatureCertChainUrl)

Second security check

In the second security check (inside the first if clause) we create a file of the content from the SignatureCertChainUrl which we defined as variable in the introduction. Then we compare its content with the content of the HTTP_SIGNATURE to verify the Signature with the PublicKey of the content from the SignatureCertChainUrl. Furthermore we have to check the URL in decoded content of SignatureCertChainUrl and its validation time.

Verify the Signature with PublicKey and openssl_verify function

$SignatureCertChainUrl_File = md5($SignatureCertChainUrl); $SignatureCertChainUrl_File = $SignatureCertChainUrl_File . '.pem'; if (!file_exists($SignatureCertChainUrl_File)) { file_put_contents($SignatureCertChainUrl_File, file_get_contents($SignatureCertChainUrl)); } $SignatureCertChainUrl_Content = file_get_contents($SignatureCertChainUrl_File); $Signature_Content = $_SERVER['HTTP_SIGNATURE']; $SignatureCertChainUrl_Content_Array = openssl_x509_parse($SignatureCertChainUrl_Content); $Signature_PublicKey = openssl_pkey_get_public($SignatureCertChainUrl_Content); $Signature_PublicKey_Data = openssl_pkey_get_details($Signature_PublicKey); $Signature_Content_Decoded = base64_decode($Signature_Content); $Signature_Verify = openssl_verify($input, $Signature_Content_Decoded, $Signature_PublicKey_Data['key'], 'sha1');

Check the URL in decoded content from SignatureCertChainUrl

preg_match('/echo-api\.amazon\.com/', base64_decode($SignatureCertChainUrl_Content))

Check the validation time of the signature from SignatureCertChainUrl

$SignatureCertChainUrl_Content_Array['validTo_time_t'] > time() AND $SignatureCertChainUrl_Content_Array['validFrom_time_t'] < time()

Check the validity of signature

$Signature_Content AND $Signature_Verify == 1

If security check fails

If one requirement of the security check fails, we response HTTP Code 400:

http_response_code(400);

Response a simple sentence

If the security check is successful, we can respone in JSON format:

header ('Content-Type: application/json');

For a PlainText we defined our PHP Array below:

$PHP_Output = array('version' => '1.0', 'response' => array('outputSpeech' => array('type' => 'PlainText')));

Then we fill the text output with content:

$PHP_Output['response']['outputSpeech']['text'] = 'Hello world!';

At the end we encode our PHP array into a JSON format and echo it:

echo json_encode($PHP_Output, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

Full code of PHP endpoint for Amazons Alexa Skill

<?php $input = file_get_contents('php://input'); $post = json_decode($input); date_default_timezone_set('UTC'); $SignatureCertChainUrl = $_SERVER['HTTP_SIGNATURECERTCHAINURL']; if ('amzn1.ask.skill.Your skill id' == $post->session->application->applicationId AND $post->request->timestamp > date('Y-m-d\TH:i:s\Z', time()-150) AND preg_match('/https:\/\/s3\.amazonaws\.com(:433)?\/echo\.api\//', $SignatureCertChainUrl)) { $SignatureCertChainUrl_File = md5($SignatureCertChainUrl); $SignatureCertChainUrl_File = $SignatureCertChainUrl_File . '.pem'; if (!file_exists($SignatureCertChainUrl_File)) { file_put_contents($SignatureCertChainUrl_File, file_get_contents($SignatureCertChainUrl)); } $SignatureCertChainUrl_Content = file_get_contents($SignatureCertChainUrl_File); $Signature_Content = $_SERVER['HTTP_SIGNATURE']; $SignatureCertChainUrl_Content_Array = openssl_x509_parse($SignatureCertChainUrl_Content); $Signature_PublicKey = openssl_pkey_get_public($SignatureCertChainUrl_Content); $Signature_PublicKey_Data = openssl_pkey_get_details($Signature_PublicKey); $Signature_Content_Decoded = base64_decode($Signature_Content); $Signature_Verify = openssl_verify($input, $Signature_Content_Decoded, $Signature_PublicKey_Data['key'], 'sha1'); if (preg_match('/echo-api\.amazon\.com/', base64_decode($SignatureCertChainUrl_Content)) AND $SignatureCertChainUrl_Content_Array['validTo_time_t'] > time() AND $SignatureCertChainUrl_Content_Array['validFrom_time_t'] < time() AND $Signature_Content AND $Signature_Verify == 1) { header ('Content-Type: application/json'); $PHP_Output = array('version' => '1.0', 'response' => array('outputSpeech' => array('type' => 'PlainText'))); $PHP_Output['response']['outputSpeech']['text'] = 'Hello world!'; echo json_encode($PHP_Output, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } else { http_response_code(400); } } else { http_response_code(400); } die(); ?>

Questions?

If you have further questions, please write me an e-mail at ed.nnamttug-ppilihp@liam and I will try to help :)