Skip to content

Authenticating with AWS Cognito from PHP

AWS Cognito is a service that handles user creation and authentication for your application, allowing you to focus on the key aspects of your application.

Use case

Many workloads are still running on PHP, and converting those workloads to cloud-native platforms may not happen overnight (or at all). Every so often we hear about new data breaches, where usernames and passwords are breached, and it would appear that developers are not very good at protecting credentials.

This is where Cognito comes in. By using this service from AWS, you'd be able to offload the authentication of your application onto Cognito, and let it handle the authentication of your application.

How it works

For this example, we are using the OATH2 feature of Cognito. OATH2 is a well-known protocol, so I won't delve too deep into how OATH2 works. For Cognito, let's walk through how this works.

Setting up Cognito

Start by creating a user pool, which is the collection of users. When creating the user pool, you need to decide how user accounts will be created. You can configure the parameters to suit your specific requirements.

For this example, we are going to focus on improved security. Where possible, we will ensure a high level of security integration between Cognito and your application.

A few key parameters that need to be configured :

  • Provide a user pool name
  • Select the "Use the Cognito Hosted UI" option
  • Provide a domain name
  • Provide an app client name
  • Select "Generate a client secret"
  • Provide a callback URL.

You can leave the remaining options as is. For this example, there is no need to configure MFA, or any special password policies. You're welcome to tweak these options as you need it in your application, it is not required for the example.

Once it is created, you will need the following information. Collect this from the AWS Cognito console.

  • The full domain name
  • The Client ID
  • The client secret
  • The redirect URI

A word about the "domain name"

Cognito allows you to use a domain name from AWS, or you could use your own domain name. For a production system, I would strongly recommend that you use your own domain name. Create a sub-domain in Route 53, and point that to Cognito. Refer to the AWS documentation on how to configure the custom domain.

Oath2 flow

Create the state

The state field is a feature to prevent CSRF (Cross-site Request Forgery). We define this by simply generating a random string and storing it in the $_SESSION variable.

if(!isset($_SESSION['state'])) {
    $_SESSION['state']  = sha1(time().mt_rand());
}

Login

The browser starts by opening the Hosted UI page for the login endpoint.

print "<a href=\"$cognito_domain/login?" . http_build_query([
        'client_id'     => $client_id,
        'redirect_uri'  => $redirect_uri,
        'response_type' => "code",
        'state'         => $_SESSION['state']
]). "\">Login</a>";

Validate the state

Validate that the state we got is the same as what we provided Cognito in the beginning.

if(isset($_GET['code'])) {
    if($_SESSION['state'] != $_GET['state']) {
        print "access denied";
    }

    ...
}

Issue the token

Once Cognito has performed the authentication, it returns a code. This code is returned to the redirect_uri. On receiving the code, we query /oath2/token.

Note that we also use the $client_id and $client_secret variables encoded as base64 as part of the authorization header.

$ch = curl_init();

// Get the token
$code = $_GET['code'];

curl_setopt_array($ch, [
    CURLOPT_URL => "$cognito_domain/oauth2/token?" . http_build_query([
        'grant_type'    => "authorization_code",
        'client_id'     => $client_id,
        'code'          => $code,
        'redirect_uri'  => $redirect_uri
    ]),
    CURLOPT_POSTFIELDS => $params,
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Content-type: application/x-www-form-urlencoded",
        "Authorization: Basic " . base64_encode("$client_id:$client_secret")
    ]
]);
$response = json_decode(curl_exec($ch),true);

Lookup the user information

Utilizing the oath2/userInfo endpoint, and using the token from the previous step, we can query the user information.

// Get the user info
curl_setopt_array($ch, [
    CURLOPT_URL => "$cognito_domain/oauth2/userInfo",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        "Accept: application/json",
        "Authorization: Bearer " . $response['access_token']
    ],
    CURLOPT_POST => false
]);
$user = json_decode(curl_exec($ch),true);

User email address

Provided everything works as expected, we can now query the $user variable, and retrieve the email address that was authenticated.

if(isset($user['email'])) {
    print "You have been logged on!";
    $_SESSION['emailaddress'] = $user['email'];
}

Caveats

  • The Hosted UI only supports the signup, login, and password reset functions. Anything else (like updating the passwords, or updating attributes) is up to you - you have to build those interfaces yourself.
  • You could store additional attributes within Cognito, however, I would recommend you store it in your own database. While you could use Cognito, when your application requirse additional parameters (like date of birth, address, etc), you may be better off using your database instead.
  • The script in this example is only focussing on authentication. Session state is considered within the application.

Sample script

You can use this script to demonstrate how the authentication process works within PHP. Use the URL of the script as the redirect_uri on Cognito.

<?php
session_start();

$cognito_domain = "https://<domain>.auth.ap-southeast-2.amazoncognito.com";
$client_id = "<client id>";
$client_secret = "<client secret>"; # DON'T HARD CODE THIS IN YOUR APP!!
$redirect_uri = "https://<redirect uri>";

if(!isset($_SESSION['state'])) {
    $_SESSION['state']  = sha1(time().mt_rand());
}

if(isset($_SESSION['emailaddress'])) {
    if(isset($_GET['logout'])) {
        session_destroy();
        session_start();
        print "You have been logged out<br>\n";
    } else {
        print "You are logged on as " . $_SESSION['emailaddress'] . "<br>";;

        print "<a href=\"?logout=true\">Logout</a>";
    }
} else {
    if(isset($_GET['code'])) {
        if($_SESSION['state'] != $_GET['state']) {
            print "access denied";
        } else {
            $ch = curl_init();

            // Get the token
            $code = $_GET['code'];

            curl_setopt_array($ch, [
                CURLOPT_URL => "$cognito_domain/oauth2/token?" . http_build_query([
                    'grant_type'    => "authorization_code",
                    'client_id'     => $client_id,
                    'code'          => $code,
                    'redirect_uri'  => $redirect_uri
                ]),
                CURLOPT_POST => true,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HTTPHEADER => [
                    "Content-type: application/x-www-form-urlencoded",
                    "Authorization: Basic " . base64_encode("$client_id:$client_secret")
                ]
            ]);
            $response = json_decode(curl_exec($ch),true);

            // Get the user info
            curl_setopt_array($ch, [
                CURLOPT_URL => "$cognito_domain/oauth2/userInfo",
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HTTPHEADER => [
                    "Accept: application/json",
                    "Authorization: Bearer " . $response['access_token']
                ],
                CURLOPT_POST => false
            ]);
            $user = json_decode(curl_exec($ch),true);

            if(isset($user['email'])) {
                print "You have been logged on!";
                $_SESSION['emailaddress'] = $user['email'];
            }
        }

    } else {
        print "Not logged on - ";

        print "<a href=\"$cognito_domain/login?" . http_build_query([
            'client_id'     => $client_id,
            'redirect_uri'  => $redirect_uri,
            'response_type' => "code",
            'state'         => $_SESSION['state']
        ]). "\">Login</a>";
    }
}

function json_curl($url,$headers,$params) {
    $defaults  = [
        CURLOPT_URL => $url,
        CURLOPT_POSTFIELDS => $params,
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [ 
            "Content-type" => "application/x-www-form-urlencoded"
        ]
    ];

    $ch = curl_init();
    curl_setopt_array($ch, $defaults);
    curl_setopt_array($ch, $headers);

    return json_decode(curl_exec($ch),true);
}
?>