Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Authenticating G+ users on the server side, after client-side login

I'm trying to set up a Sign in with Google button that will allow people to purchase things on my website.

Client-side authentication looks pretty straightforward, but I'm struggling to understand how the server-side authentication works. In the example code they pass the client-side "code" parameter to the server, where it can be traded in for an access token, which can then be used to see the list of the user's friends.

But I don't want to see the list of the user's friends. I just want to be certain that the client is actually who they claim to be.

After retrieving the token, the sample code puts the token in the session, and seems to use the presence of the token to verify that the user is authenticated. Is that correct/safe? Should(n't) my server re-verify the token somehow (how?) when it's time to do a purchase? Should I constantly re-verify the token with Google on every request? (Hopefully not?)

like image 345
Dan Fabulich Avatar asked Nov 03 '22 02:11

Dan Fabulich


1 Answers

What you might want to do before performing a purchase is to verify the user is who you expect them to be by securely passing the user id from the client to your server and verifying it against the user id for the stored credentials. This offers additional protection against replay attacks where an attacker is pretending to be your site's user by hijacking their session and is the most relevant check to make before accepting payment from the user.

I would not rely on user validation alone as a mechanism for protection against fraud. You should use a secure payment system such as the Google Commerce platform and follow the best practices for commerce.

As a reminder, the OAuth2 v2 endpoint should be used to check your token every time the cached credentials are initialized. Checking on every request seems a bit excessive because you should be using cached credentials that have already been validated and stored server-side. At most, you could perform the checks when you update an access token but if you trust your refresh token, you should be sufficiently safe if you perform the checks when you create the account and set that refresh token.

The following steps are taken in addition to the user id validation upon account creation:

  • Verify the client is who you expect it to be. This protects you against forged access tokens being passed to your app for effectively making requests on behalf of the attacker using your quota.
  • Verify that the account was created by your application, this protects you and your users against cross-site request forgery in cases where additional accounts are created on behalf of a user.

As mentioned in your linked post, the sample code in the Google+ quickstarts should sufficiently demonstrate how to perform these checks in a variety of programming languages for account authorization.

Within the HTML/JS client, the following code shows where the userId (value, as opposed to the special string "me") is retrieved for passing to the connect method to verify the Google+ userId:

  var request = gapi.client.plus.people.get( {'userId' : 'me'} );
  request.execute( function(profile) {
      $('#profile').empty();
      if (profile.error) {
        $('#profile').append(profile.error);
        return;
      }
      helper.connectServer(profile.id);
      $('#profile').append(
          $('<p><img src=\"' + profile.image.url + '\"></p>'));
      $('#profile').append(
          $('<p>Hello ' + profile.displayName + '!<br />Tagline: ' +
          profile.tagline + '<br />About: ' + profile.aboutMe + '</p>'));
      if (profile.cover && profile.coverPhoto) {
        $('#profile').append(
            $('<p><img src=\"' + profile.cover.coverPhoto.url + '\"></p>'));
      }
    });

... and the following code shows the Google+ id being passed.

connectServer: function(gplusId) {
  console.log(this.authResult.code);
  $.ajax({
    type: 'POST',
    url: window.location.href + '/connect?state={{ STATE }}&gplus_id=' +
        gplusId,
    contentType: 'application/octet-stream; charset=utf-8',
    success: function(result) {
      console.log(result);
      helper.people();
    },
    processData: false,
    data: this.authResult.code
  });
}

The relevant code performing these checks in the Java sample is as follows:

      // Check that the token is valid.
      Oauth2 oauth2 = new Oauth2.Builder(
          TRANSPORT, JSON_FACTORY, credential).build();
      Tokeninfo tokenInfo = oauth2.tokeninfo()
          .setAccessToken(credential.getAccessToken()).execute();
      // If there was an error in the token info, abort.
      if (tokenInfo.containsKey("error")) {
        response.status(401);
        return GSON.toJson(tokenInfo.get("error").toString());
      }
      // Make sure the token we got is for the intended user.
      if (!tokenInfo.getUserId().equals(gPlusId)) {
        response.status(401);
        return GSON.toJson("Token's user ID doesn't match given user ID.");
      }
      // Make sure the token we got is for our app.
      if (!tokenInfo.getIssuedTo().equals(CLIENT_ID)) {
        response.status(401);
        return GSON.toJson("Token's client ID does not match app's.");
      }
      // Store the token in the session for later use.
      request.session().attribute("token", tokenResponse.toString());
      return GSON.toJson("Successfully connected user.");
    } catch (TokenResponseException e) {
      response.status(500);
      return GSON.toJson("Failed to upgrade the authorization code.");
    } catch (IOException e) {
      response.status(500);
      return GSON.toJson("Failed to read token data from Google. " +
          e.getMessage());
    }

In the samples, the ClientID came from the Google API console and will be distinct for your application.

like image 160
class Avatar answered Nov 11 '22 15:11

class