How to pass OAuth token for Google SpreadSheet API from Kotlin code to JavaScript code? - google-sheets-api

I am trying to access Google SpreadSheet API in a special way, but I cannnot achieve that.
In short, I want to get OAuth access token in Kotlin code, but use it in JavaScript code.
At first, I get OAuth access token in Kotlin like below (using com.google.api-client.google-api-client and com.google.api-client.google-oauth-client-jetty).
val scopes = Collections.singletonList(SheetsScopes.SPREADSHEETS)
val httpTransport = GoogleNetHttpTransport.newTrustedTransport()
val jsonFactory = GsonFactory.getDefaultInstance()
val inputStream = this.javaClass.getResourceAsStream("credentials.json") ?: throw Exception("file not found")
val clientSecrets = GoogleClientSecrets.load(
jsonFactory,
InputStreamReader(inputStream)
)
val flow = GoogleAuthorizationCodeFlow
.Builder(
httpTransport,
jsonFactory,
clientSecrets,
scopes
)
.setDataStoreFactory(FileDataStoreFactory(File("cache")))
.setAccessType("offline")
.build()
val receiver = LocalServerReceiver.Builder()
.setPort(8888)
.build()
val credential = AuthorizationCodeInstalledApp(flow, receiver)
.authorize("user")
val info = Body(
clientSecrets.installed.clientId, // clientId
clientSecrets.installed.clientSecret, // clientSecret
clientSecrets.installed.redirectUris, // redirectUris
credential.accessToken, // apiAccessToken
credential.refreshToken, // refreshToken
scopes, // scopes
"Bearer", // tokenType
credential.expirationTimeMilliseconds, // expiryDate
"Sample Sheet" // title
)
Next, I want to use access token and access Google SpreadSheet API in JavaScript. I pass required information via HTTP from Kotlin code to JavaScript code. My JavaScript code like below (using googleapis#39.2.0).
import { google } from 'googleapis'
// event.body equals 'info' object shown in Kotlin code
export async function handler(event, context) {
const body = JSON.parse(event.body)
const oAuth2Client = new google.auth.OAuth2(
body.clientId,
body.clientSecret,
body.redirectUris[0]
)
oAuth2Client.setCredentials({
access_token: body.apiAccessToken,
refresh_token: body.refreshToken,
scope: body.scopes.join(' '),
token_type: body.tokenType,
expiry_date: body.expiryDate
})
const sheets = google.sheets({ version: 'v4', oAuth2Client })
const spreadSheetId = await createSheet(sheets, body.title)
return {
id: spreadSheetId
}
}
async function createSheet(sheets, title) {
const request = {
resource: {
property: {
title: title
}
}
}
const response = await sheets.spreadsheets.create(request)
return {
id: response.data.spreadsheetId
}
}
While I can successfully sign in via OAuth and get access token, I got a error below at const response = await sheets.spreadsheets.create(request).
{
code: 401,
errors: [
{
message: 'Login Required.',
domain: 'global',
reason: 'required',
location: 'Authorization',
locationType: 'header'
}
]
}
I cannot understand why I got this error. So, my question is how can I successfully use Google SpreadSheet API in this case (authorize in Kotlin, use API in JavaScript).

Related

razor pages with firebase auth - where to put this token ? :)

i am working on web site with razor pages. part of the site should be accessed only by registred users. decided to go with firebase authentification (now with login and password ).
created everything necessary in firebase.
created backend code for user registration - works well.
created area which requires authorisation
services.AddRazorPages(options =>
{
options.Conventions.AuthorizeAreaFolder("User", "/");
})
added jwt middleware
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
added code to login page to call firebase to get token
function login()
{
firebase.auth().signInWithEmailAndPassword(email, password)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
alert("signed");
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorMessage);
});
}
got token from firebase.
if i'd call service next, i'd simply put token in "bearer" header.
tried to find how to add header to current browser for future requests and failed.
as i understand, i need this token to be added to auth header ? how ? :)
feeling dumb ;( tried to google, but most samples are for using this token later with api calls.
or i am going in the wrong direction?
tia
ish
well. it seems that it is not possible to add bearer from js, so i switched to cookies
in startup.cs use cookies
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["bearer"];
return Task.CompletedTask;
}
};
code to login with firebase, put token into the cookie and redirect
function login() {
firebase.auth().signInWithEmailAndPassword(email, password)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
firebase.auth().currentUser.getIdToken(true).then(function (idToken)
{
document.cookie = "bearer" + "=" + idToken;
window.location.href = "/user/index";
}).catch(function (error) {
// Handle error
});
alert("signed");
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
alert(errorMessage);
});
}
or the same with firebaseUI
function login1()
{
ui.start('#firebaseui-auth-container', {
signInSuccessUrl: '/User/index',
signInOptions: [
{
provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,
requireDisplayName: false
}
],
callbacks:
{
signInSuccessWithAuthResult: function (authResult, redirectUrl)
{
var user = authResult.user;
firebase.auth().currentUser.getIdToken(true).then(function (idToken) {
document.cookie = "bearer" + "=" + idToken;
}).catch(function (error) {
// Handle error
});
return true;
}
}
});
}

setting cookies from GraphQL resolver

I'm learning GraphQL via https://github.com/the-road-to-graphql/fullstack-apollo-express-postgresql-boilerplate
and I'm wondering how to set cookies from a resolver as I'm used to using Express to do so.
signIn: async (
parent,
{ login, password },
{ models, secret },
) => {
const user = await models.User.findByLogin(login);
if (!user) {
throw new UserInputError(
'No user found with this login credentials.',
);
}
const isValid = await user.validatePassword(password);
if (!isValid) {
throw new AuthenticationError('Invalid password.');
}
return { token: createToken(user, secret, '5m') };
},
instead of returning a token obj, how can I access the response object and add a cookie?
You can achieve this using the context object, looking at the example you send. You will need to return the res variable from this function https://github.com/the-road-to-graphql/fullstack-apollo-express-postgresql-boilerplate/blob/master/src/index.js#L55
The context object is located at the 3rd argument to your resolver. The context is created on each request & available to all resolvers.
Example:
const server = new ApolloServer({
context: ({res}) => ({res})
});
function resolver(root, args, context){
context.res// express
}

Bing Ads Script to change shared campaign budget on multiple accounts using Google Sheets

I have a Google Ads script running to change campaign budgets, but implementation of the same script into Bing Ads is more difficult for me. I'm having problems with the code to connect Google Sheets with Bing Ads Script. I got clientId, clientSecret and refresh token to authorize Google service in Bing, but am struggling with the code to allow the script read my Google Sheets file.
I attached some code responsible for connecting Google Sheets file to Bing Script. It should allow it to read it's content and later change it to whatever values I provided in that file.
const credentials = {
accessToken: '', // not sure if i needed it if I got refresh token
clientId: 'HIDDEN',
clientSecret: 'HIDDEN',
refreshToken: 'HIDDEN'
};
function main() {
var SPREADSHEET_URL = 'HIDDEN';
var GoogleApis;
(function (GoogleApis) {
GoogleApis.readSheetsService = credentials => readService("https://sheets.googleapis.com/$discovery/rest?version=v4", credentials);
 
// Creation logic based on https://developers.google.com/discovery/v1/using#usage-simple
function readService(SPREADSHEET_URL, credentials) {
const content = UrlFetchApp.fetch(SPREADSHEET_URL).getContentText();
const discovery = JSON.parse(content);
const accessToken = getAccessToken(credentials);
const standardParameters = discovery.parameters;
}
function getAccessToken(credentials) {
if (credentials.accessToken) {
return credentials.accessToken;
}
const tokenResponse = UrlFetchApp.fetch('https://www.googleapis.com/oauth2/v4/token', { method: 'post', contentType: 'application/x-www-form-urlencoded', muteHttpExceptions: true, payload: { client_id: credentials.clientId, client_secret: credentials.clientSecret, refresh_token: credentials.refreshToken, grant_type: 'refresh_token' } });
const responseCode = tokenResponse.getResponseCode();
const responseText = tokenResponse.getContentText();
if (responseCode >= 200 && responseCode <= 299) {
const accessToken = JSON.parse(responseText)['access_token'];
return accessToken;
}
throw new Error(responseText);
})(GoogleApis || (GoogleApis = {}));
it throws syntax error on the last line of the code:
})(GoogleApis || (GoogleApis = {}));
but i think there is more than that.
Please try the var GoogleApis declaration outside main() as this example shows: https://learn.microsoft.com/en-us/advertising/scripts/examples/calling-google-services
I hope this helps.

serverless custom authorizer denying authorization

I'm building an api with serverless and a custom authorizer on api-gateway. When running serverless-local and passing a valid authorization token, I'm getting 401 responses.
I've mostly copied this from examples, the function which is called by the authorizer is
var apiGatewayAuth = function(event, context, callback) {
if(event.authorizationToken == 'undefined'){
return callback(null,generatePolicy('', 'Deny', event.methodArn));
}
var token = validateToken(event.authorizationToken);
if (token.error) {
return callback(null, generatePolicy('', 'Deny', event.methodArn));
}
console.log('allow',generatePolicy(token.sub, 'Allow', event.methodArn))
return callback(null,generatePolicy(token.sub, 'Allow', event.methodArn));
};
The policy is generated via
var generatePolicy = function(accountId, effect, resource) {
var authResponse = {};
authResponse.principalId = accountId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17';
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke';
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
return authResponse;
};
The returned policy looks like
{ principalId: 'test_api_user',
policyDocument: {
Version: '2012-10-17',
Statement: [ { Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: 'my:arn' } ]
}
Is there something I am obviously doing wrong here?
My token.sub is a string of unique user name for my app, but AWS has no concept of who the user is, I thought this was fine.

Attempt to reuse auth token to connect xamarin app to azure fails

This initial login succeeds:
public static MobileServiceClient MOBILE = new MobileServiceClient("https://myapp.azure-mobile.net/",myApplicationKey);
MobileServiceAuthenticationProvider GOOGLEPROVIDER = MobileServiceAuthenticationProvider.Google;
private async Task Connect() {
var USER = await MOBILE.LoginAsync(this, GOOGLEPROVIDER);
var CACHE = new Dictionary<string, string> { { "token", USER.MobileServiceAuthenticationToken } };
var ACCOUNT = new Account(USER.UserId, CACHE);
var STORE = AccountStore.Create(this);
STORE.Save(ACCOUNT, "Google");
}
but then this attempt to reuse the token to reconnect without a login page fails:
public async Task Reconnect() {
var STORE = AccountStore.Create(this);
var token = STORE.FindAccountsForService("Google").ToArray()[0].Properties["token"];
// token seems ok
var jsonToken = new JObject();
jsonToken.Add("access_token", token);
var USER = await MOBILE.LoginAsync(MobileServiceAuthenticationProvider.Google, jsonToken); // BOOM!
}
... with the following message: "The POST Google login request must contain both code and id_token in the body of the request."
What I am getting wrong here?
The token you use in the code, viz.
var CACHE = new Dictionary { { "token",USER.MobileServiceAuthenticationToken } };
The MobileServiceAuthenticationToken above is a token specific to MobileServices and cannot be used in the LoginAsync method (LoginAsync method requires a Google OAuth token.)
Please see this Get User Info from Google Api Using Azure Mobile Services for Xamarin Android