Immediate Access Token Expiration?

MasonA93
Regular Participant

So I've hit a wall in the last 24 hours on the Constant Contact implementation. I'm using the Oauth flow and I have obtained an access token and refresh token and am now trying to Post a contact. The issue is in I'm hitting the Unauthorized/Unauthorized message when doing so. 

 

Going off what I have read in other posts, I then created an automatic refresh mechanic so that if a request comes back with an httpCode of 401, we do a quick refresh of the access tokens and try the request over with that new access token. No matter what I do, it's always coming back as Unauthorized/Unauthorized. 

 

Since every single thing I've seen in this forum states that "Unauthorized/Unauthorized" messages mean that the access token has expired, how is that occurring if I'm refreshing the access token (and seeing that new access token update and within the request) and then making the same request again (w/ newly updated access token) only to get the same error message?

 

I've tried every solution I can come up within in the last 6 hours and at this point I need a little help. Here is the class I'm working off of, follow the logic of the createContact() function.

 

 

<?php  

class Constant_Contact extends Email
{	
	private $clientKey;
	private $clientSecret;
	private $connection;
	private $refreshAttempts = 0;

	const BASIC = 1;
	const BEARER = 2;
	
	function __construct()
	{
		$this->clientKey = $GLOBALS['fw']['config']['constantContact']['clientKey'];
		$this->clientSecret = $GLOBALS['fw']['config']['constantContact']['clientSecret'];
	}

	function storeInit(Store $store)
	{
		$connection = StoreManager::singleton()->WHERE(array('store_id' => $store->id, 'type_id' => Connection_To_Store::ConstantContact))->getObject('Connection_To_Store');
		if (isset($connection->id) && $connection->id) {
			$this->connection = $connection;
		}
		return false;
	}

	// creates a URL that the browser will redirect to obtain a code for authorization
	public function getAuthorizationURL()
	{

		$baseUrl = 'https://authz.constantcontact.com/oauth2/default/v1/authorize';
		$scope = '+contact_data+offline_access';
		$responseType = 'code';
		$state = 'asdf08a8dfljsfs1';
		$redirectUri = 'http://devadmin.xxxx.com/admin/connections/ConstantContactGetCode';

		return $baseUrl . '?client_id=' . $this->clientKey . '&scope=' . $scope . '&response_type=' . $responseType . '&state=' . $state . '&redirect_uri=' . urlencode($redirectUri);
	}


	// Using the ?code from authorization, we will grab the tokens for using Constant Contact
	public function getTokens($code)
	{
		if (!$code)
			return;

		$redirectUri = 'http://devadmin.xxxx.com/admin/connections/ConstantContactGetCode';
		$baseUrl = 'https://authz.constantcontact.com/oauth2/default/v1/token?code=' . $code . '&redirect_uri=' . $redirectUri . '&grant_type=authorization_code';

		return $this->request(Constant_Contact::BASIC, FWHTTPRequest::POST, $baseUrl, $data);

	}

	public function createContact(User $user)
	{
		if (!$user->id)
			return;

		if (!$this->connection->id)
			return;

        $data['api_key'] = $this->clientKey;
        $data['first_name'] = $user->first_name;
        $data['last_name'] = $user->last_name;
        $data['email_address']['address'] = $user->email;
        $data['email_address']['permission_to_send'] = 'implicit';

		return $this->request(Constant_Contact::BEARER, FWHTTPRequest::POST, 'https://api.cc.email/v3/contacts', $data);
	}

	private function refreshToken()
	{

		$baseUrl = 'https://authz.constantcontact.com/oauth2/default/v1/token?refresh_token=' . $this->connection->key_3 . '&grant_type=refresh_token';
		$data = $this->request(Constant_Contact::BASIC, FWHTTPRequest::POST, $baseUrl);

		$connection = new Connection_To_Store($this->connection->id);
		$connection->access_token = $data->access_token;
		$connection->account_index = $data->expires_in;
		$connection->key_3 = $data->refresh_token;
		$connection->save();

		$this->connection = $connection;
	}

	private function request($type, $mode = FWHTTPRequest::POST, $endpoint, $data = array(), $refresh = false)
	{
		if (!$type)
			return;

		if (!$this->clientKey)
			return;

		if ($refresh && $this->refreshAttempts == 1)		
        	$this->refreshToken();

        $fwoptions = new FWHTTPRequestOptions();
        $fwoptions->action = $mode;
        $fwoptions->contentType = 'json';
        $fwoptions->returnResult = true;
        $fwoptions->timeout = 300;
        $fwoptions->connectionTimeout = 30;
        $fwoptions->maxRedirects = -1;

        if ($type == Constant_Contact::BEARER) {

			$authorization = 'Authorization: Bearer ' . $this->connection->access_token;
	        $fwoptions->headers = array($authorization, 'Content-Type: application/json');

        } else if ($type == Constant_Contact::BASIC) {       	

			$credentials = base64_encode($this->clientKey . ':' . $this->clientSecret);
			$authorization = 'Authorization: Basic ' . $credentials;
	        $fwoptions->headers = array($authorization, 'Content-Type: application/x-www-form-urlencoded');
        }

        if (is_array($data) && count($data)) {
        	foreach($data as $key => $value) {
        		$fwoptions->postFields[$key] = $value;
        	}
        }

        try {
                LogManager::info(__METHOD__ . ' making call to ' . $endpoint);

                if ($request = new FWHTTPRequest($endpoint, $fwoptions, true)) {

                		// if we got here, the access token needs to be renewed
                		if ($request->httpCode == '401') {

                			$this->refreshAttempts++;
                			$this->request($type, $mode, $endpoint, $data, true);

                		} else if ($request->httpCode != '404') {

                                if ($request->responseBody == 'false') {
                                        exit;
                                }
                                $content = $request->responseBody;
                                $content = json_decode($content);

                                if (isset($content->error_key) && $content->error_key)
        							Log_Error::create($_SESSION['store']->id, Log_Error::Connections, $content->error_message, $content->error_key, $endpoint);

                                if (isset($content->error) && $content->error)
        							Log_Error::create($_SESSION['store']->id, Log_Error::Connections, $content->error_description, $content->error, $endpoint);

                                return $content;
                        }   
                        print_r($request);                                                   
                }

        } catch(exception $e) {
                LogManager::error(__METHOD__ . ' endpoint: ' . $endpoint . ', exception: ' . $e);
                throw $e;
        }
	}

}

?>

 

3 REPLIES 3
MasonA93
Regular Participant

It's been 4 days without a response. Can I please get some guidance as this is stalled out development.

MasonA93
Regular Participant

It's now been 8 days without a reply. I'm basically going to have to suggest my customer switches to a different email provider.

 

If you're going to go Oauth2 for authorization you should have the support to handle the issues that comes with it. So far I've integrated Mailchimp, Emma and SendinBlue and none of them use Oauth2 and they all have had better support (some of which I did not actually have to contact due to the resources being so thorough).

Courtney_E
Moderator

Hello MasonA93,

 

Thank you for reaching out to Constant Contact API Developer Support. My team is here to assist outside software developers with questions about building into Constant Contact's API.

 

We appreciate your patience and apologize for our delay in response, as our team answers inquiries in the order that they are received.

 

While you mentioned that you were receiving a 401 response, after taking a look in our logs, it looks like the responses for your key's endpoint calls are returning a 400 response on our end with the error "create_source is missing, create_source does not have a valid value".

 

In your example, you provided the following code: 

        $data['api_key'] = $this->clientKey;

        $data['first_name'] = $user->first_name;

        $data['last_name'] = $user->last_name;

        $data['email_address']['address'] = $user->email;

        $data['email_address']['permission_to_send'] = 'implicit';

 

Which logged the request body in our system as:

{
"api_key":"-----------,
"first_name":"-----------",
"last_name":"-----------",
"email_address":{"address":"-----------","permission_to_send":"implicit"
}

 

When making a POST call to /v3/contacts, you'll want to format the request body like so:

{

"email_address": {

"address": "-----------",

"permission_to_send":"implicit"

},

"first_name":"-----------",

"last_name":"-----------",

"create_source": "Contact",

 "list_memberships": ["-----------"]

}

 

So, the three things you'll want to update are:

  • In V3, you won't include the api_key/client_id as a parameter of the call, as this is already specified within the bearer token of your authorization header and can cause issues with the system's ability to read some endpoint calls.
  • Make sure to include the "create_source", which can be "Contact" or "Account".
  • While not including a list ID will not cause an error with this endpoint (as it does with some others), adding a contact without specifying which list to add it to will essentially leave it floating in the account. Without a list, a contact can't be sent to.

 

Please have a look and let us know if you have any other questions!


Regards,

Courtney E.
Tier II API Support Engineer

Did I answer your question?
If so, please mark my post as an "Accepted Solution" by clicking the Accept as Solution button in the bottom right hand corner of this post.
Resources
Developer Portal

View API documentation, code samples, get your API key.

Visit Page

Announcements

API Updates

Join our list to be notified of new features and updates to our V3 API.

Sign Up