Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there any way to do sign in through Google Authentication rather than through the API? #26

Closed
dphuang2 opened this issue Aug 2, 2016 · 28 comments

Comments

@dphuang2
Copy link
Contributor

dphuang2 commented Aug 2, 2016

Edit for Clarification:

is it possible to do authentication with Google rather than the API.
For example:

  1. Redirect a user to the google sign in page and retrieve an access token there
  2. Using that access token retrieve the endpoint for the API
  3. Continue to make requests as needed from there.

The purpose of this is to allow for a more secure login rather than inputting your username and password into the API.

@xssc
Copy link
Contributor

xssc commented Aug 2, 2016

Yes. Please read the README before posting an issue

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 2, 2016

Sorry, I was unclear with what I meant. I meant is it possible to do authentication with Google rather than the API.
For example:

  1. Redirect a user to the google sign in page and retrieve an access token there
  2. Using that access token retrieve the endpoint for the API
  3. Continue to make requests as needed from there.

The purpose of this is to allow for a more secure login rather than inputting your username and password into the API.

@dphuang2 dphuang2 changed the title Is there any way to do sign in through google? Is there any way to do sign in through Google Authentication rather than through the API? Aug 2, 2016
@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 2, 2016

Yes, this should be possible to do as long as the token returned by Google works for Niantec (I have no idea about which scope it needs, but I assume it'll just be viewing their e-mail address). Here's some reference documentation: https://developers.google.com/api-client-library/ruby/auth/installed-app

I won't really get time before weekend to get this done, however, the only caveat is that machines that do not have a browser may not be able to authorize (in which case perhaps a URl can be given to visit and token can be be entered/set). We can also save the refresh token somewhere local (a file for example, Google's API client gem handles this) and keep using the refresh token to generate a new access token upon expiry.

@rojosinalma
Copy link

I'd say since this is just an API wrapper (with some extras), you can leave that implementation to the client that's integrating this gem.

That should be a basic oauth flow and you can even use omniauth-google-oauth2 (i.e).

I wouldn't consider that as a basic necessity for this gem and even more, I would consider it an overhead.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

I did try to implement it within my application with omniauth-google-oauth2 and ran into some problems. The steps I took were:

  1. Create a new Poke::API::Client
    client = Poke::API::Client.new
  2. Successfully retrieve the access token
  3. Make a Poke::API::Auth::GOOGLE object instance (username and password as just fillers)
    google = Poke::API::Auth::GOOGLE.new(@user.name, "password")
  4. Set the GOOGLE Object's @access_token instance variable to the access token received from google
    google.instance_variable_set(:@access_token, @user.access_token)
  5. Set the client's @auth instance variable to google
    client.instance_variable_set(:@auth, google)
  6. Fetch client endpoint
    client.instance_eval{ fetch_endpoint }

The error comes when I try to fetch_endpoint:
Poke::API::Errors::UnknownProtoFault (An unknown proto fault has occured => [Unable to fetch endpoint, please try to login again. @ /Users/xxx/.rvm/gems/ruby-2.3.1/bundler/gems/poke-api-46e0d6d5761c/lib/poke-api/response.rb:41:instore_endpoint']):`

Let me know if I missed anything. @elfenars

@rojosinalma
Copy link

rojosinalma commented Aug 3, 2016

@dphuang2 I read something about Niantic implementing cert-pinning on the endpoints now, I don't know if this applies to all the endpoints, but this sounds like either the endpoint changed or you're getting rejected.

Have you tried the vanilla solution for this API and see if it works anyway?
(Sorry I can't really try myself, the last time I used this was last Friday and the cert-pinning came out on Monday I think)

@rojosinalma
Copy link

rojosinalma commented Aug 3, 2016

Or maybe everything was good and the endpoint just failed... try again :(
Try debugging all the vars you set, including the @endpoint in response.rb:41... even better, post the full log of the client when you run it.

@xssc
Copy link
Contributor

xssc commented Aug 3, 2016

Cert pinning its only enabled client side currently. Should not be an issue.

@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 3, 2016

Isn't omniauth gem only for OAuth flow involving a web app (not a local program)? I have used it before but I remember needing a callback URL that will present a token to retrieve the access token.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

Yeah it is, I am working on a web app.

@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 3, 2016

Ah I see, should the api client only expose a way to store the access token by Google then to use? As you said it doesn't feel right to implement the entire OAuth flow. Provided the access token works (unsure at the moment).

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

I ran some tests and comparisons.

I found that the @access_token of the GOOGLE Object from the client (from client.login) differs from the token I get from omniauth-google-oauth2. I assume that this is because omniauth uses Web login and poke-api implements the use of gpsoauth which is an Android login.

The two tokens I get back from omniauth look like this (I tried using both and both gave: "Unable to fetch endpoint, please try to login again."):

token="ya29.Rejijrq01231RJIAFJdaifijYjwiadhiR3921231jfdbajfkbaCfajkMWLrgZZHiLA"
id_token="eyJhbGciOiJSUzI1NiIsImtpZCI6ImQwZWM1MTRhMzJiNmY4OGMwYWJkgndjanfadkfenajkfndajkbfeaukaa.aREQRqu3dibqyibwfeakjdsbfakfeakhjfbadsjfeajkfnaksdljbcknasdfhrq8uw3hrufeJAJAINAW$FIJA$WFJAJIDSAFNA3nfaFNA#I#FNAfjA#FIANFaFIANDSKFNAFNADNSKFANCSKVAFNADSFNKXZMVKAFNSEKDFNAEISNFKASNFAEIWNFASNKFANSFDSAFJASDNFAESNFAJSNFADSNFajsfkansmfbadskfbaskdfbak2ZXJpZmllZCI6dHJ1ZSwiYXpwIjoiMTcxMzk1MTQyMDk4LW92Z2piY2kzMXMxam5nOTB0dGMwOWtuYmZmOWM1NWZrLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiZW1haWwiOiJkeWxhbi5wLmh1YW5nQGdtYWlsLmNvbSIsImlhdCI6MTQ3MDI0NjgzOCwiZXhwIjoxNDcwMjUwNDM4fQ.vGQ364s5COriBeyhbsE7GUNrX9LyRbtlMyqqw_Bcf-W5zYJMw5Yfdiz1_QzFxfGAv-MP2-9h4I-6zKbL_QZwUNeVuYeHRGaoic16UGiZfSo3WF4zr-EZcdSnv7syCMx6gQsBIbCFIQLfpLy0JcaHlYFtt91hCoJJauO2cCq366E8qEuUE4zm5M7QhlyLnXWVt79of9nYWkzQBnZ6QuR9XXnEoFnCFxu-makaNdHKokm776WKgNMZtokrL4LeDp5rWwX7YBX2n79vCuJ-Q5dVviSQopFTJm5AQAcmHA_2YpAUaNojqwKVGz9REpYhqscCG0hbClIP8Paj0DqwT6nNZQ"

The access_token from GOOGLE Object looks like this (looks similar to the id_token from omniauth):

@access_token="eyJhbGciOiJSUzI1NiIsImtpZCI6IjM2ZTJlNDE5MzQxYjY5M2VjNDgyZDY2NzZiYWZjMzEzMzA2MWNhYWYifQ.fndjanfjkaenbfiabjkabfkasdjbfkabefkjaebfkbadjbfkjbadskjbvakjsbfnjkfaksfbeabfubjkeadbsfkabkebakjdbfjkasbjkfbaeklbnlabnfuaebfadsbfnmabskvmnb8ajksnefmaehjoiqhfoihqwiofhqwoifhqiwohfoqwenjfakbsdkjfbakfasfhafjheabkjfajkdshbfahjfdasjbnfukfgbhjeakjwhfiahlhjqraejkhruw4k5hw34789htugi3b3w98ufnb9i4f89iuw3dvb2dsZXVzZXJjb250ZW50LmNvbSIsImVtYWlsIjoiZHlsYW4ucC5odWFuZ0BnbWFpbC5jb20iLCJpYXQiOjE0NzAyNTA0MDEsImV4cCI6MTQ3MDI1NDAwMSwibmFtZSI6IkR5bGFuIEh1YW5nIiwicGljdHVyZSI6Imh0dHBzOi8vbGg0Lmdvb2dsZXVzZXJjb250ZW50LmNvbS8tVXgxTElzNmVlbDQvQUFBQUFBQUFBQUkvQUFBfQUFBQUFBTEEvWjVzd1NGR2MtYjQvczk2LWMvcGhvdG8uanBnIiwiZ2l2ZW5fbmFtZSI6IkR5bGFuIiwiZmFtaWx5X25hbWUiOiJIdWFuZyJ9.Er6weJIM-LMizaAPFUUJyjNE4oLWpjBWcDG50ogS6prjj1kZ57X9LIrZ_YZt8RgYcs4bCh-VZhX-bWNV3aXkMgoLX2YXtrHHmydlZ1wGiRq0qkpKyyv2Pj7nPaLmpxLCW9s7AflqNGdina26QUJEuApUy7CX3R-skHFcWMT-n76CzidIhGjOkWTRLv_AdhcpTtivd0tQ5coEqee_VGwUR8eQ_KfyxK4bZRZaXX6HNQFXSu3XdIUl6AXAUvYYfvT70MQWJ79XlTPQEyhOzCXsXChyotH_G5GgmQl2k1Wvc5txoQyYRHmHNDcKYLRmvtA3dV78aPnwuO9OBJFrfyDUkQ"

The point of comparing the two were to see if similar token lengths meant they were both compatible (that is why I tried using the id_token).

This is the line error is raised on:

     def store_endpoint(client)
        logger.debug "[+] Current Endpoint #{client.endpoint}"

        if client.endpoint == 'https://pgorelease.nianticlabs.com/plfe/rpc'
  -->     raise Errors::InvalidEndpoint if @response.api_url.empty?
        end

        return if @response.api_url.empty?

        logger.debug "[+] Setting Endpoint to #{@response.api_url}"
        client.endpoint = "https://#{@response.api_url}/rpc"
      end

It looks like using omniauth yields nothing when fetching an endpoint. Do any of you have an idea why the omniauth tokens do not work?

@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 3, 2016

Tokens that start with eY are JSON Web tokens, you should try and not share them (or garble them slightly) as they can share personal information (and are valid as well until expiry). I've edited your post so it's not valid a token anymore.

I believe the first token you get back is an authorization code as described by RFC 6749 which is then posted to generally the same location to retrieve an access token (this can then also include the refresh token). This access token is I believe what is required to be posted to Niantec, but I'm really not sure on this part (will have to look into another repo). Generally speaking the access token can be used to do any Google call for which you have the scope to do so.

I'll look into it this weekend, in the meantime without having to worry about the entire OAuth flow you could get your access token from https://developers.google.com/oauthplayground/ (use scope profile)

edit
It seems id_token is a google thing, it should be token (generally speaking) that you use, I'm not sure on this either.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

I garbled them slightly beforehand but thanks for the precaution :)

Could I use gpsoauth myself to retrieve an access token with the first token?

@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 3, 2016

JSON Web tokens continue to show information even after slightly garbling them (unless you change quite a lot). Regarding GPSOAuth, I'm not sure, Android has a completely different way of authenticating with Google.

I suppose you could compare the two tokens on https://jwt.io and see what their payload looks like?

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

I see, thanks for the heads up.

I'll try that out.

@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 3, 2016

I had a quick go at this as well, here's what I got

Android

{
  "iss": "accounts.google.com",
  "aud": "848232511240-7so421jotr2609rmqakceuu1luuq0ptb.apps.googleusercontent.com",
  "sub": "117355651878090214803",
  "email_verified": true,
  "azp": "848232511240-3vdrtrfdntljf2u4mlgtnnlhnign35d5.apps.googleusercontent.com",
  "email": "snip@gmail.com",
  "iat": 1470254166,
  "exp": 1470257766,
  "name": "snip",
  "picture": "snip",
  "given_name": "snip",
  "family_name": "snip",
}

Google OAuth

{
  "iss": "https://accounts.google.com",
  "at_hash": "8cmdAEc-4lYeIGueFL14MQ",
  "aud": "407408718192.apps.googleusercontent.com",
  "sub": "117355651878090214803",
  "email_verified": true,
  "azp": "407408718192.apps.googleusercontent.com",
  "email": "snip@gmail.com",
  "iat": 1470254218,
  "exp": 1470257818,
  "name": "snip",
  "picture": "snip",
  "given_name": "snip",
  "family_name": "snip",
  "locale": "en-GB"
}

Perhaps the audience/authorized presenter needs to match? Not sure how Niantec are validating. I can't even test anything as every time I login in I get a blank api_url... (for both Google and PTC).

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

I got similar results with my payload...

On a side note, how is www.pokeadvisor.com doing google authentication? It redirects me to a get a token that is authorized by Niantic themselves: image

@nabeelamjad
Copy link
Owner

nabeelamjad commented Aug 3, 2016

I would guess that Niantec set their OAuth2 flow to local app instead of web app so anyone could use their address.

Example: https://accounts.google.com/o/oauth2/auth?client_id=848232511240-73ri3t7plvk96pj4f85uj8otdat2alem.apps.googleusercontent.com&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code&scope=openid%20email%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email

This gives you a token back which probably then works when fed in to retrieve an access token.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

It's kind of clunky but I am guessing this is the only way to get a token through the web that validates with Niantic.

@nabeelamjad
Copy link
Owner

Alright figured it out, this is what you have to do:

grant_type :authorization_code
redirect_uri: urn:ietf:wg:oauth:2.0:oob
scope: openid email https://www.googleapis.com/auth/userinfo.email
client_secret: NCjF1TLi2CcY6t5mt0ZveuL7
client_id: 848232511240-73ri3t7plvk96pj4f85uj8otdat2alem.apps.googleusercontent.com
code: __YOUR-CODE-HERE__
  • You now get a response such as this
{
  "access_token": "ya29......",
  "token_type": "Bearer",
  "expires_in": 3600,
  "id_token": "eyJhbG.......",
  "refresh_token": "1/_2_U6VFBCyk......"
}
  • Use the id_token to authenticate successfully (untested but should work).

A bit of information, the client_id and client_secret are available on any Pokemon GO installation to be retrieved so that's where they come from (I just googled them up)

As for urn:ietf:wg:oauth:2.0:oob (copy paste from Google)

This value signals to the Google Authorization Server that the authorization code should be returned in the title bar of the browser, with the page text prompting the user to copy the code and paste it in the application. This is useful when the client (such as a Windows application) cannot listen on an HTTP port without significant client configuration.

When you use this value, your application can then detect that the page has loaded, and can read the title of the HTML page to obtain the authorization code. It is then up to your application to close the browser window if you want to ensure that the user never sees the page that contains the authorization code. The mechanism for doing this varies from platform to platform.

If your platform doesn't allow you to detect that the page has loaded or read the title of the page, you can have the user paste the code back to your application, as prompted by the text in the confirmation page that the OAuth 2.0 server generates.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 3, 2016

Wow, nice job on the procedure. That would have taken me a while...

I'll take a stab at it right now.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 4, 2016

Works like a charm! Here is the procedure I used:

require 'poke-api'
require 'httpclient'

clnt = HTTPClient.new
body = {
  grant_type: 'authorization_code',
  redirect_uri: 'urn:ietf:wg:oauth:2.0:oob',
  scope: 'openid email https://www.googleapis.com/auth/userinfo.email',
  client_secret: 'NCjF1TLi2CcY6t5mt0ZveuL7',
  client_id: '848232511240-73ri3t7plvk96pj4f85uj8otdat2alem.apps.googleusercontent.com',
  code: "xxx",
}
uri = 'https://accounts.google.com/o/oauth2/token'
response = clnt.post(uri, body)
body = response.body
hash = JSON.parse body
token = hash["id_token"]

client = Poke::API::Client.new
google = Poke::API::Auth::GOOGLE.new("username", "password")
google.instance_variable_set(:@access_token, token)
client.instance_variable_set(:@auth, google)
client.instance_eval { fetch_endpoint }

I was wondering though, is there any way to do this without having to get the code manually?

@nabeelamjad
Copy link
Owner

It is possible but it's going to be really hard to explain how, you basically have to 'spoof' being an actual user logging in while saving cookies and all that. HTTPClient can do all of this for you, but it'll be a lot of trial and error. Google Chrome's developer console (network tab) should be a great deal of help here too.

I found this https://github.com/ernilos/PokemonGoApi/blob/4adfa7a891940156193aa6997ceb9fcd895a2f65/PokemonGoApi/AuthenticationService.cs which will hopefully explain the process.

@dphuang2
Copy link
Contributor Author

dphuang2 commented Aug 4, 2016

Thats a pretty hacky solution X_X. Well now that I know that it is possible I am going to close this issue.

@dphuang2 dphuang2 closed this as completed Aug 4, 2016
@nabeelamjad
Copy link
Owner

It might just be easier requesting the user to provide you the token, saves a lot of trouble and you get a re-usable refresh token you can store somewhere in your DB for that user to generate the access token indefinitely.

@kddnewton
Copy link

In case anyone is interested, this https://github.com/kddeisz/poke-go-ivs encapsulates the google login business and shows you the IVs locally.

@nabeelamjad
Copy link
Owner

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants