phpgoogle-plusactions-on-googledialogflow-esgoogle-profiles-api

Account linking and google api


I've implemented the account linking in streamlined identity flow (https://developers.google.com/actions/identity/oauth2-assertion-flow)

// Get the jwt token from the request
    $token = $this->jwt->parse($body['assertion']);

    if (null === Client::where('id', $body['client_id'])->where('secret', $body['client_secret'])->first()
        && (false === $this->verifySignature($token) || false === $this->validateData($token))
    ) {
        return response()->json(
            [
                'error' => 'invalid_grant',
            ],
            Http::UNAUTHORIZED
        );
    }

    $sub = $token->getClaim('sub');
    $email = $token->getClaim('email');
    $scopes = explode(' ', $body['scope']);

    // Check if the user exists in the database or not
    $userId = User::findIdByGoogleIds($email, $sub);
    \Log::debug($userId);

    // If user doesn't exists and intent is get, ask google to send you the create intent with all user info.
    if (null === $userId && 'get' === $body['intent']) {
        \Log::debug('user not found, ask for a create request');

        return response()->json(
            [
                'error' => 'user_not_found',
            ],
            Http::UNAUTHORIZED
        );

    // If user doesn't exist and we are in intent create, let's create this user in our database.
    } elseif (null === $userId && 'create' === $body['intent']) {
        \Log::debug('user not found, create it');

        // If user exists bug don't have the google profile associated.
        if (null === ($user = User::findByMail($email))) {
            $user = new User();
            $user->password = Hash::make(str_random(8));
            $user->email = $email;
            $user->name = $token->getClaim('name');

            $user->save();
            //Generate a token for the user
            static::generateToken($user, $scopes);
        }

        // Asssociate google profile that user
        $profile = new GoogleProfile();
        $profile->google_id = $sub;
        $user->googleProfiles()->save($profile);

    } elseif (null !== $userId) {
        \Log::debug('google profile already existing');

        $user = User::find($userId->id);
    }

The JWT Token contain that informations :

{
  "sub": 1234567890,        // The unique ID of the user's Google Account
  "iss": "https://accounts.google.com",        // The assertion's issuer
  "aud": "123-abc.apps.googleusercontent.com", // Your server's client ID
  "iat": 233366400,         // Unix timestamp of the assertion's creation time
  "exp": 233370000,         // Unix timestamp of the assertion's expiration time
  "name": "Jan Jansen",
  "given_name": "Jan",
  "family_name": "Jansen",
  "email": "jan@gmail.com", // If present, the user's email address
  "locale": "en_US"
}

And I need to get the phone number filled by the user in his google profile (if there is one).

So i were thinking to use google + api, so I'm trying this :

    $client = new \Google_Client();
    $client->setClientId('my_client_id');
    $client->setClientSecret('my_client_secret');
    $client->setAccessToken($token->getPayload());
    $plus = new Google_Service_Plus($client);

    $test = $plus->people->get($sub);

the client_id and secret have been created on https://console.developers.google.com

The response is :

 {
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "authError",
    "message": "Invalid Credentials",
    "locationType": "header",
    "location": "Authorization"
   }
  ],
  "code": 401,
  "message": "Invalid Credentials"
 }
}
 {"exception":"[object] (Google_Service_Exception(code: 401): {
 \"error\": {
  \"errors\": [
   {
    \"domain\": \"global\",
    \"reason\": \"authError\",
    \"message\": \"Invalid Credentials\",
    \"locationType\": \"header\",
    \"location\": \"Authorization\"
   }
  ],
  \"code\": 401,
  \"message\": \"Invalid Credentials\"
 }
}

So, on what I understand, the account linking is an OAuth authentication, so it should send me a valid token access that I can use with the google api. But it's telling me that this token is invalid.

Am I missing something or i'm not doing that in the good way? Do I have to do again a new authentication specific to google api?


Solution

  • There are a number of problems with how you're doing what you want to do.

    First, the token you're getting in assertion is an Identity Token, not an Authorization Token. The token itself is not a bearer token that authorizes you to do anything on the user's behalf. The guidelines you point to about the assertion flow says that you should verify the token and then use this to get the unique ID of the user from the sub field. You should use this ID to determine if the user has already authenticated in your system and use the Auth Token and Refresh Token that you have for the user.

    Second, you probably don't want the Google+ API since the Google+ People object doesn't include a field for phone number. If it did, and if you wanted to use the "me" parameter to get private info about the user, you would have needed to request either the https://www.googleapis.com/auth/plus.login or https://www.googleapis.com/auth/plus.me auth scopes when the user logs in.

    Instead, you probably want to use the People API. The people object contains the phone numbers which you can retrieve using the get method, but there are some restrictions: