3 Replies Latest reply on Jan 11, 2016 6:58 AM by Bert.Dilliën

    Exact online : getting an oauth2 token

    Bert.Dilliën Level 1

      Hi All,

       

      I'm struggling to connect our Coldfusion 11 (x64) to the oauth2 methods used by Exact Online accounting API s.

       

      Every call I make always returns status 400 - Bad Request. No additional info is supplied, nor found on their development site.

       

      I had contact with their helpdesk, but as they have no knowledge of Java/Coldfusion, they just send me a .Net HTTP call example:

       

      strTokenBody = "code=" & strCode
      strTokenBody = strTokenBody & "&client_id=" & strClientID
      strTokenBody = strTokenBody & "&client_secret=" & strClientsecret
      strTokenBody = strTokenBody & "&redirect_uri=" & strCallbackURL
      strTokenBody = strTokenBody & "&grant_type=authorization_code"
      byteArray = Encoding.ASCII.GetBytes(strTokenBody)
      uri = strBaseURL & "/api/oauth2/token"
      
      Dim req As WebRequest = WebRequest.Create(uri)
      req.ContentType = "application/x-www-form-urlencoded"
      req.Method = "POST"
      req.ContentLength = byteArray.Length
      
      Dim stream As Stream = req.GetRequestStream()
      stream.Write(byteArray, 0, byteArray.Length)
      stream.Close()
      

       

      The credentials I have do check out and work with the above code example, according to the Helpdesk employee. He then stressed that the HTTP body should be a byte array of an ASCII encoded string, which is a numeration of all parameters to pass along.

       

      I tried following approaches:

      - I checked out the PHP code example the Exact has put up, but I cannot determine which setting I'm missing.

      - changed call URL to a local template to dump the request (and headers) and I see that all parameters are there and display correctly.

       

      Can anybody point me in the right direction on how to proceed in this?

       

      Thanks for reading so far,

      Regards Bert.

       

      CFC I made :

      <cfcomponent>
        <cfscript>
        // define all paths used for interfacing
        VARIABLES.Exact = {
        URL = {
        main = "https://start.exactonline.be/api", // main access URL
        authenticate = "/oauth2/auth", // for optaining an access code
        token = "/oauth2/token", // to get / refresh an access token
        user = "/v1/current/Me" // get current user data
        },
        access = {
        client = {
        name = "MyName", // from API access token
        id = "pre-generated API Id ", // from API access token
        secret = "pre-generated API secret" // from API access token
        },
        redirectURI = "My Site URL", forceLogin = 0,
        code = "Optained code through manual login form"
        },
        settings = {
        grant_authorization_code = "authorization_code",
        grant_refresh_token = "refresh_token",
        response_type_code = "code"
        }
        };
        // define the access token used in all API calls. Must be optained before starting any work.
        THIS.token = {
        access = "", // access key used for the session
        type = "", // type of token
        expiresIn = 600, // seconds the token will be life, default = 10 min
        refresh = "" // handle used to refresh current
        };
        </cfscript>
      
      
        <cffunction name="CreateASCIIByteArray" access="private" returntype="Any" output="false" hint="Converts a ColdFusion string to an ASCII Java byte array.">
        <cfargument name="str" type="string" required="true" hint="The string to get the byte array for.">
        <cfscript>
        return ToBinary( toBase64( ARGUMENTS.str ) );
        </cfscript>
        </cffunction> 
      
        <cffunction name="GetToken" access="public" returntype="any" output="false" hint="">
        <cfscript>
        LOCAL.URL = VARIABLES.Exact.URL.Main & VARIABLES.Exact.URL.token;
        LOCAL.result = DoHTTPRequest(
        method = "post",
        url = LOCAL.URL,
        params = [
        NewRequestParam(type="formField", name="client_id", value=VARIABLES.Exact.access.client.id),
        NewRequestParam(type="formField", name="client_secret", value=VARIABLES.Exact.access.client.secret),
        NewRequestParam(type="formField", name="redirect_uri", value=VARIABLES.Exact.access.redirectURI),
        NewRequestParam(type="formField", name="grant_type", value=VARIABLES.Exact.settings.grant_authorization_code),
        NewRequestParam(type="formField", name="code", value=VARIABLES.Exact.access.code)
        ]
        );
      
        return LOCAL.result;
        </cfscript>
        </cffunction>
      
        <cffunction name="NewRequestParam" access="private" returntype="struct" output="false" hint="Creates a new request parameter">
        <cfargument name="type" type="string" required="false" default="URL" hint="[header,CGI,body,XML,file,URL,formField,cookie]">
        <cfargument name="name" type="string" required="true" hint="Name of the parameter">
        <cfargument name="value" type="string" required="false" default="" hint="value for the paramter">
      
        <cfreturn ARGUMENTS>
        </cffunction>
      
        <cffunction name="DoHTTPRequest" access="private" returntype="Any" output="true" description="performs an HTTP request and returns the result as a structure.">
        <cfargument name="method" type="string" required="false" default="get" hint="[get,post,delete]">
        <cfargument name="url" type="string" required="true" hint="the complete URL to call">
        <cfargument name="params" type="array" required="false" default="#ArrayNew(1)#" hint="parameter list to send along the request, created with NewRequestParam().">
        <cfscript>
        // launch the http request
        LOCAL.httpRequest = new http(
        method = uCase(ARGUMENTS.method),
        charset = "utf-8",
        url = ARGUMENTS.url,
        compression = "none"
        );
      
        if (ARGUMENTS.method == "post") {
        LOCAL.httpRequest.setMultipartType("form-data");
        }
      
        // add params to request body
        LOCAL.body = [];
        LOCAL.paramCount = arrayLen(ARGUMENTS.params);
        for (LOCAL.index = 1; LOCAL.index <= LOCAL.paramCount; LOCAL.index++) {
        LOCAL.currentParam = ARGUMENTS.params[LOCAL.index];
        if (LOCAL.currentParam.type ==  "header") {
        LOCAL.httpRequest.addParam( argumentCollection = LOCAL.currentParam );
        } else {
        arrayAppend(
        LOCAL.body,
        LOCAL.currentParam.name & "=" & LOCAL.currentParam.value
        );
        }
        } // end-for
      
        LOCAL.httpRequest.addParam(
        type = "body",
        value = CreateASCIIByteArray( str = arrayToList(LOCAL.body, "&") )
        );
      
        // fire request
        try {
        LOCAL.http = LOCAL.httpRequest.send().getPrefix();
        // return serialized filecontent when able.
        if ( isJSON(LOCAL.http.FileContent) ) {
        LOCAL.return = deserializeJSON(LOCAL.http.Filecontent);
        LOCAL.return.statusCode = listFirst(LOCAL.http.statusCode," "); // status code is returned as "200 OK" instead of the number.
        } else {
        LOCAL.return = {
        statusCode = 500,
        message = LOCAL.http.Filecontent,
        request = LOCAL.httpRequest,
        result = LOCAL.http
        };
        } // end-if
        } catch (any except) {
        LOCAL.return = {
        statusCode = 500,
        message = except.type & " : " & except.message,
        URL = ARGUMENTS.url,
        params = ARGUMENTS.params,
        fullExcept = except
        };
        } // end-catch
        return LOCAL.return;
        </cfscript>
        </cffunction>
      </cfcomponent>
      
        • 1. Re: Exact online : getting an oauth2 token
          BKBK Adobe Community Professional & MVP

          I haven't gone into the details of the code. But I have two suggestions:

           

          1) The functions are quite linked. Debug them piecewise. Take doHTTPRequest, for example. It is complex and assembles many different parts. We would wish to know what kind of data it is passing in the request. Therefore put a line similar to this one, just before the return statement:

           

          <cfdump output="c:\temp\exactTest.html" var='#local#' format="html">

           

          Then examine the parameters. Are they what you expect?

           

          2) In ColdFusion's interaction with external systems such as Exact, structs are usually more interoperable than arrays. So, convert the parameter Local.body to a struct, and see what happens.

          • 2. Re: Exact online : getting an oauth2 token
            Bert.Dilliën Level 1

            Hi BKBK,

             

            Thanks for your reply.

             

            The code works when calling a local test URL and performing a cfdump on that test template. I gather it's not a syntax issue, but rather something missing or the way it works.

             

            The body message is a byte-array value, so not an array or a string. But I also tested it without any body data, or as a plain string. Whatever I do, it always returns a '400 Bad Request'.

             

            I just tried the chrome app 'Postman'. Using that interface, I can successfully log in and receive an access token.

            • 3. Re: Exact online : getting an oauth2 token
              Bert.Dilliën Level 1

              I finally manage to get it to work, it appears Exact Online needs some valid userAgent sent along! CF sends the value 'Coldfusion' as user agent string per default.

               

              The moment I put in userAgent = "Mozilla/5.0" into my cfhttp, I got my refresh token.

              To be on the safe side, I also added the compression = "none" parameter to my cfhttp.

               

              Regards, Bert.