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']}

      

+3


source to share


1 answer


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.

+1


source







All Articles