cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
Announcements
Share your feedback on the Document Scanning Experience in the Dropbox App right here.

Dropbox API Support & Feedback

Find help with the Dropbox API from other developers.

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Re: Simple Node script not working with clientId /secret

Simple Node script not working with clientId /secret

rdyar
Helpful | Level 5

I'm trying to do the worlds simplest node dropbox script but getting an error, clearly I am missing something.

 

I set up an App - and gave it all the read permissions.

 

Then I have this script which should list the contents of the root - which I assume would be the App folder name I setup:

 

```

import { Dropbox } from "dropbox";
import fetch from "node-fetch";

const clientId = "***";
const clientSecret = "***";

const config = {
  fetch,
  clientId,
  clientSecret,
};
const dbx = new Dropbox(config);

dbx
  .filesListFolder({ path: "" })
  .then((response) => {
    const entries = response.result.entries;
    console.log("Contents of the root directory:");
    entries.forEach((entry) => {
      console.log(entry.name);
    });
  })
  .catch((error) => {
    console.error("Error listing contents:", error);
    if (error.response) {
      console.error("Error response:", error.response.data);
    }
  });

```

 

I have the correct id and secret entered when I run it, not ***

 

When I run it I get an error:

```

Error listing contents: DropboxResponseError: Response failed with a 409 code
at D:\Repos\locket-emailer\node_modules\dropbox\cjs\src\response.js:34:11
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
status: 409,
headers: {
'accept-encoding': 'identity,gzip',
'cache-control': 'no-cache',
connection: 'close',
'content-length': '126',
'content-security-policy': 'sandbox allow-forms allow-scripts',
'content-type': 'application/json',
date: 'Sat, 23 Dec 2023 03:34:45 GMT',
server: 'envoy',
'x-content-type-options': 'nosniff',
'x-dropbox-request-id': '266f5a0735ac4753a45ad1bb0214e9c5',
'x-dropbox-response-origin': 'far_remote'
},
error: {
error_summary: 'path/unsupported_content_type/..',
error: { '.tag': 'path', path: [Object] }
}
}

```

 

Shouldn't this work? I read in another thread that the app permissions don't allow access to anything but in another place I read I should be able to access the apps folder which makes more sense to me.

 

All I want to end up with is a script I will run on a local computer on a schedule to get some shareable links - for my own files which will be in the apps folder.

1 Accepted Solution

Accepted Solutions

rdyar
Helpful | Level 5

I was able to get this all to work as a script. So far it has worked great, running every 15 minutes for the last week. I only do the dropbox auth part if the thing I want to share has actually changed which happens several times per day during working hours.

 

In order get the refresh token chatGPT give me a script to run in powershell:

I pasted this in all at once:

 

$clientId = "yourAppIDHere"
$clientSecret = "YourAppSecret"
$authorizationCode = "ACCESSCodeFromPreviousStep"
$base64Auth = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("${clientId}:${clientSecret}"))
$headers = @{ Authorization = "Basic $base64Auth"}
$body = @{ code = $authorizationCode  grant_type = "authorization_code"}
$response = Invoke-RestMethod -Uri "https://api.dropbox.com/oauth2/token" -Method Post -Headers $headers -Body $body
# Output the access token
$response.refresh_token
 
That gave me refresh token that I can then use to get a current access token - so that is hard coded in the script.
 
I then have a db auth function which uses that refresh token and returns a new access token:
 
import fetch from "node-fetch";
const clientId = "yourAppID";
const clientSecret = "YourAppSecret";
const refreshToken =
  "RefreshTokenFromPowershell";

// Function to refresh the access token
export default async function refreshAccessToken() {
  const response = await fetch("https://api.dropboxapi.com/oauth2/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Basic ${Buffer.from(
        `${clientId}:${clientSecret}`
      ).toString("base64")}`,
    },
    body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
  });

  const data = await response.json();
  console.log("data in refresh :>> ", data);
  if (data.access_token) {
    return data.access_token;
  } else {
    throw new Error("Failed to refresh access token");
  }
}
 
Then I use that access token like you normally would.
 
import fetch from "node-fetch";
 const config = {
    fetch,
    accessToken,
    clientId,
    clientSecret,
  };

  const dbx = new Dropbox(config);
 
const data = await dbx.filesListFolder({ path: "" });

View solution in original post

10 Replies 10

Здравко
Legendary | Level 20

Hi @rdyar,

I suppose, that's your first try to use cloud storage API (including Dropbox). Did you read the documentation (or some of the examples at least)? You have variety of errors. Better take a look on the example here (very basic one and close to your idea).

Hope this gives direction.

rdyar
Helpful | Level 5

yes, first time trying to do anything with the dropbox api, and in general I am not the most skilled at this but can usually get the job done. I did look thru lots of example and have not seen anything like what I want to do (node script on schedule).

 

I can do what that code does with an access token, but that only works for a few hours thus why I tried to use the app id and secret.

 

All of the examples I have seen need to interact with someone via a browser or like that prompt to get a token - but this is just for a script that I want to run on my own files and it will run on a schedule so there won't be anyone around to fill in the prompt. It sounds like this used to be easy when the access codes were not short lived.

 

Can you tell me if the app id/secret can do this? I have seen other posts by you I think that said the app id/secret does not have access to any particular users DB but somewhere else I read that it should be able to do stuff with the apps folder.

 

 

I'll try to get the refresh token thing to work with an access token since I'm guessing I am misunderstanding the purpose of the app id/secret.

 

Thanks for the reply. 

Здравко
Legendary | Level 20

@rdyar wrote:

... since I'm guessing I am misunderstanding the purpose of the app id/secret.

...


Yes, for sure. As their name part (app - 'app' key/id and 'app' secret) suppose, it's not account related, but application related. To access particular account related thing (like listing some folder in Dropbox account, even when the account is yours) you need to authenticate the account, not application (even when the application is your property). Otherwise you don't have access to there.

 


@rdyar wrote:

...

I can do what that code does with an access token, but that only works for a few hours thus why I tried to use the app id and secret.

...


Yes, access token is short lived and works for limited time span (4 hours or so). As explained above already, app related authentication cannot replace account related! For long term access you need refresh token that doesn't expire automatic and can be reused. The common (border) thing between application authentication and account authentication is OAuth flow. To get refresh token, you need to perform offline access type OAuth flow.

Hope this sheds additional light.

rdyar
Helpful | Level 5

so basically you can't just have a simple script run on a schedule (not an app that is running) and be able to access the users files without having some sort of interactivity to authenticate?

 

Even if I do the refresh token I'd still have to store it  but my intention was to just have a simple script and handle the permissions/access by setting something in an env.  Even if I did store the token somewhere my guess is eventually it would want to authenticate again and the script would break.

 

I have not seen any examples of this type of use case, the only mentions I have seen were people using the old long lived tokens seeming to come to the same conclusion more or less.

Здравко
Legendary | Level 20

@Здравко  wrote:

... For long term access you need refresh token that doesn't expire automatic and can be reused. ...



@rdyar wrote:

so basically you can't just have a simple script run on a schedule (not an app that is running) and be able to access the users files without having some sort of interactivity to authenticate?

...


Probably you haven't read my previous post carefully. 🤷

 


@rdyar wrote:

...

Even if I do the refresh token I'd still have to store it  but my intention was to just have a simple script and handle the permissions/access by setting something in an env. ...


Hm..:thinking_face: To be honest, I have no any idea what you mean here. Yes, you need to store the same as you do it for app id, app secret, etc. (including in env or somewhere else - wherever you want).

 


@rdyar wrote:

...  Even if I did store the token somewhere my guess is eventually it would want to authenticate again and the script would break.

...


:slightly_smiling_face: Again read my previous post more careful. Your assumption is wrong!

 


@rdyar wrote:

...

I have not seen any examples of this type of use case, ...


Yes, most of the examples are simplified. You can take a look here how you can perform OAuth flow in proper way and how you can use the received refresh token. In spite significantly simplified, the example may give you idea how you can get the things working (don't repeat the example one to one in your code - store the received token instead). Even more, you can perform the OAuth flow outside the application and only use the refresh token inside (similar to long lived access token - not the same!!!). You can see here how you can get to a refresh token by hands.

Good luck.

rdyar
Helpful | Level 5

that last part sounds promising - OAuth  outside the app.

 

When I do the second step in that post where you are using curl to "materialize the code" I get errors - saying that -u is ambiguous. If I change it to one of the suggestions then it complains that -d is used too many times:

 

curl https://api.dropbox.com/oauth2/token -d code=FBB9bkp9HG8AAAAAAAKA_rGNCro3U_3ivlcfb6vXrg -d grant_type=authorization_code -u 0y3gaiz5efo2sr:63fcehmajsdex
(I edited the strings so that is not the real values).
 
with -u I get: 
Invoke-WebRequest : Parameter cannot be processed because the parameter name 'u' is ambiguous. Possible matches include: -UseBasicParsing -Uri -UseDefaultCredentials -UserAgent.
 
If I change -u I get: 
Invoke-WebRequest : Cannot bind parameter because parameter 'd' is specified more than once. To provide multiple values to parameters that can accept multiple values, use the array syntax.
 
I'm on windows and haven't use curl but it seems like it should work now a days. But maybe it is windows related.

rdyar
Helpful | Level 5

this appears to be a windows issue? instead of -u I did --user and -d as --data-urlencode

 

this didn't work but chatgpt gave me a powershell script that seemed to work, I now have another code and will work thru the next step and will post back whatever happens.

Здравко
Legendary | Level 20

@rdyar, Probably you're using some custom version of the command. This tool is OS agnostic and is used in the same way on every OS and that's why preferred when examples are provided (aside of its powerful support for almost all recent protocols versions and all their varieties). The documentation can be seen here. Download the official version to be able follow all examples here (not only mine). The '-u' parameter provides data for authentication (basic by default - the one used by Dropbox) and is shorthand of --user, Yes. Authentication can be performed once only. As alternative you can use -H or --header parameter with value explicitly representing the basics authentication header. Something like 'Authorization: Basic <credentialsVal>'. Here credentialsVal should be represented with base64 encoded value of -u parameter '<App key>:<App secret>' (together with the colon in the middle).

 

PS: Data shouldn't be pre-encoded in any way! So using --data-urlencode may make parameters invalid. Such parameter pre-encoding is only useful when you want to pass something that may break the used encoding,  so need to be pre-encoded (i.e. saned). Here is no such a thing.

rdyar
Helpful | Level 5

I was able to get this all to work as a script. So far it has worked great, running every 15 minutes for the last week. I only do the dropbox auth part if the thing I want to share has actually changed which happens several times per day during working hours.

 

In order get the refresh token chatGPT give me a script to run in powershell:

I pasted this in all at once:

 

$clientId = "yourAppIDHere"
$clientSecret = "YourAppSecret"
$authorizationCode = "ACCESSCodeFromPreviousStep"
$base64Auth = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("${clientId}:${clientSecret}"))
$headers = @{ Authorization = "Basic $base64Auth"}
$body = @{ code = $authorizationCode  grant_type = "authorization_code"}
$response = Invoke-RestMethod -Uri "https://api.dropbox.com/oauth2/token" -Method Post -Headers $headers -Body $body
# Output the access token
$response.refresh_token
 
That gave me refresh token that I can then use to get a current access token - so that is hard coded in the script.
 
I then have a db auth function which uses that refresh token and returns a new access token:
 
import fetch from "node-fetch";
const clientId = "yourAppID";
const clientSecret = "YourAppSecret";
const refreshToken =
  "RefreshTokenFromPowershell";

// Function to refresh the access token
export default async function refreshAccessToken() {
  const response = await fetch("https://api.dropboxapi.com/oauth2/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: `Basic ${Buffer.from(
        `${clientId}:${clientSecret}`
      ).toString("base64")}`,
    },
    body: `grant_type=refresh_token&refresh_token=${refreshToken}`,
  });

  const data = await response.json();
  console.log("data in refresh :>> ", data);
  if (data.access_token) {
    return data.access_token;
  } else {
    throw new Error("Failed to refresh access token");
  }
}
 
Then I use that access token like you normally would.
 
import fetch from "node-fetch";
 const config = {
    fetch,
    accessToken,
    clientId,
    clientSecret,
  };

  const dbx = new Dropbox(config);
 
const data = await dbx.filesListFolder({ path: "" });
Need more support?