Child pages
  • Guide to API Authentication - Two-Factor Authentication
Skip to end of metadata
Go to start of metadata

Introduction

Two-Factor authentication (2FA) requires an additional security code to log in to cPanel & WHM.  A smartphone with a supported time-based one-time password (TOTP) app provides the security code.

Important:

  • We introduced this feature in cPanel & WHM version 54.
  • API calls that use a method that includes a URL must use the correct port:
    • 2082 — Unsecure calls to cPanel's APIs.
    • 2083 — Secure calls to cPanel's APIs.
    • 2086 — Unsecure calls to WHM's APIs, or to cPanel's APIs via the WHM API.
    • 2087 — Secure calls to WHM's APIs, or to cPanel's APIs via the WHM API.
    • 2095 — Unsecure calls to cPanel's APIs via a Webmail session.
    • 2096 — Secure calls to cPanel's APIs via a Webmail session.
    Otherwise-correct calls will return Permission denied or Function not found errors if they use an incorrect port number. 
  • This document only includes cPanel & WHM authentication methods. For Manage2 authentication information, read our Guide to the Manage2 API API documentation.

2FA with session-based authentication

This script sends the OTP once to establish an authenticated session, and then performs all of the API calls within that session.

Example Perl Script

Note:

This script requires the LWP::Protocol:https module. If you attempt to run this script, you must first run the /scripts/perlinstaller LWP::Protocol::https command to install the module.

#!/usr/local/cpanel/3rdparty/bin/perl

use strict;
use warnings;
use Data::Dumper;

use Cpanel::JSON::XS                               ();
use LWP::UserAgent                                 ();
use HTTP::Cookies                                  ();
use Cpanel::Security::Authn::TwoFactorAuth::Google ();

my $user = 'cptest';
my $pass = 'blahblah';

my $base_url  = 'https://127.0.0.1:2083';
my $login_url = $base_url . '/login';

my $cookie_jar = HTTP::Cookies->new();
my $ua         = LWP::UserAgent->new(
    ssl_opts => {
        verify_hostname => 0,
    },
    cookie_jar => $cookie_jar,
);

# This takes the secret that is generated for the account when 2FA
# was configured - if you do not have the secret handy, you can
# choose to reconfigure 2FA on the account, and note the new secret.
my $google_auth = Cpanel::Security::Authn::TwoFactorAuth::Google->new(
    {
        'account_name' => $user,
        'secret'       => 'QSARBVKLZHYHLJ3M',
        'issuer'       => 'issuer name'
    }
);

# Login, and establish a session
my $resp = $ua->post( $login_url, { 'user' => $user, 'pass' => $pass, 'tfa_token' => $google_auth->generate_code() } );
# Parse the security token out, so that you can provide it properly in the subsequent requests
my $security_token = ( split /\//, $resp->header('location') )[1];

my $api_url = $base_url . '/' . $security_token . '/execute/Email/list_pops';
$resp = $ua->get($api_url);
my $json = Cpanel::JSON::XS::decode_json( $resp->decoded_content );
print Dumper $json;

Example PHP script

<?
// Path to your OTP Library
// The one used for this script can be downloaded from:
// https://github.com/lelag/otphp
require_once('otphp-master/lib/otphp.php');

$user = "username";
$pass = "password";
$secret = "secret"; // the secret generated from the 2fa setup page

$base_url  = 'https://127.0.0.1:2083';
$login_url = $base_url . '/login';
$cookie_jar = 'cookie.txt';

// get the token
$totp = new \OTPHP\TOTP($secret);
$token = $totp->now();

// these are the fields we went to send in the body of the POST request
$params = array(
    'user' => $user,
    'pass' => $pass,
    'tfa_token' => $token
);

// create a request to the login page that sends your username, password, and tfa token
$login_request = curl_init();                                     // Create Curl Object.
curl_setopt($login_request, CURLOPT_SSL_VERIFYPEER, false);       // Allow self-signed certificates...
curl_setopt($login_request, CURLOPT_SSL_VERIFYHOST, false);       // and certificates that don't match the hostname.
curl_setopt($login_request, CURLOPT_HEADER, true);                // Include headers
curl_setopt($login_request, CURLOPT_RETURNTRANSFER, true);        // Return contents of transfer on curl_exec.
curl_setopt($login_request, CURLOPT_POST, true);                  // We want a POST submission
curl_setopt($login_request, CURLOPT_POSTFIELDS, $params);         // Set the parameters we want
curl_setopt($login_request, CURLOPT_COOKIEJAR, $cookie_jar);      // Set the cookie jar.
curl_setopt($login_request, CURLOPT_URL, $login_url);             // Execute the query.
$result = curl_exec($login_request);
if (!$result) {
    error_log("curl_exec threw error \"" . curl_error($login_request) . "\" for $login_url");
}
curl_close($login_request);

$found_location = preg_match("/cpsess\d+/i", $result, $matches);
if (!$found_location) {
    error_log("Could not find the security token.");
    die;
}

// create an api request to fetch email accounts on your cPanel account
$security_token = $matches[0];
$api_url = $base_url . '/' . $security_token . '/execute/Email/list_pops';

$api_request = curl_init();
curl_setopt($api_request, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($api_request, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($api_request, CURLOPT_RETURNTRANSFER, true);
curl_setopt($api_request, CURLOPT_POST, true);
curl_setopt($api_request, CURLOPT_COOKIEFILE, $cookie_jar); // Send the cookie file with our request
curl_setopt($api_request, CURLOPT_URL, $api_url);
$api_result = curl_exec($api_request);
if (!$api_result) {
    error_log("curl_exec threw error \"" . curl_error($api_request) . "\" for $api_url");
}
curl_close($api_request);

print $api_result;

// remove the cookie jar
unlink($cookie_jar);

?>

2FA with non-session-based authentication

This script allows you to perform API calls without the need to establish a session, but requires you to send the OTP token with every request in the X-CPANEL-OTP header. This script also requires that you know the 2FA secret in order to generate the required tokens.

Example Perl script

use strict;
use warnings; 

use Data::Dumper;
use MIME::Base64 ();
use HTTP::Tiny   ();

use Cpanel::Security::Authn::TwoFactorAuth::Google ();

my $user = "asder";
my $pass = "blah1";

my $auth_str    = "Basic " . MIME::Base64::encode( $user . ":" . $pass );
chomp $auth_str;
my $ua          = HTTP::Tiny->new( 'verify_ssl' => 0 );
my $google_auth = Cpanel::Security::Authn::TwoFactorAuth::Google->new(
    {
        'account_name' => $user,
        'secret'       => 'FZ4ZVGTMFTIOF54T',
        'issuer'       => 'e'
    }
);

my $resp = $ua->request(
    "GET",
    "https://127.0.0.1:2083/execute/Ftp/list_ftp",
    {
        headers => {
            'Authorization' => $auth_str,
            'X-CPANEL-OTP'  => $google_auth->generate_code(),
        }
    }
);
 
if ( $resp->{'success'} ) {
    print "CPANEL API Request successful using user/pass combination\n";
    print "Return:\n$resp->{'content'}\n";
}
else {
    print "CPANEL API Request failed using user/pass combination. $resp->{'status'}: $resp->{'reason'}\n";
}
 
my $token = "UWU28DCA23NKY76CN17MDPKM3O7EFQY8";
 
$auth_str = "WHM $user:$token";
$resp = $ua->request(
    "GET",
    "https://127.0.0.1:2087/json-api/applist?api.version=1",
    {
        headers => {
            'Authorization' => $auth_str,
            'X-CPANEL-OTP'  => $google_auth->generate_code(),
        }
    }
);
 
if ( $resp->{'success'} ) {
    print "WHM API Request successful using user/pass combination\n";
    print "Return:\n$resp->{'content'}\n";
}
else {
    print "WHM API Request failed using user/pass combination. $resp->{'status'}: $resp->{'reason'}\n";
}
use strict;
use warnings; 

use Data::Dumper;
use MIME::Base64 ();
use HTTP::Tiny   ();

use Cpanel::Security::Authn::TwoFactorAuth::Google ();

my $user = "asder";
my $pass = "blah1";

my $auth_str    = "Basic " . MIME::Base64::encode( $user . ":" . $pass );
chomp $auth_str;
my $ua          = HTTP::Tiny->new( 'verify_ssl' => 0 );
my $google_auth = Cpanel::Security::Authn::TwoFactorAuth::Google->new(
    {
        'account_name' => $user,
        'secret'       => 'FZ4ZVGTMFTIOF54T',
        'issuer'       => 'e'
    }
);

my $resp = $ua->request(
    "GET",
    "https://127.0.0.1:2083/execute/Ftp/list_ftp",
    {
        headers => {
            'Authorization' => $auth_str,
            'X-CPANEL-OTP'  => $google_auth->generate_code(),
        }
    }
);
 
if ( $resp->{'success'} ) {
    print "CPANEL API Request successful using user/pass combination\n";
    print "Return:\n$resp->{'content'}\n";
}
else {
    print "CPANEL API Request failed using user/pass combination. $resp->{'status'}: $resp->{'reason'}\n";
}
 
my $accesshash = "7287c7dc48af430f8d72e836c3facda4
ecc6d5b802f9f046e6ed204353eddc5e
82a4cc0e57d018efb7bf2604af339489
f491c01c30dd659c5955b16c91511602
51285e0e8fcd150b3c5b772b8c244989
a53b36af96d78798b656cbcbff34e443
3bd557787825f1eedcf8504c9b96196c
6fb5b3c5946282625d316c2e64876aab
7578ce0918180c907f5f64317345ea7a
48b8499a38db5dc2ae0d97ae8c69de30
86a7cfac479add55b8005d87c33ec5f8
8045986c5a61c48ab57adbe5c25e30f9
759a7c2f149925a9be0bd1371f78fb45
e214bfd7039deb9cbd44fa358913ca2f
c44733c749bd269079ba87aedb83b75f
0ee50b6c6c320dcaa12147c4f07e3257
125dda32b1acccfc4a09dc42d022f493
294903bd3fa14c646a31a4847b7fcdf6
55d8bc29804e4719b4f377fe92bd5115
5e3f77f720ee1bd2571749c8aa8f081a
47a2f87085253d3c1ee012111bf1d221
8cda0e2131080682d7d30ed55c0f7260
7673392f8d775f50923903d38f8bc742
c98afb0802b4b04064b8782922308423
31f87c300eae452ca7588304b59e10a7
64da2083bcc3ad568c872b8e3e537896
d49da203ece1a37e1c81ead3713591d9
832c6aa23cb7931643d17f79cfd6ba8e
4609538e308b14dd8b1b2529afd1b458
56243e84055f8366b0f675ce0ab19958";
$accesshash =~ s/\n//g;
 
$auth_str = "WHM $user:$accesshash";
$resp = $ua->request(
    "GET",
    "https://127.0.0.1:2087/json-api/applist?api.version=1",
    {
        headers => {
            'Authorization' => $auth_str,
            'X-CPANEL-OTP'  => $google_auth->generate_code(),
        }
    }
);
 
if ( $resp->{'success'} ) {
    print "WHM API Request successful using user/pass combination\n";
    print "Return:\n$resp->{'content'}\n";
}
else {
    print "WHM API Request failed using user/pass combination. $resp->{'status'}: $resp->{'reason'}\n";
}