Help

Re: External access token authorization working differently in Airtable than in Google Apps Script?

Solved
Jump to Solution
1752 3
cancel
Showing results for 
Search instead for 
Did you mean: 
DPG
6 - Interface Innovator
6 - Interface Innovator

I can't seem to get a valid access token in Airtable based on JavaScript code that works in a Google Apps Script. Here is the working code:

 

  var clientId = '...';
  var clientSecret = '...';
  var url = 'https://accounts.spotify.com/api/token';
  var headers = {
    'Authorization': 'Basic ' + Utilities.base64Encode(clientId + ':' + clientSecret)
  };
  var payload = {
    'grant_type': 'client_credentials'
  };
  var options = {
    'method': 'post',
    'headers': headers,
    'payload': payload
  };
  var response = UrlFetchApp.fetch(url, options);
  var accessToken = JSON.parse(response.getContentText()).access_token;

 

This code relies on objects and methods that don't exist in Airtable's automation coding environment, namely Utilities.base64encode() and UrlFetchApp.fetch(), so I have tried rewriting the script to work in Airtable. To bypass an encoding method, I simply encoded the clientId and clientSecret manually and substituted the encoded string for Utilities.base64encode(clientId + ':' + clientSecret). In order to fetch the access token, I have tried a few lines of declaring variables. This is what the Airtable script looks like:

 

var url = 'https://accounts.spotify.com/api/token';
var headers = {
  'Authorization': 'Basic ' + '...'}; // '...' is the encoded string
var payload = {
  'grant_type': 'client_credentials'};
var options = {
  'method': 'post',
  'headers': headers,
  'payload': payload};

var responseToken = await fetch(url, options);
var dataToken = await responseToken;
var accessToken = await responseToken.access_token;

 

Have I approached this incorrectly? I appreciate any help.

1 Solution

Accepted Solutions
ag314
6 - Interface Innovator
6 - Interface Innovator

Hello @DPG - i just created a Spotify endpoint and tested it with the following code, which successfully returns a token. And it uses the btoa function that I referenced earlier, included below. So this should give you everything you need.

 

async function getSpotifyToken() {
  const client_id = 'xxx'
  const client_secret = 'xxx'

const url ='https://accounts.spotify.com/api/token' const authOptions = { method: 'POST', headers: { 'Authorization': 'Basic ' + btoa(client_id + ':' + client_secret), 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'grant_type=client_credentials' }; try { const response = await fetch(url, authOptions); if (response.ok) { const data = await response.json() const token = data.access_token console.log(token) return token } else { throw new Error('Request failed with status: ' + response.status) } } catch (error) { console.error('Error:', error) } } function btoa(token) { var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}} return Base64.encode(token) }

 

See Solution in Thread

10 Replies 10

What error messages are you getting? Are you using scripting extension or an automation script? If you are using Scripting extension, have you tried remoteFetchAsync()? Airtable fetch also has a few other limitations listed here

DPG
6 - Interface Innovator
6 - Interface Innovator

Thanks for your quick response. Not so much an error, but when including console.log(accessToken) after the entire script, it returns "undefined". It evidently isn't properly fetching the access token. I am implementing this code in an automation script.

ag314
6 - Interface Innovator
6 - Interface Innovator

What are you seeing in the responseToken?

Also, is seems like you are missing a json method to extract data from the fetch response. I would have expected something more along these lines:

const response = await fetch(url, options)
const data = await response.json()
const accessToken = data.access_token

Lastly, fwiw, I wrote about how to use Base64-encoding in an Airtable automation script in this thread: https://community.airtable.com/t5/development-apis/base64-conversion/td-p/103578. Scroll down close to the end of the thread you'll see an implementation for btoa that is quite helpful for accessing various external services from within automation scripts. 

DPG
6 - Interface Innovator
6 - Interface Innovator

I appreciate your help.

In the script I have, console.log(responseToken) gives me the following:

{type: "basic", url: "https://accounts.spotify.com/api/token", status: 400, statusText: "Bad Request", ok: false…}
type: "basic"
url: "https://accounts.spotify.com/api/token"
status: 400
statusText: "Bad Request"
ok: false
headers: Object
redirected: false

Clearly I’ve improperly formatted the request, but I can’t pinpoint my mistake.

When I try adding .json() to responseToken, I get the following error: "SyntaxError: Unexpected token < in JSON at position 0 at main on line 18". Line 18 is var dataToken = await responseToken.json(). Is the request in the wrong format?

I suspect there's yet another revision I need to make for this script to work. "var accessToken = await responseToken.access_token;" returns an error when I add the .json() method to responseToken: "Property 'access_token' does not exist on type 'Response'.(2339)". Will I need to convert responseToken into a format that isn't a response?

In the meantime I’ll try implementing your getBambookToken function and see if it resolves anything.

Thanks!

ag314
6 - Interface Innovator
6 - Interface Innovator

Send me the full fetch with options please. Taking a quick peek at Spotify's API docs and it looks like there is a form component in there. Which means the content-type must be set to x-www-form-urlencoded. In the request, that would look something like this:

 
headers: { 
  'Authorization': 'Basic ' + btoa(client_id + ':' client_secret) 
  'Content-Type': 'application/x-www-form-urlencoded' 
}

 

ag314
6 - Interface Innovator
6 - Interface Innovator

Hello @DPG - i just created a Spotify endpoint and tested it with the following code, which successfully returns a token. And it uses the btoa function that I referenced earlier, included below. So this should give you everything you need.

 

async function getSpotifyToken() {
  const client_id = 'xxx'
  const client_secret = 'xxx'

const url ='https://accounts.spotify.com/api/token' const authOptions = { method: 'POST', headers: { 'Authorization': 'Basic ' + btoa(client_id + ':' + client_secret), 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'grant_type=client_credentials' }; try { const response = await fetch(url, authOptions); if (response.ok) { const data = await response.json() const token = data.access_token console.log(token) return token } else { throw new Error('Request failed with status: ' + response.status) } } catch (error) { console.error('Error:', error) } } function btoa(token) { var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}} return Base64.encode(token) }

 

DPG
6 - Interface Innovator
6 - Interface Innovator

You're an invaluable resource for me. Thank you. Here's my revised script, which I still can't seem to get working:

let token
async function getSpotifyToken() {
  const client_id = '...';
  const client_secret = '...';

  const url ='https://accounts.spotify.com/api/token'

  const authOptions = {
    method: 'POST',
    headers: {
      'Authorization': 'Basic ' + btoa(client_id + ':' + client_secret),
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: 'grant_type=client_credentials'
  };

  try {
    const response = await fetch(url, authOptions);

    if (response.ok) {
      const data = await response.json()
      const token = data.access_token
      console.log(token)
      return token
    } else {
      throw new Error('Request failed with status: ' + response.status)
    }
  } catch (error) {
    console.error('Error:', error)
  }
}

function btoa(token) {
  var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
  return Base64.encode(token)
}

getSpotifyToken()

console.log(token)

 I added "let token" at the top because console.log(token) returned the following error without it: "ReferenceError: token is not defined at main"

What am I doing wrong? Any ideas?

ag314
6 - Interface Innovator
6 - Interface Innovator

Very close. Get rid of the "let token" at the top. and where you have "getSpotifyToken()" replace it with this:

 

const token = await getSpotifyToken()

Since the function is an async function, you need to include the "await" to wait for it to finish. then, after the line above, you can console.log(token) and you'll see the value.

saadat
4 - Data Explorer
4 - Data Explorer

Hi , i am getting same error this code was running perfectly in Google App script but giving error at "Utilities" 

 async function uploadMediaAndAssociateWithPost(free) {
  var wordpressUrl = "";
  var wordpressUsername = "";
  var wordpressPassword = "";
  var imageUrl = free; // Replace with the actual image URL
  var associatedPostId = 123; // Replace with the actual post ID

  var authHeader = "Basic " + Utilities.base64Encode(wordpressUsername + ":" + wordpressPassword);

  // Download the image file
  var imageBlob = UrlFetchApp.fetch(imageUrl).getBlob();

  // Upload the media file
  var uploadUrl = wordpressUrl + "/media";
  var uploadOptions = {
    method: "post",
    headers: {
      "Authorization": authHeader
    },
    payload: {
      file: imageBlob
    },
    muteHttpExceptions: true
  };

  
  var uploadResponse = await fetch(uploadUrl, uploadOptions);
  var data = await uploadResponse.text();
  var uploadData = JSON.parse(data);
  console.log(uploadData);
  var newImageId = uploadData.id;
  console.log(newImageId);
  return newImageId;
}

 

Kindly suggest me Solutions  

DPG
6 - Interface Innovator
6 - Interface Innovator

@saadat, Airtable's JavaScript environment doesn't support the Utilities.base64Encode() method, which is why the solution relies on a custom function, btoa(), to encode.