My *nix world

Dropbox OAuth authentication

In the last three days I worked to create a WordPress plugin that simply creates a multi-volume compressed backup for a specified directory (eg. entire website) and then uploads it on my Dropbox repository. Maybe I will write an article about the backup part someday but now I want only to explain the process of acquiring the authorization code for the Dropbox storage repository.

Dropbox provides the OAuth version 1 and 2 authorization model. The SDK I am using (v1.1.3 as of 2014.03.11) implements Dropbox OAuth authentication version 2.0 only.

You need only two things to know before everything else:

  1. To be able to connect your application to Dropbox (to download,upload,search and delete files from Dropbox storage repository) you must firstly create a Dropbox API application and then get the authorization code (token) which is necessary during the authentication session of your client application.
  1. In order to get that access token you must copy your Dropbox application App key and App secret from Dropbox App Console because you will need them later. Once you have this key-secret pair you can request the Dropbox access token by using one of the two methods described below.

Transparently request token with OAuth redirect URI

This method has the advantage that the whole process is quite transparently for the end-user. The user must, however, provide the app key and the app secret but then everything is done quite transparently.

The code below illustrates how to obtain the access token without user intervention (transparently). The code is using the Dropbox API SDK for PHP (see line 3).

Basically the code will check if you have entered the App key and App secret and if you didn't then it will provide a form for you that will simplify this process for you. When you submit the form (see /dropbox-save-key-secret) the key-secret is temporarly saved to disk and a two-step authentication process is started by sending a request to the Dropbox server. The request (see /dropbox-auth-start) includes, among other query parameters, a callback/redirect URI where the Dropbox response has to be redirected. The redirect URI is actually the same script but with a different action (namely /dropbox-auth-finish). The Dropbox response will include the access token that will be saved on the local disk for later use. You can use this token anytime you want.

The function that takes care of the 2-steps authentication is called authRun. The other functions are just helper functions the authRun function depends on.

<?php

require_once __DIR__ . '/lib/Dropbox/autoload.php';
use \Dropbox as dbx;

const DROPBOX_MSG_JSON = -1;
const DROPBOX_MSG_UNEXPECTED = -3;

$requestPath = initReqPath();

session_start();

function getTokenAccessFile()
{

	return __DIR__ . DIRECTORY_SEPARATOR . "access.token";
}

function getKeySecretFilePath()
{

	return __DIR__ . DIRECTORY_SEPARATOR . "key-secret.json";
}

function initReqPath()
{

	// If we were run as a command-line script, launch the PHP built-in web server.

	if (PHP_SAPI === 'cli')
		throw new Exception("CLI support not implemented");

	if (PHP_SAPI === 'cli-server')
	// For when we're running under PHP's built-in web server, do the routing here.
		return $_SERVER['SCRIPT_NAME'];

	else
	{
		// For when we're running under CGI or mod_php.
		if (isset($_SERVER['PATH_INFO']))
			return $_SERVER['PATH_INFO'];

		else
			return "/";

	}
}

function getAuthUrl($relative_path, $force_ssl = false)
{

	if ($force_ssl || (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'))
		$scheme = "https";

	else
		$scheme = "http";

	$host = $_SERVER['HTTP_HOST'];
	$path = getAuthPath($relative_path);
	return $scheme . "://" . $host . $path;
}

function getAuthPath($relative_path)
{

	if (PHP_SAPI === 'cli-server')
		return "/" . $relative_path;

	else if (isset($_SERVER["SCRIPT_NAME"]))
		return $_SERVER["SCRIPT_NAME"] . "/" . $relative_path;

	else
		return $relative_path;
}

function getAuthConfig()
{

	$key_secret_file = getKeySecretFilePath();

	if (file_exists($key_secret_file))
		$appInfo = dbx\AppInfo::loadFromJsonFile($key_secret_file);
	else
	{
		$msg = '<ol><li>Create a <a target="_blank" href="https://www.dropbox.com/developers/apps/create">Dropbox API app</a></li><li>Copy the <b>App key</b> and <b>App secret</b> below:</li></ol>';
		$msg .= '<form name="dropbox_key_secret" method="post" action="' . getAuthUrl("dropbox-save-key-secret")
				. '">API key : <input type="text" name="key" value="DROPBOX_APP_KEY"></input> API secret : <input type="text" name="secret" value="DROPBOX_APP_SECRET"></input><input type="submit" class="button" value="Save">';
		if (!(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off'))
			$msg .= '<br><font color="red">(by saving you will connect in SSL mode; SSL is required only once for Dropbox authentication)</font></form>';
		echo $msg;
		exit;
	}

	$clientIdentifier = 'examples-web-file-browser';
	$userLocale = null;

	return array($appInfo, $clientIdentifier, $userLocale);
}

function getWebAuth($force_ssl = false)
{

	list($appInfo, $clientIdentifier, $userLocale) = getAuthConfig();
	$redirectUri = getAuthUrl('dropbox-auth-finish', $force_ssl);
	$csrfTokenStore = new dbx\ArrayEntryStore($_SESSION, 'dropbox-auth-csrf-token');
	return new dbx\WebAuth($appInfo, $clientIdentifier, $redirectUri, $csrfTokenStore, $userLocale);
}

function authRun($requestPath)
{

	$token_access_file = getTokenAccessFile();
	if (isset($_SESSION['access-token']))
		if (!file_exists($token_access_file))
			unset($_SESSION['access-token']);
		else
			return file_get_contents($token_access_file);

	if ($requestPath === '/' || $requestPath === '/dropbox-auth-start')
	{
		$authorizeUrl = getWebAuth(true)->start();

		echo '<META HTTP-EQUIV="Refresh" Content="0; URL=' . $authorizeUrl . '">';

	}
	else if ($requestPath === '/dropbox-auth-finish')
	{
		try
		{

			list($accessToken, $userId, $urlState) = getWebAuth()->finish($_GET);
			// We didn't pass in $urlState to finish, and we're assuming the session can't be
			// tampered with, so this should be null.
			assert($urlState === null);
		}
		catch (Exception $e)
		{
			$msg = sprintf("Authentication error: %s<br>Error code %d, line %d on %s", $e->getMessage(), $e->getCode(), $e->getLine(),
					$e->getFile()
			);

			throw new Exception($msg, DROPBOX_MSG_UNEXPECTED);
		}

		// NOTE: A real web app would store the access token in a database.
		$_SESSION['access-token'] = $accessToken;

		@unlink(getKeySecretFilePath());
		file_put_contents(getTokenAccessFile(), $accessToken);
		echo "Authorization succeed.<br>Token : " . $accessToken . "<br>Token saved on " . getTokenAccessFile();
		return $accessToken;
	}
	else if ($requestPath === '/dropbox-save-key-secret')
	{

		if (isset($_POST['key']) && isset($_POST['secret']))
		{
			file_put_contents(getKeySecretFilePath(), json_encode(array("key" => $_POST['key'], "secret" => $_POST['secret'])));
			authRun('/dropbox-auth-start');
		}
	}

}

authRun($requestPath);
?>

 

Request token without OAuth redirect URI (requires user intervention)

This procedure is slightly similar with the previous one, it's also a two-step authorization process. The greatest difference is the method used to gain the authorization code. It is WebAuthNoRedirect (se line 12 below) while in the previous method we've used the WebAuth (see line 106 above).

This method has the advantage of simplicity: no redirection required since the end-user is involved in the authorization process. So if you cope with the fact that the end-user is involved three times (instead only once, see previous method) in the authorization process then I guess this method is preferable.

The code below illustrates how it works:

<?php

require_once __DIR__ . "/lib/Dropbox/autoload.php";
use \Dropbox as dbx;

session_start();

function getWebAuth($app_key, $app_secret)
{

	$appInfo = dbx\AppInfo::loadFromJson(json_decode('{"key":"' . $app_key . '","secret":"' . $app_secret . '"}', TRUE));
	return new dbx\WebAuthNoRedirect($appInfo, "MyPHPApp/1.0");

}

$url = "http" . (!empty($_SERVER['HTTPS']) ? "s" : "") . "://" . $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];

if (!(isset($_POST['app_key']) && isset($_POST['app_secret'])) && !isset($_POST['auth_code']))
{
	echo '<ol><li>Create a Dropbox API application <a target="_blank" href="https://www.dropbox.com/developers/apps/create">here</a>. Make sure you specify no <b>OAuth redirect URIs</b>!</li>';
	echo '<li>Copy the <b>App key</b> and <b>App secret</b> below:</li>';
	echo '</ol><form method="post" action="' . $url
			. '">App key :<input type="text" name="app_key" value="DROPBOX_APP_KEY"></input> App key :<input type="text" name="app_secret" value="DROPBOX_APP_SECRET"></input><input type="submit" value="Submit"></input></form>';
}
else if (!isset($_POST['auth_code']))
{
	$app_key = $_POST['app_key'];
	$app_secret = $_POST['app_secret'];

	$webAuth = getWebAuth($_SESSION['app_key'], $_SESSION['app_secret']);
	$authorizeUrl = $webAuth->start();

	echo "We use the <b>App key</b>=" . $_SESSION['app_key'] . " and the <b>App secret</b>=" . $_SESSION['app_secret']
			. " to aquire the Dropbox authorization code (token)";
	echo '<ol><li>Click <a target="_blank" href="' . $authorizeUrl
			. '">here</a> to allow this app to use your Dropbox API app and then copy the authorization code below</li>';

	echo '</ol><form method="post" action="' . $url
			. '">Authorization code : <input type="text" name="auth_code" value="AUTH_CODE_HERE" size=50></input><input type="submit" value="Submit"></input></form>';
}
else
{
	$webAuth = getWebAuth($_SESSION['app_key'], $_SESSION['app_secret']);
	list($accessToken, $dropboxUserId) = $webAuth->finish($_POST['auth_code']);

	echo "Access granted. Your authorization code(token) is : " . $accessToken . "\n";
}
?>

 

Now, if you think that this article was interesting don't forget to rate it. It shows me that you care and thus I will continue write about these things.

 
The following two tabs change content below.
Dropbox OAuth authentication

Eugen Mihailescu

Founder/programmer/one-man-show at Cubique Software
Always looking to learn more about *nix world, about the fundamental concepts of math, physics, electronics. I am also passionate about programming, database and systems administration. 16+ yrs experience in software development, designing enterprise systems, IT support and troubleshooting.
Tagged on: ,

Leave a Reply

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