Access Google spreadsheet API without auth token - google-sheets

I have created Google Spreadsheet, and given edit access to all (can edit even without login).
Here is the link. I would like to update this sheet with Google Spreadsheet API. But I am getting error. My requirement is update the sheet thru API even without access credential.

It is possible to write to spreadsheet without OAuth or API Keys. You need to use Service Account Keys.
Here is what I did for my Node.js environment.
Get a service account key from https://console.cloud.google.com/apis/credentials (You can here also restrict what this keys is allowed todo)
When creating, make sure you click the Furnish a new private key
Select JSON when it asks you how to download the key.
The service account key you have just generated includes a client_email.
Go to you google spreadsheet and allow this client_email to have write access on this document
Use the following code to authenticate
let jwtClient = new google.auth.JWT(client_email, null, private_key, [
"https://www.googleapis.com/auth/spreadsheets",
]);
//authenticate request
jwtClient.authorize(function(err, tokens) {
// at this point the authentication is done you can now use `jwtClient`
// to read or write to the spreadsheet
});
client_email and private_key are part of the service account key
A more detailed description can be found here. http://isd-soft.com/tech_blog/accessing-google-apis-using-service-account-node-js/ Also, all credit goes to this page.

You need to be authorized to make such requests
Every request your application sends to the Google Sheets API needs to
identify your application to Google. There are two ways to identify
your application: using an OAuth 2.0 token (which also authorizes the
request) and/or using the application's API key. Here's how to
determine which of those options to use:
If the request requires authorization (such as a request for an
individual's private data), then the application must provide an OAuth
2.0 token with the request. The application may also provide the API key, but it doesn't have to. If the request doesn't require
authorization (such as a request for public data), then the
application must provide either the API key or an OAuth 2.0 token, or
both—whatever option is most convenient for you.
That's it. There's no bypassing authorization.

Finally digged deep enough and found the answer. Any kind of writing, even to publicly editable sheets requires an OAuth flow:
https://issuetracker.google.com/issues/36755576#comment3

Jürgen Brandstetter's answer above is completely right. Using Postman, I have been successful without using an OAuth token (I needed my personal API key and a service account) - I have written to a new sheet (in fact I did a batchUpdate operation with two steps, first create a new sheet and then pasteData on it). I followed the instructions here to create a service account, downloaded the credentials JSON and used it to create and sign a JWT string that is later used as Bearer.
Here is the Java code to obtain the JWT string:
private static String getSignedJWT() throws IOException {
InputStream in = YourClass.class.getResourceAsStream("/downloaded-service-account-creds.json");
if (in == null) {
throw new FileNotFoundException("Resource not found");
}
ServiceAccountCredentials serviceAccountCredentials = ServiceAccountCredentials.fromStream(in);
GoogleCredentials googleCredentials = serviceAccountCredentials
.createScoped(Collections.singletonList(SheetsScopes.SPREADSHEETS));
PrivateKey privateKey = serviceAccountCredentials.getPrivateKey();
String privateKeyId = serviceAccountCredentials.getPrivateKeyId();
long now = System.currentTimeMillis();
Algorithm algorithm = Algorithm.RSA256(null, (RSAPrivateKey)privateKey);
String signedJwt = JWT.create()
.withKeyId(privateKeyId)
.withIssuer(serviceAccountCredentials.getClientEmail())
.withSubject(serviceAccountCredentials.getClientEmail())
.withAudience("https://sheets.googleapis.com/")
.withIssuedAt(new Date(now))
.withExpiresAt(new Date(now + 3600 * 1000L))
.sign(algorithm);
return signedJwt;
}
Dependencies needed: com.auth0:java-jwt and com.google.auth:google-auth-library-oauth2-http.
Here's the curl that uses the JWT string generated above:
curl --location --request POST 'https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID:batchUpdate?key=ANY_PERSONAL_API_KEY' \
--header 'Accept: application/json' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer YOUR_JWT_STRING' \
--data-raw '{
"requests": [
{
"addSheet": {
"properties": {
"title": "newPred",
"sheetId": 0
}
}
},
{
"pasteData": {
"coordinate": {
"columnIndex": 0,
"rowIndex": 0,
"sheetId": 0
},
"delimiter": "\t",
"data": "col1\tcol2\nPeter\t25",
"type": "PASTE_NORMAL"
}
}
]
}'

Not exactly what asked but here is a solution with google-api-nodejs-client worked for me:
const { google } = require('googleapis');
const sheets = google.sheets('v4');
const spreadsheetId = '.........';
(async () => {
const auth = new google.auth.GoogleAuth({
keyFile: './my_service_account_privatekey.json',
scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});
// get
try {
const getResult = await sheets.spreadsheets.values.get({
auth,
spreadsheetId,
range: 'Sheet1!B2:C4'
})
console.log('Got values:', getResult.data.values)
} catch (error) {
console.error('Get error:', error)
}
})()
docs: https://github.com/googleapis/google-api-nodejs-client#using-the-keyfile-property

Related

How to validate a self-issued JWT?

I was looking at the section about Self-issued OpenID Provider Response, where they describe a method of validation, where the public key is included in the token itself. They use this as an example token:
{
"iss": "https://self-issued.me",
"sub": "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs",
"aud": "https://client.example.org/cb",
"nonce": "n-0S6_WzA2Mj",
"exp": 1311281970,
"iat": 1311280970,
"sub_jwk": {
"kty":"RSA",
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx
4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2
QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqb
w0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
"e":"AQAB"
}
}
I get how you can use the appended key for validation. But I don't get what prevents someone from using a fake key-pair to create a similar token. The only way I see this happening is if the public key is known by the validator from somewhere else, but in that case it doesn't make a lot of sense to include it in the token.
How does this work?
The way this work is that the hackers don't have access to the private key that was used to sign the token signature. The public key is derived from the private key.
The public key is safe to distribute as it only verifies the token signature. T
I don't think it's that common to include the public key inside the token as the token size gets bigger. Instead, you, as a receiver, download it separately, or it is provided to you some other way.
The picture below gives a summary of how public-key cryptography works.
But I don't get what prevents someone from using a fake key-pair to create a similar token.
Given the validations in Self-Issued ID Token Validation there is no feasible way for "someone" to sign a token which would have the same sub (JWT Subject). Of course they may sign a Token which would pass validations, but ONLY for a different subject. So IF you choose to accept Self-Issued ID Tokens, the guarantee you get is that you can re-identify the same Subject. That's kind of the whole point, Self-Issued OpenID providers are personal wallets, given the cryptography present in the flow you can be sure a given returned subject is the same one you encountered prior as long as sub is the same as before (or one that you know and have established trust with OOB).
Node.js code as a reference:
import * as assert from "node:assert";
import * as jose from "jose";
let jwt;
const redirect_uri = "https://rp.example.com/siop/cb";
const nonce = "n-0S6_WzA2Mj";
{
// this is unreachable by the party verifying
const kp = await jose.generateKeyPair("ES256");
const sub_jwk = await jose.exportJWK(kp.publicKey);
jwt = await new jose.SignJWT({ sub_jwk, nonce })
.setSubject(await jose.calculateJwkThumbprint(sub_jwk))
.setIssuer("https://self-issued.me")
.setAudience(redirect_uri)
.setProtectedHeader({ alg: "ES256" })
.setExpirationTime("5m")
.setIssuedAt()
.sign(kp.privateKey);
}
const verified = await jose.jwtVerify(
jwt,
async (protectedHeader, token) => {
const { sub_jwk, sub } = JSON.parse(
new TextDecoder().decode(jose.base64url.decode(token.payload))
);
assert.equal(sub === (await jose.calculateJwkThumbprint(sub_jwk)), true);
const key = await jose.importJWK(sub_jwk, protectedHeader.alg);
assert.equal(key.type, "public");
return key;
},
{
audience: redirect_uri,
issuer: "https://self-issued.me",
}
);
assert.equal(verified.payload.nonce, nonce);
console.log(verified);

How can I set the Bearer token after authentication in Swagger UI

I have a REST API service provider, written in PHP. I tested it successfully in Postman, and it works properly there.
Now I am going to prepare API documentation for it, and I am using Swagger UI 3. I set it up properly and I can process Authorization with the top Authorize button.
After a successful login, I expect the respective Bearer token being set and used by the endpoints. But this is not gonna happen, when I try any endpoint, the REST server complains about lack of Authorization Header. I tested the network traffic, and there is no token along with the HTTP request.
My question is, how can I send the Bearer token in the header in Swagger UI, after successfully login using the Authorize button on the top? Is there any steps/process I should take to accompany the endpoint request with the token?
I used it in a .NET core project, and in the Startup file I had to put the following code part:
services.AddSwaggerGen(options =>
{
//authentication
var security = new Dictionary<string, IEnumerable<string>>
{
{"Bearer", new string[] { }},
};
options.AddSecurityDefinition("Bearer", new ApiKeyScheme
{
In = "Header",
Description = "Please insert JWT into field",
Name = "Authorization",
Type = "apiKey"
});
options.AddSecurityRequirement(security);
}

Error "The OneDriveForBusiness for this user account cannot be retrieved." when accessing Microsoft OneNote with Graph API

I make the following REST GET request:
https://graph.microsoft.com/v1.0/me/onenote/notebooks
I get the following response:
{
"error": {
"code": "30108",
"message": "The OneDriveForBusiness for this user account cannot be retrieved.",
"innerError": {
"request-id": "25926552-3157-483a-bbcd-41a7105cd531",
"date": "2017-07-22T18:46:07"
}
}
}
I do not have a One Drive For Business account. Do I really need one to access the OneNote API?
Thanks.
Yes. In order to use the API (to access OneNote data), you must have a OneDrive (whether personal/consumer or business/Office 365) - since the OneNote cloud data is actually stored in OneDrive/SharePoint. If you have an Office 365 account, you can try going to https://portal.office.com and then click in the left-hand "waffle" button, and click OneDrive which should create your own personal OneDrive for Business.
Please take a look at https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/onenote for more details.
Also, if you are just trying out the API you could use Graph Explorer. It has some saved/sample queries that you can try. (Under Sample Queries, click show more samples and toggle the OneNote switch).
Hope this helps,
Here how I solved it in my Azure function by switching to authentication with Microsoft account and using the classic OneNote Rest API.
var request = require('request');
module.exports = function (context, req) {
var microsoftAccountAccessToken = req.headers['x-ms-token-microsoftaccount-access-token'];
context.log( "Microsoft Account Access Token: " + microsoftAccountAccessToken );
request(
{
url: 'https://www.onenote.com/api/v1.0/me/notes/notebooks',
method: "GET",
headers: {
'Authorization': 'Bearer ' + microsoftAccountAccessToken
},
},
function( error, response, body )
{
if (!error && response.statusCode === 200) {
context.log(body);
context.res = {
body: body
};
context.done();
}
else {
context.log("error: " + error)
context.log("response.statusCode: " + response.statusCode)
context.log("response.statusText: " + response.statusText)
context.res = {
body: response.statusText
};
context.done();
}
}
);
};
https://learn.microsoft.com/en-us/graph/onenote-error-codes#30108
The user's personal OneDrive for Business could not be retrieved. The following table lists some possible causes.
The user's personal site has not been provisioned. The user should open OneDrive for Business and follow any instructions to provision the site. If this fails, they should contact their Office 365 tenant administrator.
The user's personal site is currently being provisioned. Try the request later.
The user does not have a valid OneDrive for Business license. The user should contact their Office 365 tenant administrator.
A network issue prevented the request from being successfully sent.
I tried many ways and finally I used the method mentioned here: https://learn.microsoft.com/en-us/previous-versions/office/office-365-api/how-to/onenote-auth
The auth server is login.live.com, the above page provides two methods: code and token. Both could use. After auth and get the token, I can call Graph API with that token.
Code method is simpler to demonstrate. First, open this in browser:
https://login.live.com/oauth20_authorize.srf
?response_type=token
&client_id={client_id}
&redirect_uri={redirect_uri}
&scope={scope}
Then, after login an account, it will callback. Just copy the access_token in the callback URL. Do:
GET https://graph.microsoft.com/v1.0/me/onenote/pages
Accept: application/json
Authorization: Bearer {access_token}
The pages could be retrieved without 30108 error. These are simple test steps. I implemented in Java, and can get OneNote data through Microsoft's Graph library(com.microsoft.graph:microsoft-graph:1.5.+). As below:
IOnenotePageCollectionPage pages = graphClient.me().onenote().pages().buildRequest().get();
graphClient is IGraphServiceClient. But I implemented the authentication provider through login.live.com.

Issue exchanging LInkedIn javascript token to rest oauth token

I am using the article located at https://developer-programs.linkedin.com/documents/exchange-jsapi-tokens-rest-api-oauth-tokens to exchange my Javascript access token to a REST OAuth token.
After following the directions here, no matter what I seem to do, I only get a 400 Bad Request response back.
The flow I use for Facebook and want to recreate with LinkedIn is; front end authenticates to LinkedIn and passes an access token to my API, the API then gets all necessary user information and passes my own bearer token back to the client, et voila.
Unfortunately LinkedIn doesn't play so nicely with this, and I need to convert my token to an OAuth token from its Javascript token.
I pass the cookie LinkedIn gives me to my API, it looks something like the below (where OAuthBase is http://oauth.googlecode.com/svn/code/csharp/OAuthBase.cs)
access_token: "oxmKI9aU4RCfksdegZ3obZGHK-vo6Q4-4FSQk"
member_id: "AmjWCF7ExN"
signature: "t8KEbLjJ+r6uM42tUwfJm5yWp70="
signature_method: "HMAC-SHA1"
signature_order: ["access_token","member_id"]
signature_version: "1"
I then am attempting to make a call to https://api.linkedin.com/uas/oauth/accessToken to do the actual exchange. My code for this is:
public async Task<IHttpActionResult> ConvertLinkedInToken(LinkedInCovertTokenObject val)
{
string normalizeduri;
string normalizedparams;
OAuthBase o = new OAuthBase();
string signature = o.GenerateSignature(new Uri("https://api.linkedin.com/uas/oauth/accessToken"), Startup.linkedInAuthOptions.ClientId, Startup.linkedInAuthOptions.ClientSecret, val.access_token, null, "POST", o.GenerateTimeStamp(), o.GenerateNonce(), out normalizeduri, out normalizedparams);
var client = new HttpClient();
var uri = new Uri("https://api.linkedin.com/uas/oauth/accessToken?" +
"oauth_consumer_key=" + Startup.linkedInAuthOptions.ClientId +
"&xoauth_oauth2_access_token=" + val.access_token +
"&signature_method=HMAC-SHA1" +
"&signature=" + signature
);
var response = await client.GetAsync(uri);
return Ok();
}
No matter how I play around all I get back from LinkedIn is a 400 Bad Request without any other useful information.
1) How can I convert LinkedIn JS token to Rest OAuth token in my c# api
This is how I achieved that:
On the frontend:
IN.User.authorize(function(){
// here you can find oauth token
var oauth_token = IN.ENV.auth.oauth_token;
// send this token to your API endpoint
});
On your API (curl example), of course replace OAUTH_TOKEN with token received on the frontend.
curl -X GET \
'https://api.linkedin.com/v1/people/~:
(id,firstName,lastName,siteStandardProfileRequest,picture-url,email-
address)?format=json' \
-H 'oauth_token: OAUTH_TOKEN'
You are looking at old documentation from LinkedIn. Starting from 12th May, LinkedIn has started rolling out new changes in their API which includes authentication. In my knowledge, LinkedIn is not using OAuth anymore, and you need OAuth2.0 henceforth for authentication. You should check this link for more information:
https://developer.linkedin.com/docs/signin-with-linkedin

Require Google to return email address as part of OAuth

I am using OAuth to access Gmail with dotNetOAuth. How can I force Google to return user's email address as part of callback after authorization?
By default, Google OAuth callback only returns the token secret and access tokens.
First you need to add the following scope (https://www.googleapis.com/auth/userinfo.email) to your oauth request.
After you're back to your app from Google and you have your access token, you can make a request using the access token to https://www.googleapis.com/userinfo/email?alt=json.
This will return the email address. More info at http://sites.google.com/site/oauthgoog/Home/emaildisplayscope
For getting the Email Id, you need to add the scope "https://wwww.googleapis.com/auth/userinfo.email"
Then you will get id_token in the response.
Response={
"access_token" : "ya29.eAG__HY8KahJZN9VmangoliaV-Jn7hLtestkeys",
"token_type" : "Bearer",
"expires_in" : 3600,
"id_token" : "id_token_from_server",
"refresh_token" : "1/GIHTAdMo6zLVKCqNbA"
}
Then use this id_token as below POST request:
https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=id_token_from_server
And you will get response like below:
Response={
"issuer": "accounts.google.com",
"issued_to": "80780.apps.googleusercontent.com",
"audience": "8078909.apps.googleusercontent.com",
"user_id": "1118976557884",
"expires_in": 3598,
"issued_at": 1456353,
"email": "emailId#gmail.com",
"email_verified": true
}
Make sure you add "www" in the APIs as shown above...
OAuth doesn't provide a facility for extra parameters during an OAuth handshake, so I don't think you can force Google to supply it. There is likely a Google API however that you can use your OAuth access token to call to fetch the email address after the handshake, however.
request OAuth scope to include the "Email Display Scope" https://www.googleapis.com/auth/userinfo.email
scope="http://www.google.com/m8/feeds/ https://www.googleapis.com/auth/userinfo.email"
Then use REST API like Hammock to get address
RestClient client = new RestClient
{
Authority = "https://www.googleapis.com",
};
RestRequest request = new RestRequest
{
Path = "userinfo/email?alt=json",
Credentials = OAuthCredentials.ForProtectedResource(
this.requestSettings.ConsumerKey,
this.requestSettings.ConsumerSecret,
this.requestSettings.Token,
this.requestSettings.TokenSecret)
};
var response = client.Request(request);
Here's a c# function for when you have pre-authorized the request as detailed above:
private void FetchUsersEmail(token)
{
var emailRequest = #"https://www.googleapis.com/userinfo/email?alt=json&access_token=" + token;
// Create a request for the URL.
var request = WebRequest.Create(emailRequest);
// Get the response.
var response = (HttpWebResponse) request.GetResponse();
// Get the stream containing content returned by the server.
var dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
var reader = new StreamReader(dataStream);
// Read the content.
var jsonString = reader.ReadToEnd();
// Cleanup the streams and the response.
reader.Close();
dataStream.Close();
response.Close();
dynamic json = JValue.Parse(jsonString);
var currentGoogleEmail = json.data.email;
}
(JValue is part of JSON.Net)
In php, apiOauth2Service.php class provides methods to access logged in user info. For this you can use userinfo->get() method. Make sure you also use scope https://www.googleapis.com/auth/userinfo.email.
This will work with same access token. Also you should try looking in other APIs for similar kind of information in return. This is much easier to look through oAuth_playground >> http://code.google.com/apis/explorer/
If you request the userinfo.email scope, Google returns an id_token along with the access_token.
The id_token can be unencrypted to provide the user's email address, at www.googleapis.com?/oauth2/v1/tokeninfo?id_token=IDTOKENHERE
More information here: https://developers.google.com/accounts/docs/OAuth2Login

Resources