i'm trying to update google spreadsheet from android application.
I found the flow for such operation should be:
- Send request from android App
- receive request at Google App Script
- modify spreadsheet data in script
I used Oauth to authorize user, and i received idToken related to his session. However when i create HTTP request with this token attached, i get response 401 unauthorized
E/Volley: [676] NetworkUtility.shouldRetryException: Unexpected response code 401 for <HERE GOES URL OF MY SCRIPT>
2021-11-13 20:47:01.498 20626-20626/com.example.e2 W/System.err: com.android.volley.AuthFailureError
2021-11-13 20:47:01.498 20626-20626/com.example.e2 W/System.err: at com.android.volley.toolbox.NetworkUtility.shouldRetryException(NetworkUtility.java:189)
2021-11-13 20:47:01.498 20626-20626/com.example.e2 W/System.err: at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:145)
2021-11-13 20:47:01.499 20626-20626/com.example.e2 W/System.err: at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:132)
2021-11-13 20:47:01.499 20626-20626/com.example.e2 W/System.err: at com.android.volley.NetworkDispatcher.processRequest(NetworkDispatcher.java:111)
2021-11-13 20:47:01.499 20626-20626/com.example.e2 W/System.err: at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:90)
What is the correct way i should attach this token, to let google script "know" that this request is authenticated ?
Code of my script:
var ssa = SpreadsheetApp.openByUrl('<URL OF MY SPREADSHEET>');
var sheet = ssa.getSheetByName('<NAME OF TAB>');
function doPost(e){
var action = e.parameter.action;
if(action=='ADD'){
return addItem(e);
}
return 'Unsuported operation';
}
function addItem(e){
var date = new Date();
var id = "Item"+sheet.getLastRow();
var data = e.parameter.data;
sheet.appendRow([date,id,data]);
return ContentService.createTextOutput("Inserting Successfull").setMimeType(ContentService.MimeType.TEXT);
}
Code of my android app:
public class MainActivity extends AppCompatActivity {
private static final int RC_SIGN_IN = 11;
private static String oAuthAccessToken;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this);
if (account != null) {
oAuthAccessToken = account.getIdToken();
}
Button btn1 = (Button) this.findViewById(R.id.btn_login);
btn1.setOnClickListener((v) -> {
loginViaGoogle();
});
Button btn2 = (Button) this.findViewById(R.id.btn_send);
btn2.setOnClickListener((v) -> {
volleySendDataToGSheet();
});
Button btn3 = (Button) this.findViewById(R.id.btn_logout);
btn3.setOnClickListener((v) -> {
logout();
});
}
@NonNull
private GoogleSignInOptions getGoogleSignOptions() {
return new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.server_client_id))
.requestScopes(
new Scope("https://www.googleapis.com/auth/spreadsheets"),
new Scope("https://www.googleapis.com/auth/drive.scripts"),
new Scope("https://www.googleapis.com/auth/script.projects"),
new Scope("https://www.googleapis.com/auth/script.processes"),
new Scope("https://www.googleapis.com/auth/script.projects.readonly"))
.build();
}
private void logout() {
GoogleSignInOptions gso = getGoogleSignOptions();
GoogleSignInClient mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
mGoogleSignInClient.signOut();
}
private void loginViaGoogle() {
GoogleSignInOptions gso = getGoogleSignOptions();
GoogleSignInClient mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
Intent signInIntent = mGoogleSignInClient.getSignInIntent();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
}
}
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount account = completedTask.getResult(ApiException.class);
oAuthAccessToken = account.getIdToken();
Log.w(TAG, "Login success, user: " + account.getDisplayName() + ", token length:" + oAuthAccessToken.length());
} catch (ApiException e) {
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
}
}
private void volleySendDataToGSheet() {
String googleScriptAppUrl = "<URL OF MY SCRIPT>";
StringRequest request = new StringRequest(Request.Method.POST, googleScriptAppUrl,
successResponse -> {
Toast.makeText(this, "success: " + successResponse, Toast.LENGTH_LONG).show();
},
volleyError -> {
String message = "error: " + volleyError.getMessage() + " " + volleyError.networkResponse.statusCode;
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
volleyError.printStackTrace();
}
) {
@Override
protected Map<String, String> getParams() {
Map<String, String> params = new HashMap<>();
params.put("action", "ADD");
params.put("data", "Data value");
Log.i(TAG, "sending params: " + params.toString());
return params;
}
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json");
headers.put("idToken", oAuthAccessToken);
headers.put("id_token", oAuthAccessToken);
headers.put("accessToken", oAuthAccessToken);
headers.put("access_token", oAuthAccessToken);
headers.put("authorization", oAuthAccessToken);
headers.put("Authorization", oAuthAccessToken);
Log.i(TAG, "sending headers:" + headers.toString());
return headers;
}
};
int socketTimeout = 50000;
int retries = 1;
request.setRetryPolicy(new DefaultRetryPolicy(socketTimeout, retries, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(request);
}
}
Google App Script itself works fine, when i test it using chrome Postman extension, it correctly adds data into spreadsheet. So i hope its only a matter of fixing authorization headers in android request.
Thanks in advance!
Bearerauthorization token. The token should belong to the same GCP as the android app and the web app. It should also contain scopes of Spreadsheets or drive. This token is different fromidToken. Edit to show - web app publish specification(Execute as, Who has access)