Token request: How do I get past DEVELOPER_INACTIVE error

Hi there,
I’m a developer making an app for a client. I have searched and read all the posts that are encountering this error but none seems to solve my problem, so I’m posting a new question.

My use case is that I have an app that talks to a backend server which is supposed to be connected to InfusionSoft. The app needs to know if the user has an active subscription in my client’s IS account, so my understanding is that I only need the backend server to authenticate once with IS to get the authorization token, and then store that somewhere to use in future requests.

I’ve been able to request and acquire authentication, so I have a code from IS, but when I then send that code to /token endpoint, the only response I’ve been able to get is "error": "invalid_client". The header contains X-Mashery-Error-Code:ERR_403_DEVELOPER_INACTIVE.

Here is the full response header:

   { 'cache-control': [ 'no-store' ],
  'content-type': [ 'application/json;charset=UTF-8' ],
  date: [ 'Tue, 12 Sep 2017 21:07:05 GMT' ],
  pragma: [ 'no-cache' ],
  server: [ 'Mashery Proxy' ],
  'www-authenticate': [ 'Basic realm="api.infusionsoft.com"' ],
  'x-error-detail-header': [ 'Account Inactive' ],
  'x-mashery-error-code': [ 'ERR_403_DEVELOPER_INACTIVE' ],
  'x-mashery-responder': [ 'prod-j-worker-us-west-1c-59.mashery.com' ],
  'content-length': [ '26' ],
  connection: [ 'Close' ] }

I’m making my request via an express.js server, but I’ve tested it with curl and postman as well, and am getting the same response. The URL I’m POSTing to is:

https://api.infusionsoft.com/token?client_id=MY_CLIENT_ID&client_secret=MY_SECRET&code=MY_CODE&grant_type=authorization_code&redirect_uri=http://localhost:4500/admin.html

At this point, I’m not even sure what to check or how to proceed. Any help would be appreciated.

Thanks,
Khaled

Is http://localhost:4500/admin.html the same redirect_uri you sent in for the authorization?

no. I sent in http://localhost:4500/auth&response_type=code
The flow is

  1. Start at http://localhost:4500/admin.html
  2. Authorize
  3. Come back to http://localhost:4500/auth?PARAMS
  4. Hopefully end up back at admin.html

I don’t think grant_type can be authorization_code and when requesting the access token, you wouldn’t need code as a parameter which makes me wonder if you aren’t getting the access token already and trying to use it as an authorization code? Not being sure, here’s a video that goes over the details, process and some of the “gotchas”

The redirect_uri param you send to https://signin.infusionsoft.com/app/oauth/authorize needs to match the redirect_uri you send to https://api.infusionsoft.com/token.

It looks like you are sending http://localhost:4500/auth for the redirect_uri during authorization but then sending http://localhost:4500/admin.html when you request the token. They have to match exactly. It is a security measure to make sure someone has not intercepted the code somehow and then attempts to get a token with it. They have to know the redirect_uri the code was initial issued against.

@John_Borelli: This video will be very helpful for wrapping my head around the general process. Thanks. I’ll give it a watch.

@bradb: Oh, I see. Ok. I’ll try that tonight and let you know how it worked.

Unfortunately, after implementing your suggestion, I’m still seeing the same ERR_403_DEVELOPER_INACTIVE error.

What I have now is my server serving the appropriate site (admin.html) when the /admin route is accessed. IF the route is accessed and there is a code query parameter in the URL (as it will be when it gets back to /admin from the /authorize loop), the server then makes the request to /token for the authorization token.

So, both redirect_uri properties, for /authorize and for /token are now set to http://localhost:4500/admin.

I’ve tested the server, and it is able to differentiate when there is and isn’t a code, and attach it to the POST it sends to /token.

I think I missed this in your original post, but The token request needs to be a POST and the params should be in the body as form encoded params. I can’t tell if you are posting or not.

Here is an example POST from the OAuth spec (https://tools.ietf.org/html/rfc6749#section-4.1.3)

POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
     &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

I always suggest that your client_id and client_secret is sent as a Basic Auth header like in the above sample, and not in the post body or query params when requesting tokens. It is what the spec suggests to do. Including in the body works, but I try get people to follow the spec as close as possible.

I am POSTing, but I wasn’t including the params in the body, just in the POST endpoint URL.

Which code is in the “Authorization” header? Is that the client_secret or client_id?

Still getting the same error.

Here’s what I have:

const url = 'https://api.infusionsoft.com/token';
fetch(url, {
          method: 'POST',
          headers: {
                  'Host' : 'api.infusionsoft.com',
                  'Authorization' : 'Basic ' + client_id,
                  'Content-Type' : 'application/x-www-form-urlencoded'
           },
           body: 'grant_type=authorization_code&code=' + code + '&redirect_uri=' + redirect_uri

The Authorization header should be prefixed with Basic and then a base64 encoded string of client_id + : + client_secret. Example for a client_id of my_client_id and client_secret of supersecret The header value would be Basic bXlfY2xpZW50X2lkOnN1cGVyc2VjcmV0

OK! we’re making progress now.

I got a different response. This one doesn’t have an error code, but it doesn’t have a token anywhere that I can see either.

Here’s what I’ve gotten now:

Body {
  url: 'https://api.infusionsoft.com/token',
  status: 200,
  statusText: 'OK',
  headers:
   Headers {
     _headers:
      { 'cache-control': [Object],
        'content-type': [Object],
        date: [Object],
        pragma: [Object],
        server: [Object],
        'x-mashery-responder': [Object],
        'transfer-encoding': [Object],
        connection: [Object] } },
  ok: true,
  body:
   PassThrough {
     _readableState:
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [Object],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: null,
        ended: false,
        endEmitted: false,
        reading: false,
        sync: false,
        needReadable: true,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: true,
     domain: null,
     _events:
      { end: [Object],
        prefinish: [Object],
        unpipe: [Function: onunpipe],
        drain: [Function],
        error: [Function: onerror],
        close: [Object],
        finish: [Object] },
     _eventsCount: 7,
     _maxListeners: undefined,
     _writableState:
      WritableState {
        objectMode: false,
        highWaterMark: 16384,
        needDrain: false,
        ending: false,
        ended: false,
        finished: false,
        decodeStrings: true,
        defaultEncoding: 'utf8',
        length: 0,
        writing: false,
        corked: 0,
        sync: true,
        bufferProcessing: false,
        onwrite: [Function],
        writecb: null,
        writelen: 0,
        bufferedRequest: null,
        lastBufferedRequest: null,
        pendingcb: 0,
        prefinished: false,
        errorEmitted: false,
        bufferedRequestCount: 0,
        corkedRequestsFree: [Object] },
     writable: true,
     allowHalfOpen: true,
     _transformState:
      TransformState {
        afterTransform: [Function],
        needTransform: false,
        transforming: false,
        writecb: null,
        writechunk: null,
        writeencoding: null } },
  bodyUsed: false,
  size: 0,
  timeout: 0,
  _raw: [],
  _abort: false }

I got the request to work via postman! So, now I just need to figure out what is different…

1 Like

I figured it out…or rather, I got it to work.

After using Postman to generate a working response, I was able to export its request as a node.js request snippet.

Not sure why that worked but fetch didn’t, so I just installed node’s request package, and copied the postman snippet into my server. Now it works fine.

@bradb thanks for all your help. All your input made the call work, then I just had to find the right way to send it in. Hope this helps anyone else trying to authenticate with node.

Here’s the code snippet:

var options = {
    method: 'POST',
    url: 'https://api.infusionsoft.com/token',
    headers: {
        'cache-control': 'no-cache',
        host: 'api.infusionsoft.com',
        authorization: 'Basic' + base64ofClient_idClient_secret,
        'content-type': 'application/x-www-form-urlencoded' },
    form: {
        grant_type: 'authorization_code',
        code: code,
        redirect_uri: redirect_uri,
        client_id: client_id
    }
};

request(options, function (error, response, body) {
    if (error) throw new Error(error);
    console.log(body);
});
3 Likes

Glad you got it working!