Google+ Login Python Server - "invalid_client - OAuth client not found"
I have been trying to implement Google Sign in Android app for several weeks now. I was able to do a login for an android app getting the access token fine.
However, I have a python based application server that needs offline access to a google user account. I understand that in order to do this I need to get the OAuth code and send it to the server. The server will then use the OAuth code to get the refresh token.
However, this doesn't work. The server will never be able to retrieve the refresh token. I usually get the error,
invalid_client - OAuth client not found
Here is the android GooglePlusFragment.java,
package com.mywash.onboard;
import android.app.Activity;
import android.app.Fragment;
import android.content.Intent;
import android.content.IntentSender;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.crashlytics.android.Crashlytics;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;
import com.mywash.R;
import com.mywash.database.CurrentUserDBAdapter;
import com.mywash.server.ServerAsyncTask;
import com.mywash.server.ServerUrls;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import java.util.ArrayList;
import java.util.Set;
public class GooglePlusFragment extends Fragment implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
GoogleApiClient.ServerAuthCodeCallbacks
{
private final String TAG = "GooglePlusFragment";
/* Request code used to invoke sign in existingUser interactions. */
public static final int REQUEST_SIGNIN = 100;
public static final int REQUEST_AUTHORIZATION = 200;
public static final int REQUEST_AUTH_CODE = 300;
GooglePlusFragment instance = this;
/* Client used to interact with Google APIs. */
private static GoogleApiClient mGoogleApiClient;
private UserBean existingUser = null;
private String phoneNumber = "";
private boolean isFirstLogin = false;
/* A flag indicating that a PendingIntent is in progress and prevents
* us from starting further intents.
*/
private boolean mIntentInProgress;
/* Track whether the sign-in button has been clicked so that we know to resolve
* all issues preventing sign-in without waiting.
*/
private boolean mSignInClicked;
/* Store the connection result from onConnectionFailed callbacks so that we can
* resolve them when the existingUser clicks sign-in.
*/
private ConnectionResult mConnectionResult;
private OnFragmentInteractionListener mListener;
private Activity activity;
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @return A new instance of fragment GooglePlusFragment.
*/
public static GooglePlusFragment newInstance()
{
GooglePlusFragment fragment = new GooglePlusFragment();
return fragment;
}
public GooglePlusFragment()
{
// Required empty public constructor
}
@Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
this.activity = activity;
try
{
mListener = (OnFragmentInteractionListener) activity;
}
catch (ClassCastException e)
{
Crashlytics.logException(e);
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
try
{
CurrentUserDBAdapter userDBA = new CurrentUserDBAdapter(getActivity());
userDBA.open();
existingUser = userDBA.getUser();
phoneNumber = existingUser.getProfilePhone();
userDBA.close();
Log.d(TAG, "existingUser.isGooglePlusLogin() " + existingUser.isGooglePlusLogin());
Log.d(TAG, "existingUser.getProfileEmail() "+existingUser.getProfileEmail());
}
catch (Exception e)
{
e.printStackTrace();
Crashlytics.logException(e);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Log.d(TAG, "onCreateView");
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_google_plus, container, false);
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.build();
SignInButton signInButton = (SignInButton) view.findViewById(R.id.google_plus_login_button);
//signInButton.setVisibility(View.GONE); // Temp code.
signInButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View view)
{
Log.d(TAG, "mSignInClicked");
if (view.getId() == R.id.google_plus_login_button && !mGoogleApiClient.isConnecting())
{
mSignInClicked = true;
mGoogleApiClient.connect();
Log.d(TAG, "isConnecting "+mGoogleApiClient.isConnecting());
}
}
});
return view;
}
@Override
public void onStart()
{
super.onStart();
if(existingUser.isActive())
mGoogleApiClient.connect();
else
googlePlusLogout();
}
@Override
public void onStop()
{
super.onStop();
if (mGoogleApiClient.isConnected())
{
mGoogleApiClient.disconnect();
}
}
@Override
public void onDetach()
{
super.onDetach();
mListener = null;
}
@Override
public void onConnected(Bundle connectionHint)
{
try
{
Log.d(TAG, "#### onConnected");
if(mSignInClicked) {
mSignInClicked = false;
if (Plus.PeopleApi.getCurrentPerson(mGoogleApiClient) != null) {
Person person = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);
UserBean freshUserData = new UserBean();
freshUserData.setProfileName(person.getDisplayName());
freshUserData.setProfilePicUrl(person.getImage().getUrl());
freshUserData.setProfileEmail(Plus.AccountApi.getAccountName(mGoogleApiClient));
freshUserData.setGooglePlusLogin(true);
if (phoneNumber.length() > 0)
freshUserData.setProfilePhone(phoneNumber);
new LoginActivity.BitmapFromUrl().execute(freshUserData.getProfilePicUrl());
CurrentUserDBAdapter userDBA = new CurrentUserDBAdapter(getActivity());
userDBA.open();
userDBA.insertOrUpdateUser(freshUserData);
userDBA.close();
new AccessTokenTask().execute();
}
}
else
googlePlusLogout();
}
catch (Exception e)
{
e.printStackTrace();
Crashlytics.logException(e);
}
}
public void onConnectionFailed(ConnectionResult result)
{
if (!mIntentInProgress)
{
if (mSignInClicked && result.hasResolution())
{
// The existingUser has already clicked 'sign-in' so we attempt to resolve all
// errors until the existingUser is signed in, or they cancel.
try
{
result.startResolutionForResult(getActivity(), REQUEST_SIGNIN);
mIntentInProgress = true;
}
catch (IntentSender.SendIntentException e)
{
Crashlytics.logException(e);
// The intent was canceled before it was sent. Return to the default
// state and attempt to connect to get an updated ConnectionResult.
mIntentInProgress = false;
mGoogleApiClient.connect();
}
}
}
}
@Override
public void onConnectionSuspended(int i)
{
}
@Override
public CheckResult onCheckServerAuthorization(String s, Set<Scope> set) {
return null;
}
@Override
public boolean onUploadServerAuthCode(String idToken, String serverAuthCode)
{
return true;
}
private class AccessTokenTask extends AsyncTask<Void, Void, String>
{
String freshEmail = "";
@Override
protected void onPreExecute()
{
super.onPreExecute();
Log.d(TAG, "existingUser.getProfileEmail() >>"+existingUser.getProfileEmail() +"<<");
Log.d(TAG, "Plus.AccountApi.getAccountName(mGoogleApiClient) >>"+Plus.AccountApi.getAccountName(mGoogleApiClient)+"<<");
// if existing email id does not match with returned mail id, consider as first login.
if(!existingUser.getProfileEmail().equals(Plus.AccountApi.getAccountName(mGoogleApiClient)))
isFirstLogin = true;
}
@Override
protected String doInBackground(Void... params)
{
Log.d(TAG, "Fetching .. GOOGLE ACCESS TOKEN");
String accessToken = null;
try
{
freshEmail = Plus.AccountApi.getAccountName(mGoogleApiClient);
Log.d(TAG, "freshEmail .. "+freshEmail);
Log.d(TAG, "isFirstLogin .. "+isFirstLogin);
/*if(isFirstLogin)
{*/
Bundle appActivities = new Bundle();
appActivities.putString(GoogleAuthUtil.KEY_REQUEST_VISIBLE_ACTIVITIES,
"http://schema.org/AddAction");
String serverID = getString(R.string.GOOGLE_SERVICE_CLIENT_ID);
String scopes = "oauth2:server:client_id:" + serverID
+ ":api_scope: "
+ "https://www.googleapis.com/auth/plus.login" + " "
+ "https://www.googleapis.com/auth/plus.profile.emails.read" + " "
+ "https://www.googleapis.com/auth/userinfo.email" + " "
+ "https://www.googleapis.com/auth/userinfo.profile";
accessToken = GoogleAuthUtil.getToken(
getActivity(), // Context context
freshEmail, // String accountName
scopes, // String scope
appActivities); // Bundle bundle
mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
// other builder methods
.addApi(Plus.API)
.addScope(Plus.SCOPE_PLUS_LOGIN)
.addScope(Plus.SCOPE_PLUS_PROFILE)
.requestServerAuthCode(serverID, instance)
.build();
/*}
else
{
String scope = "oauth2: " + Scopes.PLUS_LOGIN + " " + Scopes.PROFILE + " " + Scopes.PLUS_ME;
accessToken = GoogleAuthUtil.getToken(activity, freshEmail, scope);
}*/
}
catch (UserRecoverableAuthException e)
{
Crashlytics.logException(e);
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
}
catch (Exception e)
{
Crashlytics.logException(e);
e.printStackTrace();
}
return accessToken;
}
@Override
protected void onPostExecute(String token)
{
super.onPostExecute(token);
Log.d(TAG, "GOOGLE ACCESS TOKEN: " + token);
try
{
if(token != null && !token.equals(""))
{
ArrayList<NameValuePair> nameValuePairs = new ArrayList<>();
nameValuePairs.add(new BasicNameValuePair("type", "android"));
nameValuePairs.add(new BasicNameValuePair("device_id", "blah_blah"));
nameValuePairs.add(new BasicNameValuePair("email", freshEmail));
nameValuePairs.add(new BasicNameValuePair("access_token", token));
ServerAsyncTask serverTask = new ServerAsyncTask(getActivity());
serverTask.setNameValuePairList(nameValuePairs);
serverTask.setCancellable(false);
serverTask.execute(ServerAsyncTask.POST, ServerUrls.POST_LOGIN, "0");
}
}
catch (Exception e)
{
e.printStackTrace();
Crashlytics.logException(e);
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == REQUEST_SIGNIN)
{
if (resultCode != getActivity().RESULT_OK)
{
mSignInClicked = false;
}
mIntentInProgress = false;
if (!mGoogleApiClient.isConnected())
{
mGoogleApiClient.reconnect();
}
}
else if(requestCode == REQUEST_AUTHORIZATION)
{
new AccessTokenTask().execute();
}
}
public static void googlePlusLogout()
{
if (mGoogleApiClient.isConnected())
{
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient)
.setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
}
});
mGoogleApiClient.disconnect();
}
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnFragmentInteractionListener
{
// TODO: Update argument type and name
public void onGooglePlusFragmentInteraction(Uri uri);
}
}
Here is the application server python code where the error occurs,
def get_android_data(self, access_token, email=None):
print access_token, email, "..........."
gplus_auth = False
if email:
try:
user = db.users.find_one({'email': email})
if user and 'android_credentials' in user:
print "creds found"
credentials = Credentials.new_from_json(json.dumps(user['android_credentials']))
user_info_service = build(
serviceName='oauth2',
version='v2',
http=credentials.authorize(httplib2.Http())
)
user_info = user_info_service.userinfo().get().execute()
else:
gplus_auth = True
except AccessTokenRefreshError, e:
gplus_auth = True
print e
if gplus_auth:
try:
oauth_flow = OAuth2WebServerFlow(
client_id=app.config['GPLUS_CREDS']['client_id'],
client_secret=app.config['GPLUS_CREDS']['client_secret'],
scope=app.config['GPLUS_CREDS']['scope'],
redirect_uri=app.config['GPLUS_CREDS']['redirect_uri']
)
credentials = oauth_flow.step2_exchange(access_token)
user_info_service = build(
serviceName='oauth2',
version='v2',
http=credentials.authorize(httplib2.Http())
)
user_info = user_info_service.userinfo().get().execute()
except FlowExchangeError, e:
print e
return {'status': 'failure', 'error': str(e)}, 403
insert_data = {}
insert_data['android_credentials'] = json.loads(credentials.to_json())
insert_data["pictureUrl"] = user_info.get('picture', "")
insert_data["user_id"] = "g_" + user_info.get("id", "")
insert_data["email"] = user_info.get("email", "")
insert_data["name"] = user_info.get("name", "") + user_info.get("givenName", "")+" "+user_info.get("name", "") + user_info.get("familyName", "")
insert_data['authData'] = {
'id': user_info.get("id", ""),
'type': 'google'
}
if self.user_exists(insert_data.get("user_id")):
insert_data["updatedAt"] = datetime.datetime.now()
device_id = set(db.users.find_one({'user_id': insert_data.get("user_id")}).get('device_id', []))
if device_id:
device_id.add(self.device_id)
insert_data['device_id'] = list(device_id)
result = db.users.update(
{'user_id': insert_data.get("user_id")},
{'$set': insert_data}
)
else:
insert_data['device_id'] = [self.device_id]
db.users.insert(insert_data)
emails.email_welcome({
"email": insert_data["email"],
"name": insert_data["name"]
})
print credentials.to_json()
print user_info
return {'status': 'success', 'id': insert_data['user_id']}
source to share
The problem seems to be related to the Google Client credentials you are using with your code. It seems that you have created a single set of credentials for both of your applications by selecting Installed Application in the Google Developer Console-> Credentials-> Create New Client ID
You need to generate another set of credentials for the Python server application from the Google Developer Console by selecting Web Application in Google Developer Console -> Credentials-> Create New Client ID
This is why the API complains that its "invalid_client" is making a request to google servers. Hope this solves your problem.
source to share