User authentication in web applications has become a major challenge in recent years. One-time passwords (OTPs), for example, are used to authenticate a user's identification; the most common means of delivering OTPs is by SMS to the user's registered cellphone number.
TypingDNA, on the other hand, substitutes the usual OTP through SMS with a unique four-word code. In this post, you'll learn how to utilize Auth0 and TypingDNA's Verify2FA to build two-factor authentication.
Prerequisite
To follow this tutorial, you need the following components:
PHP 7.4 (ideally 8.1) installed on your development machine with curl.
Composer globally installed.
A TypingDNA account. If you are new to TypingDNA, click here to create a free account.
An Auth0 account. If you are new to TypingDNA, click here to create a free account.
Ngrok to expose your local server.
Create the application’s directory.
Create and navigate into the project's root directory, labeled authxdna
, by running the two commands below in a terminal.
mkdir authxdna
cd authxdna
Install the required dependencies.
Now that the directory for the application has been created, it is time to install the external dependencies:
Library | Description |
---|---|
Guzzle | Guzzle is a PHP HTTP client that makes it easy to send HTTP requests and trivial to integrate with web services. |
Psr7 | PSR-7 message implementation that also provides common utility methods. |
HTTP Factory Guzzle | An HTTP Factory using Guzzle PSR7 |
Auth0-php | This package handles API requests from your application to the Auth0 API endpoint. |
PHPdotenv | This package combines variables defined in a.env file with PHP's $_ENV and $_SERVER Superglobals. |
Simple PHP router | This is a simple and small single class PHP router that can handle the whole url routing for your project. |
Run the command below in your terminal in the application’s root directory to install these dependencies.
composer require --with-all-dependencies \
guzzlehttp/guzzle \
guzzlehttp/psr7 \
http-interop/http-factory-guzzle \
auth0/auth0-php \
steampixel/simple-php-router \
vlucas/phpdotenv
This will add a vendor folder to your project and download all of the dependencies required to utilize the Auth0 PHP SDK. This will also generate a vendor/autoload.php
file, which will be used to load all of the classes required for the application to run. It is essential that you include this autoload file in your project for the SDK to function properly.
Starting the Server
Since TypingDNA cannot interact with the localhost, we must expose our local server using ngrok. To do this, run this command below.
ngrok http 3000
This creates a bridge that exposes our local server to the web.
Our application will now be accessed via the link https://6b5b-105-112-60-199.ngrok.io (which would be different in your case, but make sure you use the HTTPS version).
!!!tip
If you try accessing the site, you might get a 502 bad gateway. This is because we haven’t set up our local server, which we will do much later.
!!!tip
Add the required environment variables.
Once the dependencies have been installed, create a new file called .env
in the root directory of your application and paste the code below into it.
# The URL of your Auth0 tenant domain
AUTH0_DOMAIN=https://YOUR_DOMAIN
# Your Auth0 application's Client ID
AUTH0_CLIENT_ID=YOUR_AUTH0_CLIENT_ID
# Your Auth0 application's Client Secret
AUTH0_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET
# A long, secret value used to encrypt the session cookie.
# This can be generated using `openssl rand -hex 32` from your shell.
AUTH0_COOKIE_SECRET=YOUR_COOKIE_SECRET
# A URL your application is accessible from. Update this as appropriate.
AUTH0_BASE_URL=YOUR_NGROK_SERVER_ADDRESS
# Your TypingDNA application's Client Id
VERIFY_2FA_CLIENT_ID=YOUR_VERIFY_CLIENT_ID
# Your TypingDNA application's Client Secret
VERIFY_2FA_CLIENT_SECRET=YOUR_VERIFY_CLIENT_SECRET
# Your TypingDNA application's Id
VERIFY_2FA_APPLICATION_ID=YOUR_VERIFY_APPLICATION_ID
Following that, you would add the right value to their variable, which I will guide you through.
Configuring Auth0 and TypingDNA.
Open your Auth0 dashboard and you should find something similar to this.
Now click on the applications dropdown and select the applications link as aligned in the picture above. Click the create application button and a pop-up window appears.
Input the application name Authxdna
Select the “Regular Web Applications” and click on the "Create" button.
You would then be greeted with a “Quick Start” tab. Navigate to the “Settings” tab.
You need the following information:
Domain
Client ID
Client Secret
Click on the copy icon on the text fields and replace :
YOUR_DOMAIN
with the value of the domain.
YOUR_AUTH0_CLIENT_ID
with the value of Client ID.
YOUR_AUTH0_CLIENT_SECRET
with the value of Client Secret.
Also replace YOUR_NGROK_SERVER_ADDRESS
with the ngrok address.
To generate the cookie secret, run this code in the terminal.
openssl rand -hex 32
Setup Auth0 Callback URLs
A callback URL is a URL in your application to which Auth0 sends the user after authentication. In the Application Settings, enter the ngrok link (with the respective URI’s as seen in the image below) as the callback URL for your app to the Allowed Callback URLs. If this field is not set, users will be unable to log in and will receive an error.
For login (https://YOUR_SUBDOMAIN.ngrok.io/login).
For callback (https://YOUR_SUBDOMAIN.ngrok.io/callback).
For logout (https://YOUR_SUBDOMAIN.ngrok.io/logout).
Scroll to the bottom and click on the save button.
TypingDNA
Log in to the TypingDNA dashboard. The Verify 2FA dashboard will provide you with the client Id and client secret. Copy the values and replace them accordingly in the .env
file.
To generate the application ID, go to the Settings page by hitting the "Add your first integration" option. When you click the "Add integration" link, a pop-up window appears.
Fill in the name and domain fields with 'Authxdna' in the name field and the ngrok link produced previously in the domain field. After that, the application ID is generated. Copy the value and replace it with YOUR_VERIFY_APPLICATION_ID
in the .env
file.
Introducing PHP
Create a new file index.php
in the root directory in your editor or IDE, then put the content below into it.
<?php
session_start();
// Import our router library:
use Steampixel\Route;
// Import the Composer Autoloader to make the SDK classes accessible:
require 'vendor/autoload.php';
// Load our environment variables from the .env file:
(Dotenv\Dotenv::createImmutable(__DIR__))->load();
// Now instantiate the Auth0 class with our configuration:
$auth0 = new \Auth0\SDK\Auth0([
'domain' => $_ENV['AUTH0_DOMAIN'],
'clientId' => $_ENV['AUTH0_CLIENT_ID'],
'clientSecret' => $_ENV['AUTH0_CLIENT_SECRET'],
'cookieSecret' => $_ENV['AUTH0_COOKIE_SECRET']
]);
// Define route constants:
define('ROUTE_URL_INDEX', rtrim($_ENV['AUTH0_BASE_URL'], '/'));
define('ROUTE_URL_LOGIN', ROUTE_URL_INDEX . '/login');
define('ROUTE_URL_CALLBACK', ROUTE_URL_INDEX . '/callback');
define('ROUTE_URL_VERIFY', ROUTE_URL_INDEX . '/verify');
define('ROUTE_URL_LOGOUT', ROUTE_URL_INDEX . '/logout');
Route::add('/', function () use ($auth0) {
$session = $auth0->getCredentials();
if ($session === null) {
// The user isn't logged in.
echo '
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ath0 - Verify</title>
</head>
<body>
<p>Please <a href="/login">log in</a>.</p>
</body>
</html>';
return;
}
if (!isset($_COOKIE['2fa'])) {
// The user isn't verified.
header("Location: " . ROUTE_URL_VERIFY);
}
// The user is logged in.
echo '<pre>';
print_r($session->user);
echo '</pre>';
echo '<p>You can now <a href="/logout">log out</a>.</p>';
return;
});
Route::add('/login', function () use ($auth0) {
// It's a good idea to reset user sessions each time they go to login to avoid "invalid state" errors, should they hit network issues or other problems that interrupt a previous login process:
$auth0->clear();
// Finally, set up the local application session, and redirect the user to the Auth0 Universal Login Page to authenticate.
header("Location: " . $auth0->login(ROUTE_URL_CALLBACK));
exit;
});
Route::add('/callback', function () use ($auth0) {
// Have the SDK complete the authentication flow:
$auth0->exchange(ROUTE_URL_CALLBACK);
// Finally, redirect our end user back to the / index route, to display their user profile:
header("Location: " . ROUTE_URL_INDEX);
exit;
});
Route::add('/verify', function () use ($auth0) {
$session = $auth0->getCredentials();
if ($session === null) {
header("Location: " . ROUTE_URL_INDEX);
return;
}
if(isset($_COOKIE['2fa'])){
header("Location: " . ROUTE_URL_INDEX);
return;
}
else {
include('verify.php');
}
});
Route::add('/logout', function () use ($auth0) {
unset($_COOKIE['2fa']);
setcookie('2fa', null, time()-3600);
// Clear the user's local session with our app, then redirect them to the Auth0 logout endpoint to clear their Auth0 session.
header("Location: " . $auth0->logout(ROUTE_URL_INDEX));
exit;
});
// This tells our router that we've finished configuring our routes, and we're ready to begin routing incoming HTTP requests:
Route::run('/');
The code starts off by importing the required class and Composer's autoloader. Following that, it uses PHP Dotenv to load the application settings in .env
into PHP's $_ENV Superglobals.
After that, it initializes a new Auth0 class, and defines all the necessary routes for the application as constants.
With that done, routes were added, and a session was created. The Auth0 PHP SDK's 'getCredentials()' method is useful for checking if our user has authenticated and returned their profile. The next lines of code publish the user's profile if they're signed in, or tell them that they need to log in or verify their identity if they haven’t.
Next, the '/login' route is then included, which will create a user session using the Auth0 PHP SDK's login()
method and deliver a customized URL to Auth0's Universal Login Page for this user to login, as well as the /callback
route, which will return the user from authenticating with Auth0's Universal Login Page. When Auth0 returns our users to the application, it adds a few important elements in the HTTP request query. Working with them is handled by the Auth0 PHP SDK's exchange()
method, so ending our authentication flow is simple. If the user hasn't been authenticated yet, the '/verify' route is generated to redirect them to the authentication page.
The /logout
route, which redirects the user to Auth0's logout()
function, is the final route added. The Auth0 PHP SDK's logout()
function clears the session cookies in the application and redirects the user to Auth0's /logout
endpoint (which logs out the Auth0 session layer and any identity provider session layers), and then returns the user to our /index
route.
Last but not least, we need to inform our routing middleware to use Route::run('/');
to actually route requests. This informs our router that we've completed our route configuration and are ready to start routing incoming HTTP requests.
Enabling TypingDNA
In the root folder, add the file TypingDNAVerifyClient.php
that can be found in the repository: https://github.com/TypingDNA/TypingDNA-Verify-Client/blob/main/php/TypingDNAVerifyClient.php.
Now that the TypingDNAVerifyClient.php
file is in the root folder, create a file verify.php
in the root folder and paste the following code.
<?php
// set Verify credentials
$client_id = $_ENV['VERIFY_2FA_CLIENT_ID'];
$secret = $_ENV['VERIFY_2FA_CLIENT_SECRET'];
$application_id = $_ENV['VERIFY_2FA_APPLICATION_ID'];
// include TypingDna Verify library
include('TypingDNAVerifyClient.php');
// create and store a TypingDNAVerifyClient instance using your Verify credentials
$typingDNAVerifyClient = new TypingDNAVerifyClient($client_id, $application_id, $secret);
// if there is opt param in the link it means the Verify flow is completed and we need to validate it
if (isset($_GET['otp'])) {
// validate opt code
$response = $typingDNAVerifyClient->validateOTP([
'email' => $session->user['email'],
], $_GET['otp']);
if ($response['success']==1) {
setcookie("2fa", true);
header("Location: " .ROUTE_URL_INDEX);
}
}
// create the data object required to generate Verify button
$typingDNADataAttributes = $typingDNAVerifyClient->getDataAttributes([
'email' => $session->user['email'],
'language' => "en",
'mode' => "standard"
]);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ath0 - Verify</title>
<script src="https://cdn.typingdna.com/verify/typingdna-verify.js"></script>
<script>
// crate the callback function that will be invoked by the Verify window
function callbackFn(payload) {
window.location.href = `?otp=${payload["otp"]}`;
}
</script>
</head>
<body>
<p>Second factor of authentication</p>
<button class="typingDNA-verify" data-typingdna-client-id="<?= $typingDNADataAttributes['clientId']; ?>" data-typingdna-application-id="<?= $typingDNADataAttributes['applicationId']; ?>" data-typingdna-payload="<?= $typingDNADataAttributes['payload']; ?>" data-typingdna-callback-fn="callbackFn">
Verify with Typingdna
</button>
<p><a href="/logout">Log Out</a></p>
</body>
</html>
We include the TypingDNAVerifyClient
library first, then initialize our variable in the same way we did in index.php. Using the credentials from the Verify 2FA Dashboard, we additionally construct a TypingDNAVerifyClient
object and place it in the $typingDNAVerifyClient
variable.
Once we have a TypingDNAVerifyClient
instance, we will generate the data attributes required to start TypingDNA Verify 2FA. We'll use the email from the Auth0 session callback to register the user on TypingDNA in order to prevent mix up.
Next, we will add the JavaScript library that will allow us to draw the TypingDNA Verify pop-up at the start of the file.
Next, we will create the callbackFn
that will redirect the user to the same page where we will process the result obtained from the Verify pop-up.
To verify the created OTP code, the user will be sent to the same page, where the code will be confirmed and, if true, a cookie will be set to indicate that the user has been verified.
Finally, we will add the TypingDNA Verify button that will start the pop-up.
Let's run the application with this command.
php -S 127.0.0.1:3000 index.php
Our first screen
Followed by this
Next, this
After clicking the "Verify with TypingDNA" button.
After we complete the instructions from the TypingDNA Verify 2FA button, the final screen will contain the user details.
For more support, contact us at support@typingdna.com.
Top comments (0)