Page tree
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 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

 Click to view...

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

 Click to view...
<?
// 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

 Click to view...
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";
}