STEP BY STEP GUIDE TO IMPLEMENT AWS COGNITO AUTHENTICATION TO A PHP APPLICATION WITH HOSTED UI

PLEASE SHARE

Amazon Cognito is a cloud service that provides authentication, authorization, and user management functionalities for your custom web or mobile applications. This blog post provides step by step instructions to implement AWS Cognito authentication to a simple PHP application that displays user attributes and a logout link.

The Hosted UI feature of Cognito makes it easy to provide Signup, Sign-in functionalities to our custom web applications. Our web applications can use the login and registration user interfaces provided by Cognito and we also have options to customize them.

PREREQUISITES

  1. Access to the AWS console (https://console.aws.amazon.com/)
  2. A PHP web server installed in AWS EC2 instance (such as XAMPP to deploy the demo web application)

CREATING AMAZON COGNITO USER POOL AND APP CLIENT

A Cognito user pool manages user data such as username, password, email, phone number, etc. It allows users to sign-in to your web or mobile application through Hosted UI. Here are the instructions for creating a user pool.

  1. Login into AWS console (https://console.aws.amazon.com/)
  2. Go to the Services menu and click on the Cognito link under the “Security, Identity, & Compliance” section.
  3. Click on the “Manage User Pools” button.
  4. Click on the “Create a user pool” button.
  5. Enter a pool name (Ex: DEMO_USER_POOL)
  6. Click on the “Review defaults” button.
  7. Click on the “Edit” button for Required attributes as highlighted below,

8. Make sure email & phone number attributes are selected as shown below,

9. Click on the “Next step” button.

10. Click on the “Review” from the left side of the screen and click on the “Create pool” button.

11. A message will be displayed as “Your user pool was created successfully”. 

12. Click on the “App clients” link from the left side menu.

13. Click on the “Add an app client” link.

14. Enter the “App client name” (Ex: DEMO_PHP_APPLICATION)

15. Leave the other settings as per the default options.

16. Click on the “Create app client” button.

CONFIGURING DOMAIN NAME

  1. Login into AWS Console and open the user pool created above (Ex: DEMO_USER_POOL)
  2. Click on the “App integration” => “Domain name” link from the left side menu.
  3. Enter a domain prefix (Ex: demo-hosted-ui)
  4. Make sure to check the availability of the domain by clicking on the “Check availability” button.
  5. Click on the “Save Changes” button.

CONFIGURING APP CLIENT SETTINGS

  1. Login into AWS Console and open the user pool created above (Ex: DEMO_USER_POOL)
  2. Click on the “App integration” => “App client settings” link from the left side menu.
  3. Select “Cognito User Pool” under “Enabled Identity Providers”.
  4. Enter the “Callback URL” as “http://localhost/demo_cognito_client_app/secure_page.php” (We will be installing this demo PHP application in a section down below)
  5. Select the “Implicit grant” checkbox under “Allowed OAuth Flows”.
  6. Select the “phone”, “email”, “openid”, “aws.cognito.signin.user.admin” and “profile” checkboxes under “Allowed OAuth Scopes”.
  7. Refer to the screenshot below,

8. Click on the “Save changes” button.

9. Click on the “Launch Hosted UI” button.

10. A new tab will be opened with a Hosted UI URL. Copy the URL in the address bar and keep it for later use.

Example:

https://demo-hosted-ui.auth.ap-south-1.amazoncognito.com/login?client_id=3cnqsjkcet8340rr26i115dejr&response_type=token&scope=aws.cognito.signin.user.admin+openid+phone+profile&redirect_uri=http://localhost/demo_cognito_client_app/secure_page.php

CREATING ACCESS KEYS

AWS Access Key & Secret is required to make programmatic calls to AWS services like Cognito. Here are the instructions to generate the same,

  1. Login into AWS Console.
  2. Click on your username available on the top right corner of the page.
  3. Click on the “My Security Credentials”.
  4. Click on the “Create access key” button.
  5. A message will appear as “Your new access key is now available.”
  6. Click on the “Show secret access key” link to display the secret key.

Take a note of the “Access key ID” & “Secret access key” as we need them to be configured in the demo PHP application. (Ex: Access key ID=AKIA4J62PCPJKV5RFT4M, Secret access key=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)

INSTALLING THE DEMO PHP APPLICATION

  1. Install the latest version of XAMPP on a windows box hosted in AWS. (https://www.apachefriends.org/index.html)
  2. Install the latest version of Composer for Windows https://getcomposer.org/
  3. Create a directory “demo_cognito_client_app” under “C:\xampp\htdocs”.
  4. Create a file “composer.json” under “C:\xampp\htdocs\demo_cognito_client_app” directory and copy the following content. (This file has the AWS SDK dependencies)
{
    "require": {
        "aws/aws-sdk-php": "^3.21"
    },
    "autoload": {
        "psr-4": {"AWSCognitoApp\\": "src"}
    }
}

5. Create a file “secure_page.php” under “C:\xampp\htdocs\demo_cognito_client_app” directory and copy the following content. (Please do read the comment lines in code to understand the logic)

<?php
namespace AWSCognitoApp;
require_once('vendor/autoload.php');
use Aws\CognitoIdentityProvider\CognitoIdentityProviderClient;
?>


<!DOCTYPE html>
<html>
<head>
	<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@100;400&display=swap" rel="stylesheet">
	<style>
		body{
			font-family: 'Roboto', sans-serif;
		}
	</style>
</head>
<body>
<h1>DEMO PHP COGNITO CLIENT APPLICATION</h1>


<?php
if(!isset($_GET["access_token"]))
{
	
?>
<script>
	var url_str = window.location.href;
	//On successful authentication, AWS Cognito will redirect to Call-back URL and pass the access_token as a request parameter. 
	//If you notice the URL, a “#” symbol is used to separate the query parameters instead of the “?” symbol. 
	//So we need to replace the “#” with “?” in the URL and call the page again.
	
	if(url_str.includes("#")){
		var url_str_hash_replaced = url_str.replace("#", "?");
		window.location.href = url_str_hash_replaced;
	}
	
</script>

<?php
}
else{
?>

<?php

$access_token = $_GET["access_token"];

$region = '<AWS_REGION>';
$version = 'latest';

//Authenticate with AWS Acess Key and Secret
$client = new CognitoIdentityProviderClient([
    'version' => $version,
    'region' => $region,
	'credentials' => [
                    'key'    => '<AWS_ACCESS_KEY>',
                    'secret' => '<AWS_SECRET>',
                ],
]);

try {
	//Get the User data by passing the access token received from Cognito
    $result = $client->getUser([
        'AccessToken' => $access_token,
    ]);
	
	//print_r($result);
	
	$user_email = "";
	$user_phone_number = "";
		
	//Iterate all the user attributes and get email and phone number
	$userAttributesArray = $result["UserAttributes"];
	foreach ($userAttributesArray as $key => $val) {
		if($val["Name"] == "email"){
			$user_email = $val["Value"];
		}
		if($val["Name"] == "phone_number"){
			$user_phone_number = $val["Value"];
		}
	}	
	echo '<h2>Logged-In User Attributes</h2>';
	echo '<p>User E-Mail : ' . $user_email . '</p>';
	echo '<p>User Phone Number : ' . $user_phone_number . '</p>';
	echo "<a href='secure_page.php?logout=true&access_token=$access_token'>SIGN OUT</a>";
	
	if(isset($_GET["logout"]) && $_GET["logout"] == 'true'){
		//This will invalidate the access token
		$result = $client->globalSignOut([
			'AccessToken' => $access_token,
		]);
		
		header("Location: <COGNITO_HOSTED_UI_URL>");
		
	}
	
	
} catch (\Aws\CognitoIdentityProvider\Exception\CognitoIdentityProviderException $e) {
    echo 'FAILED TO VALIDATE THE ACCESS TOKEN. ERROR = ' . $e->getMessage();
	}
catch (\Aws\Exception\CredentialsException $e) {
    echo 'FAILED TO AUTHENTICATE AWS KEY AND SECRET. ERROR = ' . $e->getMessage();
	}

}
?>

</body>
</html>


6. Replace the following in the above source code,

<AWS_REGION> = Region string AWS instance (For example: ap-south-1)

<AWS_ACCESS_KEY> = Access key generated in “CREATING ACCESS KEYS” section above (For example AKIA4J62PCPJKV5RFT4M)

<AWS_SECRET> = Secret generated in “CREATING ACCESS KEYS” section above (For example BfAiu3tpMQXQiUHb9cNqXV4Xo/iVbTV6E0BzI6XT)

<COGNITO_HOSTED_UI_URL> = The Hosted UI URL captured at the “CONFIGURING APP CLIENT SETTINGS” section above. Make sure to replace “login” with “logout” in this URL. For example, 

https://demo-hosted-ui.auth.ap-south-1.amazoncognito.com/logout?client_id=3cnqsjkcet8340rr26i115dejr&response_type=token&scope=aws.cognito.signin.user.admin+openid+phone+profile&redirect_uri=http://localhost/demo_cognito_client_app/secure_page.php

7. Save the close the file.

8. Open the command prompt and go to the directory “C:\xampp\htdocs\demo_cognito_client_app”.

9. Execute the following command to install the dependencies,

composer install

10. Start the Apache server from the XAMPP control panel.

ADDING USERS INTO COGNITO USER POOL

  1. Here’s the simple way to add users into the Cognito user pool using UI.
  2. Login into AWS Console and open the user pool created above (Ex: DEMO_USER_POOL)
  3. Click on the “General settings” => “Users and groups” from the left side menu.
  4. Click on the “Create user” button.
  5. Enter the details as per the below screenshot and click on “Create user”.

TESTING THE COGNITO HOSTED UI AUTHENTICATION WITH DEMO PHP APPLICATION

  1. Enter the Hosted UI URL noted above in the browser,

Example: 

https://demo-hosted-ui.auth.ap-south-1.amazoncognito.com/login?client_id=3cnqsjkcet8340rr26i115dejr&response_type=token&scope=aws.cognito.signin.user.admin+openid+phone+profile&redirect_uri=http://localhost/demo_cognito_client_app/secure_page.php

2. Enter the username & password created in the above section.

3. Upon the successful validation of credentials, the Cognito will redirect the user to the demo PHP application with an Access Token. The PHP application can retrieve the user information using this Access Token. Clicking on the “SIGN OUT” link will invalidate the Access Token.

CODE IN GITHUB

The demo PHP application developed in this post is available in GitHub,

https://github.com/rajeshkumarraj82/aws-cognito-client-implementation-using-php

Follow Me

13 Comments

  1. Hi, I’ve followed your steps and it worked in localhost. After that I tried to move the same settings (only changing the domain from localhost to dev domain at header part) to my dev server in AWS EC2, the sign up works but the sign in won’t work. It shows “failed to validate the access token”.

    1. Hi, Please make sure the redirected URL is having access_token by printing the same. for example,
      echo $_GET["access_token"];
      Also can you please share the complete error message shown on the screen.

  2. All of the code in the exampled worked perfectly,
    but after signing out – the redirected page giving 2 options
    “Sign In as $EARLIER_USER”
    “Sign in as a different user?”

    Clicking the first option is opening the user session with out asking for password again. This is a deal breaker. How do we fix this situation ?

    1. Hi Chandu,
      Sorry for the very late reply. It’s a very valid question. I have identified the issue is with the URL we are redirecting after calling the globalSignOut method. Simply replace the “login” with “logout” in the URL. It’s working fine now.
      Here’s the sample URL,
      https://upsso.auth.ap-south-1.amazoncognito.com/logout?client_id=3pbnr5a9dpmuuvi4g1t7f233a6&response_type=token&scope=aws.cognito.signin.user.admin+email+openid+phone+profile&redirect_uri=http://localhost/demo_cognito_client_app/secure_page.php
      I have edited the code in the blog post also.
      Let me know if this is clear. Thank You.

  3. Hello there,
    Firstly, thank you very much for this tutorial, it really helped me get started.

    Under configuring app client settings, for step 5 the instructions state:
    Select the “Implicit grant” checkbox under “Allowed OAuth Flows”

    When I read the AWS documentation: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html

    It states:

    The Authorization code grant flow initiates a code grant flow, which provides an authorization code as the response. This code can be exchanged for access tokens with the TOKEN Endpoint. Because the tokens are never exposed directly to an end user, they are less likely to become compromised. However, a custom application is required on the backend to exchange the authorization code for user pool tokens.

    For security reasons, we highly recommend that you use only the Authorization code grant flow, together with PKCE, for mobile apps.

    The Implicit grant flow allows the client to get the access token (and, optionally, ID token, based on scopes) directly from the AUTHORIZATION Endpoint. Choose this flow if your app cannot initiate the Authorization code grant flow. For more information, see the OAuth 2.0 specification.

    Shouldn’t we be selecting the Authorization grant flow, and not the implicit grant flow? I’ll have to figure out what code changes I will need to perform in order to make this work.

    1. Hi there, excellent observation. So far, I haven’t come across any Cognito API that supports the authorization code grant flow. But I think we can implement this by directly calling the endpoints mentioned here.

      https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-userpools-server-contract-reference.html

      We need to call the AUTHORIZATION, TOKEN and USERINFO API consecutively.

      Also, Instead of directly calling these endpoints, we may use any one of the OpenID client libraries available.

      Planning to write a separate blog post on this topic

      1. Thanks for responding so quickly.

        I’m currently hitting a few issues trying to work the code-grant-flow and will try manually hitting the endpoints as you pointed out.

        I too couldnt find anything within the documentation for cognito api (https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-cognito-idp-2016-04-18.html) for converting the “code” for an authorization token.

        I did try going down the route of creating the \Aws\SSOOIDC\SSOOIDCClient() and calling startDeviceAuthorization():
        https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-sso-oidc-2019-06-10.html#startdeviceauthorization

        …in order to get a device code that i could then pass to createToken() which takes the “code” I was given from the Cognito SSO. However, I would get an error message stating that I had provided an invalid clientID, so I don’t think it likes the client ID that I set up in the Cognito user pool APP integration. I did try calling registerClient() and that successfully returned a client ID and secret, but I could not see the client in the cognito service area. When I go over to the SSO service in the console, it still shows “Enable SSO” so I’m going to leave that alone. I looked through the IAM service and couldn’t find anything there. I have no idea where that “OIDC client” was registered and can be managed through the console.

        My next course of action is to try manually hitting the endpoints as you pointed out. I’ll keep checking back here in case you find the solution before me 🙂

      2. Hey there,
        You were absolutely correct about just hitting the endpoints directly. I created a demo codebase on GitHub, in case it provides any help: https://github.com/programster/amazon-cognito-sso-demo

        The main file that matters is the authentication controller…

        https://github.com/programster/amazon-cognito-sso-demo/blob/main/site/controllers/AuthController.php

        … which holds the logic for redirecting the user’s browser to the SSO login page, and sending off the requests to exchange the “code” for the authentication tokens etc. Unfortunately, I couldn’t simplify it down to a single file for demonstration purposes, but maybe you’ll have better luck.

  4. This was truly awesome — I used an IAM Role assigned to my EC2 (technically it’s a policy, AmazonCognitoPowerUser) to avoid mucking around with keys… this was the LAST piece I needed for a demo.
    That changed the CognitoIdentityProviderClient call (and made the code ‘git-able”).

    //Authenticate with AWS Acess Key and Secret
    $client = new CognitoIdentityProviderClient([
    ‘version’ => $version,
    ‘region’ => $region
    ]);

    1. Hi Warren, Thank You for sharing the info. If I understand correctly, So if the sign-in user is an IAM user having AmazonCognitoPowerUser policy then we can skip passing key & secret while creating the client object.

  5. I am using shared hosting and i am unable to include vendor/autoload.php … can you please tell me how you taken that ? and why we are using that…

    1. Hi Anuj,
      Did you manage to install the dependencies by executing the “composer install” command? It may not be possible to execute commands with some shared hosting providers. In this case, you need to install the dependencies locally and then upload the vendor directory to the shared hosting.

Leave a Reply

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

+ 77 = 82