For over a year I've used the Google Sheets APIv4 on a web page to present data from a publicly shared Google Spreadsheet.
Even though nothing has changed on our end, the sheets API is now returning a 403, Permission Denied error. The sheet I am trying to access is one that I own and the sharing settings do appear to be correct.
This issue was reported to me on 7/17/18.
I am using Javascript to access the public sheet and since no authentication should be needed, only an API key is specified.
If I try to open the public sheet using it's public URL within a "Guest" browser window, the sheet is visible and NO authentication is requested.
Why would the API suddenly be refusing to read a publicly shared sheet?
Here is the code I am using to retrieve the public sheet.
gapi.client.init({
'apiKey': 'AIzaSyC5fTcQun-1r83DVWlPmGAZpoUGPNcuM34',
'discoveryDocs': ['https://sheets.googleapis.com/$discovery/rest?version=v4']
}).then(function() {
return gapi.client.sheets.spreadsheets.values.get({
'spreadsheetId': '1e3KBd0277mz79IgxNch1-lwWdlJGiJQSe2eMNcXWYes',
'range': 'Sheet1!A:E'
});
}, errorMessage).then(function(response) { // Code to process the response });
At the top of the html file that links to the script that reads the public spreadsheet, gapi is loaded by inserting a script tag whose src tag = "https://apis.google.com/js/api.js"
Then I use gapi.load('client', getData) to catch the client library load event, where getdata is the name of the function containing the code snippet quoted above.
Here is the direct URL to the public sheet:
NOTE: The API key in the code above is domain restricted to our domain only.
If I change the code slightly and make it so that authorization is required, by adding an OAuth clientId and Scope to the gapi.client.init() call, the sheets API does successfully retrieve the spreadsheet, but that defeats the object of making the sheet public.
Here is a snapshot of the sharing settings on the public sheet
Related
I need to access some data from a private Google Sheets document that only my google account has access to – it is not shared with anyone else.
From here: https://developers.google.com/sheets/guides/authorizing
When your application requests private data, the request must be
authorized by an authenticated user who has access to that data.
Again, that user would be me – the application developer. The users of my application will not have these sheets shared with them.
From what I’m reading in the Google API docs, I’m not sure this is possible – but it seems to me like it should be. I am using nodeJs.
Can anyone help me out?
You'll want to use Google's OAuth flow (here) in order to get access to private sheets. If only you need to be able to access the sheet then you can keep the OAuth as only tied to yourself and make requests to the endpoint in your app.
Example Implementation
View in Fusebit
// Create a sheet object for the client + auth
const sheets = google.sheets({
version: 'v4',
auth
})
// Pull the data from our spreadsheet
const data = (await sheets.spreadsheets.values.get({
// See: https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
spreadsheetId: '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms', // ID of the spreadsheet
range: 'Class Data!A2:E', // This gets all of the data in the sheet that we care about
})).data;
// Your rows/columns go into "data.values"
const rows = data.values;
});
Service account with domain-wide delegation:
The only way to avoid user interaction when accessing user private data is to use a service account with domain-wide delegation to impersonate your regular account.
That requires being a domain admin, though, so I'm not sure this option is available to you.
Sharing the spreadsheet with a service account:
Another option would be to share the spreadsheet with the service account, so you can access it using that account. Service accounts are not linked to a specific user, and so follow a different OAuth flow which doesn't require user interaction. See reference below.
Otherwise:
If you are not a domain admin and you don't want to share the spreadsheet with a service account, you are left with the web server workflow, which requires user interaction: you just cannot bypass this interaction.
I'd suggest you to read the references linked below.
Reference:
Using OAuth 2.0 for Web Server Applications
Using OAuth 2.0 for Server to Server Applications
I'm writing a Google Script that will call an external API and pull the resulting data into a Google Sheet. The API requires a Client ID and Secret value for authorization, and I need to keep those values secure. I would also like to trigger the script to run periodically (basically, I'm trying to automate the updating of this sheet as much as possible).
I'm no data security expert, but keeping the Client ID and Secret hardcoded seems like a terrible idea. Most of the search results I've found recommend using the Properties Service to store those values, but in order to set those properties I'd have to hardcode them in the same script, correct? If so, that doesn't solve the security problem.
Other recommendations involve prompting the user to enter the credentials to authorize each run of the script. This solves the security requirement, but I want this process to be as automatic as possible. If I'm opening the script and providing my credentials each time it runs, then I may as well skip the triggered executions.
Are there any other solutions? For context, I am the only person who needs to access this script and no one else should be able to access the Client ID and Secret.
Since you are the only one who has access to the script (having View access to the spreadsheet doesn't allow users to look at the bound script), hardcoding the Client ID and Secret shouldn't be a problem. Just don't give them Edit access to the spreadsheet.
If you don't want to hard-code the data directly anyway, you have some alternatives:
Using Properties Service:
Use Properties Service, as you mentioned. You could, for example, set the Client ID by running this once (in the legacy IDE, you can set these properties manually too):
function setClientId() {
var props = PropertiesService.getScriptProperties();
props.setProperty('Client ID', '{YOUR_CLIENT_ID}');
}
Once the property was set, you can remove '{YOUR_CLIENT_ID}', or even the whole function, if you don't want to keep it hard-coded. The script could then retrieve the stored property the following way:
function getClientId() {
var props = PropertiesService.getScriptProperties();
return props.getProperty('Client ID');
}
Using library:
Another option could be to store this information in a different script, to be used as a library (see Gain access to a library):
var CLIENT_ID = "YOUR_CLIENT_ID";
var SECRET = "YOUR_SECRET";
And then import this library in your main script (see Add a library to your script project). In the sample below LIBRARY is the library Identifier name:
Code.gs (from main script):
function getData() {
const clientId = LIBRARY.CLIENT_ID;
const secret = LIBRARY.SECRET;
// ...
}
Note:
Please note that, even if you don't hard-code your data directly, anyone who can execute your script can potentially retrieve this data. For example, they could log what's returned by getClientId().
If the script has access to some data, users who can execute the script can access this data too.
I am new to API's and i am trying to create a google sheet using python. I am able to create google sheet but unable to view/edit(Access issue). I am using Service Account in API's. Please help me in modifying the code to access the created sheets.
Here is the sample code i am using to create one.
service = discovery.build('sheets', 'v4', credentials=credentials)
spreadsheet_body = {
}
request = service.spreadsheets().create(body=spreadsheet_body)
response = request.execute()
Most likely you are creating the sheet as the service account - by default on its Drive, this is why you have issues accessing it.
If instead you want to create a sheet on YOUR drive, you need to use impersonation:
After enabling domain-wide delegation, you need to impersonate the service account as you.
In python you can do it by specifying credentials = credentials.create_delegated(user_email) when instantiating the service object.
I am shared folder in which does contain nearly 7 other folders. i have got client id and secret key and API key. i want to show all that folder in my application. if i m logged in to google drive.it works as soon i logged out it doesn't work, how can i by pass login screen
function handleClientLoad() {
//console.log(gapi.client);
gapi.client.setApiKey(apiKey);
//checkAuth();
//makeApiCall();
//getFoldersList();
window.setTimeout(checkAuth, 1);
}
function checkAuth() {
gapi.auth.authorize({ client_id: clientId, scope: scopes, immediate: true }, handleAuthResult);
}
Google Drive data is private data it is owned by you and your account. In order for your script to access that data you need to give it permissions to see that data. This is the consent screen you are giving it permission to view the data. Short of possibly setting the Google Drive folder to public and then using a public API key to access drive instead of Oauth2.
There are work abounds but none of them work with JavaScript. If this is the only account you will be accessing I recommend you look into a server sided language like PHP or python and use a Service account. You will have to give the service account access to your google drive by sharing the folders in question with it. This is how service accounts are preauthorized.
Answer: There is no way to do away with the consent screen in JavaScript. Or save your authentication for lager use (Refresh token). Switch to a server sided language.
I'm trying to get the connections in linkedIn using their API, but when I try to retrieve the connections I get a 401 unauthorized error.
in the official documentation says
You must use an access token to make an authenticated call on behalf
of a user
Make the API calls You can now use this access_token to make API calls on behalf of this user by appending
"oauth2_access_token=access_token" at the end of the API call that you
wish to make.
The API call that I'm trying to do is the following
Error -->
http://api.linkedin.com/v1/people/~/connections:(id,headline,first-name,last-name)?format=json&oauth2_access_token=access_token
I have tried to do it with the following endpoint without any problems.
OK --> https://api.linkedin.com/v1/people/~:(id,first-name,last-name,formatted-name,date-of-birth,industry,email-address,location,headline,picture-urls::(original))?format=json&oauth2_access_token=access_token
this list of endpoints for the connections API are described here
http://developer.linkedin.com/documents/connections-api
I just copied and pasted one endpoint from there, so the question is what's the problem with the endpoint for getting the connections? what am I missing?
EDIT:
for the preAuth Url I'm using
https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=ConsumerKey&scope=r_fullprofile%20r_emailaddress%20r_network&state&state=NewGuid&redirect_uri=Encoded_Url
https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code&code=QueryString_Code&redirect_uri=EncodedCallback&client_id=ConsummerKey&client_secret=ConsumerSecret
please find attached the login screen requesting the permissions
EDIT2:
Switched to https and worked like a charm!
Access tokens are issued for a specific scope that describes the extent of the permission you are requesting. When you start the authentication transaction, you add a specific parameter (called scope) that requests the user to consent access to what you want (in this case their connections). If I remember correctly, in LinkedIn that is r_network.
Check their documentation here: http://developer.linkedin.com/documents/authentication#granting
So, your call is perfectly ok, but very likely your access_token doesn't have enough privileges.
apiHelper.getRequest(getActivity(),"https://api.linkedin.com/v1/people/~/connections?modified=new", new ApiListener() {
#Override
public void onApiSuccess(ApiResponse response) {
}
#Override
public void onApiError(LIApiError error) {
}
});
If you are trying to get user connections using the LinkedIn SDK for android like in the snippet above,
Check for permissions in the SDK in this class com.linkedin.platform.utils.Scope.
Make sure r_network is available when building your scope. Example
public static final LIPermission R_NETWORK = new LIPermission("r_network", "Your network");
Can now be used like this to build scope
Scope.build(Scope.R_BASICPROFILE, Scope.R_EMAILADDRESS, Scope.W_SHARE, Scope.R_FULLPROFILE, Scope.R_CONTACTINFO, Scope.R_NETWORK)