I am currently following the instructions given here for cross-client validation in an Android-GAE app, so my users can give the Python backend off-line access to the G+ APIs on their behalf. That documentation also directs me here, which is really where I am having the trouble. I have successfully obtained an authorization code and sent it to the backend, but when the backend tries to exchange that code for access and refresh tokens I get "error:invalid_grant" with "error description: invalid code". To illustrate, when I copy the code and other needed info into a curl, the output is as follows:
curl -d "code=4/uG6moqbHvs9hgfG1HmEfT39zT1j0.Un-H_36OXGUWmmS0T3UFEsMwQpwOhwI&client_secret={MY_SECRET}&client_id={MY_WEB_CLIENT_ID}&grant_type=authorization_code" https://accounts.google.com/o/oauth2/token -v
* Adding handle: conn: 0x7fc359803a00
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7fc359803a00) send_pipe: 1, recv_pipe: 0
* About to connect() to accounts.google.com port 443 (#0)
* Trying 74.125.28.84...
* Connected to accounts.google.com (74.125.28.84) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_RC4_128_SHA
* Server certificate: accounts.google.com
* Server certificate: Google Internet Authority G2
* Server certificate: GeoTrust Global CA
* Server certificate: Equifax Secure Certificate Authority
> POST /o/oauth2/token HTTP/1.1
> User-Agent: curl/7.30.0
> Host: accounts.google.com
> Accept: */*
> Content-Length: 219
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 219 out of 219 bytes
< HTTP/1.1 400 Bad Request
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: Fri, 01 Jan 1990 00:00:00 GMT
< Date: Sun, 12 Jan 2014 16:38:42 GMT
< Content-Type: application/json
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
* Server GSE is not blacklisted
< Server: GSE
< Alternate-Protocol: 443:quic
< Transfer-Encoding: chunked
<
{
"error" : "invalid_grant",
"error_description" : "Invalid code.[Email: \n\nToken Record:\nToken: \"4/uG6moqbHvs9hgfG1HmEfT39zT1j0.Un-H_36OXGUWmmS0T3UFEsMwQpwOhwI\"\nIssueDomain: \"206701529154-7d35h3g4a22aef8d78p6up1cdiq3pl4d.apps.googleusercontent.com\"\nIssueTimeSec: 1389544361\nExpirationTime: 1389544961\nTokenUsage: 3\nScope: \"https://www.googleapis.com/auth/plus.login\"\nScope: \"https://www.googleapis.com/auth/userinfo.profile\"\nScope: \"https://www.googleapis.com/auth/userinfo.email\"\nScope: \"https://www.googleapis.com/auth/plus.moments.write\"\nScope: \"https://www.googleapis.com/auth/plus.me\"\nScope: \"https://www.googleapis.com/auth/plus.profile.agerange.read\"\nScope: \"https://www.googleapis.com/auth/plus.profile.language.read\"\nScope: \"https://www.googleapis.com/auth/plus.circles.members.read\"\nServiceInfo {\n ServiceId: 226\n Info <\n [security_lso_auth_oauth2.EarlyIssuedTokenProto] <\n auto_approved: false\n access_token: \"ya29.1.AADtN_UyJ2wiWB7lh7btUiCwUyHzix_DrubjdSsTxA6drG-ZccPWaeZJa31sebPnRCHiFw\"\n refresh_token: \"1/ImSjHYLkFcBOwsCezBg2dKpcqko9by3nTIh_k33ZMds\"\n >\n >\n}\nRevoked: true\nAuthorizedBy: 0x866419b291\nOAuthCallbackUrl: \"urn:accounts.google.com:api_auth:programmatic:virtual_redirect_uri\"\nOfflineAccess: true\nRevokeOnPasswordChange: true\nClientManagedRevocation: false\nInBundle: true\n]"
* Connection #0 to host accounts.google.com left intact
}%
I'm having a lot of trouble making sense of this error message to determine why my authorization code might be invalid. I did notice that in the explorer logs, all logs are time-stamped an hour in the past (so when I post a log at 11AM, it appears in the explorer with a timestamp of 10AM). Could a timezone discrepancy somehow be causing the oauth2 servers to think I am submitting authorization codes that are too old? I already tried changing my system time to match the log time, to no avail. Does anyone know what could be causing my authorization code to be invalid? I have searched high and low for hours.
Shortly after posting this question, I discovered the answer myself. Posting it here in case others run into the same problem. I noticed that a similar error was occurring in Google API Invalid grant on one-time auth code from GoogleAuthUtil.getToken, and the reason was that the developer needed to request refresh/access tokens multiple times within the span of 10 minutes (which is the expiration duration of the one-time authorization code). Inspecting my explorer logs again, I saw that I have a bug in my android code causing my cross-client validation code to be run twice in rapid succession instead of only once like it's supposed to. The first time it's run, the refresh and access tokens are returned correctly. The second time, the error is generated.
In retrospect, the "revoked = True" part of the error message should have pointed me to the answer.
Related
I am trying to connect to a WebSocket server that my Android device connects to from an app. I captured the packets on my Android device, and the initial request headers look like this:
GET / HTTP/1.1
Pragma: no-cache
Cache-Control: no-cache
Host: example.com:80
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ysWaBflPV9EmRaB1JpPMOQ==
Origin: http://example.com:80
Sec-WebSocket-Protocol: default-protocol
Sec-WebSocket-Extensions:
Sec-WebSocket-Version: 13
The response from the server looks like this:
HTTP/1.1 101 Switching Protocols
Date: Wed, 31 Jan 2018 02:37:20 GMT
Connection: upgrade
Set-Cookie: AWSALB=0yaRd5HOPlZSITp+bcXoZoIn/7YOOqE9o4/t/8b3kw2PTxooflm/85w+1JfudEE0Cwb1BUkWPV+t4kOnEm4FbLSWwMMFp8URbblZKj0a0kd0xB+glbLBHWxc/TPW; Expires=Wed, 07 Feb 2018 02:37:20 GMT; Path=/
Server: nginx/1.12.1
Upgrade: websocket
Sec-WebSocket-Accept: bj5wLF8vmyDrA7pqEgbHKbxqQSU=
Then, some communication begins, with lots of unrecognizable characters and some clear words in the messages. I don't have much experience with WebSockets, but I assume it is some form of compression.
I was able to send an identical request to this server using the ws module in Node.js, and I got an identical response to the one above. One notable difference was that when I set the protocol header to default-protocol, I received an error saying "Server sent no subprotocol". Without using this header, I still got the same response.
After the initial response, however, I did not receive any more messages, even though I did on my Android device. After about 30 seconds, the connection closes with code 1006 and no further information.
I tested the same request with curl and received the same headers back, but it also closed after about 30 seconds saying:
"Empty reply from server"
So my main question is obviously: What is going wrong, and how can I fix it?
More specifically, I am wondering if anyone with WebSocket experience knows if it is a problem with my client, or with the server itself.
It is possible that the server is authenticating me in some way on my Android device, but the headers that I captured are not revealing anything about that. Is it ever customary to authenticate a connection with a later message in the client-server communications? Is it possible that a separate HTTP request is authenticating me for this WebSocket server? All of these things seem unlikely to me since I found no other packets with anything related to auth requests. It seems much more likely that there is something wrong with the messages being sent.
After a lot of reading and Googling it seems I have made a complete setup for Google Cloud Messaging to send push-notifications. My missing link is the Reference_Ids that I must use to target apps. I have created a project and also added my apps to it.
When I send a push-request to GCM I get the following response:
{"multicast_id":7952352701122753715,"success":0,"failure":1,"canonical_ids":0,"results":[{"error":"InvalidRegistration"}]}
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Alt-Svc: quic=":443"; ma=2592000; v="35,34"
Vary: Accept-Encoding
Transfer-Encoding: chunked
Accept-Ranges: none
Cache-Control: max-age=0, private
Date: Wed, 21 Dec 2016 16:12:43 GMT
Server: GSE
Content-Type: application/json; charset=UTF-8
Expires: Wed, 21 Dec 2016 16:12:43 GMT
}
And the error reads "InvalidRegistration".
So my questions are:
Where do I find my registration Ids?
And as a related follow-up question, where do I find Registration_Ids for everyone using an app or a specific group or user?
BTW: I found a related question, but it does not seem to have an answer to as where to find these Ids. StackOverflow post.
InvalidRegistration means that the registration token (registration id) you used is invalid (doesn't exist, wrong format):
Check the format of the registration token you pass to the server. Make sure it matches the registration token the client app receives from registering with Firebase Notifications. Do not truncate or add additional characters.
Make sure that you are using the correct and corresponding registration token to the device you intend to send the message to. For testing, I would suggest to make use of the Firebase Console too, so that you can see if the error still occurs from there.
For Android, you can retrieve the registration token by calling:
FirebaseInstanceID.getToken()
You may then choose to store the token to your App Server.
You should use Api server_key .
Go to firebase console -> click on your project -> click on gear icon -> project_setting -> cloud_messaging
Background:
I am experiencing a very confusing behaviour with android Webviews in API 21 and up when testing in real devices.
I have a local HTML5 application (inside assets folder) with the following functionality
Login (2 steps authentication).
Show a list of items depending on the authentication.
The problem:
After doing the login requests, the server returns a cookie with the session. This cookie is not stored in the Webview when using real devices with API 21 or up. If I use emulators (Genymotion in this case), the cookies are properly stored.
More information:
The request to do the auth has the following headers:
POST http://myServer/j_spring_security_check HTTP/1.1
Proxy-Connection: keep-alive
Content-Length: 101
access-control-allow-origin: *
accept: application/json
access-control-allow-credentials: true
User-Agent: Framework/1.5.0 (Linux; U; Android 6.0.1; Nexus 5X Build/MMB29Q) App/0.1.1
Origin: file://
content-type: application/x-www-form-urlencoded
Accept-Language: en-US
X-Requested-With: app.package
Host: myServer
With the following response:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Set-Cookie: JSESSIONID=4D169E8656DBEDFFA4D17FE8D436A5BA; Expires=Fri, 19-Feb-2016 14:27:55 GMT; Path=/; HttpOnly
Content-Type: application/json;charset=UTF-8
Content-Length: 43
Date: Fri, 19 Feb 2016 14:17:55 GMT
The cookie is not stored in devices with API 21 or more. Same request/response works fine in the rest of devices + all the emulators
Clarification:
This flags are enabled inside the app:
android.webkit.CookieManager.setAcceptFileSchemeCookies(true);
(Before CookieManager or webview is instantiated, as documentation says)
if(VERSION.SDK_INT >= 21) {
CookieManager.getInstance().setAcceptThirdPartyCookies(this.nativeWebView, true);
}
If after doing the authentication, I access the cookies datastore and
check the "hasCookies" method, I get false.
The two step auth service actually calls 3 different paths from the same endpoints. None of the cookies that the response that generate this services are stored. I don't know if this is relevant or not.
When doing simple authentication (to a different server), cookies are stored properly in all the devices emulators.
I am using Angular 1.5
I am aware that the service is using http instead of https. That will be solved in the future.
I get no error message in the consoles.
Questions:
Is there any internal security measure in the webviews that blocks the storage of the cookies? Why does it work on emulators (that are rooted devices) and not in real devices? This really bugs me.
If the network request is done using window.fetch you may need to add:
fetch('/something', { credentials: 'same-origin' }) // or 'include'
On chromium, window.fetch has the credentials flag set by default to 'omit' and no cookies are stored into the cookie storage. More details of this bug here: https://bugs.chromium.org/p/chromium/issues/detail?id=477523
I launched burp as an emulator's proxy for debugging of http requests from my application with intercepting option switched on and at the startup I found that emulator sends a GET request to google:
GET /generate_204 HTTP/1.1
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.3; sdk Build/JWR66V)
Host: 173.194.32.129
Connection: Keep-Alive
Accept-Encoding: gzip
And gets a response like:
HTTP/1.1 204 No Content
Content-Length: 0
Content-Type: text/html; charset=UTF-8
Date: Thu, 05 Sep 2013 06:56:51 GMT
Server: GFE/2.0
So I would like to know if there is some purpose for making this request to google at the startup?
It's most likely for counting things:
active developers
emulator use
framework use
generating statistics how developers are spread over the world
...
It's Android trying to tell if the Wifi (or other network connection) connection has internet. I'm testing on real devices and it does the same thing. If you don't forward the message the connection status in Android Wifi Setting will say "Connected. No internet" until you forward and it gets a success back.
I have a rails app that uses Authlogic (from gem Communityengine). Now I want users to be able to login on a mobile device (Android in that case)
If I understand correctly I can use Http Basic Authentication to authorize specified actions, but the first step (I think) is, when the user enters his username and password in his Android device, to check wether that user exists and if that passwort is correct.
How would I do this?
The password is encrypted in the database (with a pw salt) So as I understand I can not just check the database if the provided user and password is correct.
Here are some Http headers from my app as the result from different curl requests I did, I guess Http basic auth is working since I get a correct HTTP code when I provide user credentials:
martin#martin-desktop:~/Applications$ curl -I username:password#localhost:3000/users/username/posts/new
HTTP/1.1 200 OK
Date: Sun, 21 Apr 2013 12:17:29 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 3.0.19
X-UA-Compatible: IE=Edge,chrome=1
ETag: "b9aa82940f06722b7d8e35ce46e3166d"
Cache-Control: must-revalidate, private, max-age=0
X-Request-Id: 291ee45de8cb95e06ed4e8e6d3d9408c
X-Runtime: 0.335820
X-Rack-Cache: miss
Set-Cookie: _AppName_session=BAh7CkkiD3Nlc3Npb25faWQGOgZFRkkiJTA0ZDRhYmQwNDJjYmM1OGJmZTkyNzJjMTc5NThkODg0BjsAVEkiDnJldHVybl90bwY7AEZJIj1odHRwOi8vZ2l2LWR1ZXJlbi51bmktbXVlbnN0ZXIuZGUvdXNlcnMvbWFydGluL3Bvc3RzL25ldwY7AEZJIhV1c2VyX2NyZWRlbnRpYWxzBjsARkkiAYAzYWM2NTJlMjgyODA5MTY1ODg2ZTBjNTdhNGUzYjk5MzRkNWQ0YTE4MDdlN2M0MWI2OTQzYmQyNzZmOGFlMGI5NjliMjU3NzZhYTg4NjBkM2U0Y2Q5YTJjYzgwODVlNzY5NDgwMzE2MmQ3NTBjYzlhMmQ4ZTViYTJmNjJmYzVhZQY7AFRJIhh1c2VyX2NyZWRlbnRpYWxzX2lkBjsARmkGSSIQX2NzcmZfdG9rZW4GOwBGSSIxYzlIQitJNXNYZHNGa2NCb2hKcm5DY2tIcHNxdWJuWDEydVdZUU5WSlMvZz0GOwBG--2b5f5687425d7ec414e34b79eb89e0b6b465c86f; path=/; HttpOnly
Status: 200
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8
martin#martin-desktop:~/Applications$ curl -I localhost:3000/users/username/posts/new
HTTP/1.1 302 Found
Date: Sun, 21 Apr 2013 12:17:40 GMT
Server: Apache/2.2.22 (Ubuntu)
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 3.0.19
X-UA-Compatible: IE=Edge,chrome=1
Cache-Control: no-cache
X-Request-Id: 9e4b9094f99f5877c8fb79647a1a22f1
X-Runtime: 0.004657
X-Rack-Cache: miss
Set-Cookie: _AppName_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWJhZGVlNzBjNjA0YmQ1MTgyYmI5OWMxZjcxOGIzZmZkBjsAVEkiDnJldHVybl90bwY7AEZJIj1odHRwOi8vZ2l2LWR1ZXJlbi51bmktbXVlbnN0ZXIuZGUvdXNlcnMvbWFydGluL3Bvc3RzL25ldwY7AEY%3D--32120aefd22c0748c352b016582451ede84225ca; path=/; HttpOnly
Location: localhost:3000/login
Status: 302
Vary: Accept-Encoding
Content-Type: text/html; charset=utf-8
What I also found out so far is, that the Set-Cookie uses Base64 encoding (This is the encoding for the resulting string literal from http basic auth)
Any help is appreciated!
Nevermind I added this method, which checks for username and password validity and writes the result in the header:
def auth
authenticate_or_request_with_http_basic do |username, password|
if user = User.find_by_login(username)
response.headers['Auth'] = 'Password incorrect'
if user.valid_password?(password)
response.headers['Auth'] = 'Credentials correct'
end
else
response.headers['Auth'] = 'Username incorrect'
false
end
end
end
In my mobile application I just send a request to username:password#mysite.com and read the http header. Not sure if this is an elegant method or if it is inteded, but it works for now.