Valid Google OAuth2 token unparseable? - oauth-2.0

I've got a valid OAuth2 token that Google accepts, but GoogleIdTokenVerifier cannot even parse it.
The token is ya29.1.AADtN_XcjzHgauKetBvrbgHImGFg1pjiHRQAKHyTglBDjEZsTPUMQJ5p-xAKtk955_4r6MdnTe3HZ08 (no worries, it's already expired).
It's obtained on Android using
accountManager.blockingGetAuthToken(account, "oauth2:https://www.googleapis.com/auth/userinfo.email", true);
When I call https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=...
I get sane result like
{
"issued_to": "34951113407.apps.googleusercontent.com",
"audience": "34951113407.apps.googleusercontent.com",
"scope": "https://www.googleapis.com/auth/userinfo.email",
"expires_in": 3175,
"email": "me#gmail.com",
"verified_email": true,
"access_type": "offline"
}
So it must be a valid token.
But when I call
new GoogleIdTokenVerifier(new UrlFetchTransport(), JacksonFactory.getDefaultInstance())
.verify(authToken)
It gives me
com.fasterxml.jackson.core.JsonParseException: Unexpected character ('É' (code 201)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
at [Source: java.io.ByteArrayInputStream#69886979; line: 1, column: 2]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1378)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:599)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportUnexpectedChar(ParserMinimalBase.java:520)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._handleUnexpectedValue(UTF8StreamJsonParser.java:2275)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser._nextTokenNotInObject(UTF8StreamJsonParser.java:788)
at com.fasterxml.jackson.core.json.UTF8StreamJsonParser.nextToken(UTF8StreamJsonParser.java:674)
at com.google.api.client.json.jackson2.JacksonParser.nextToken(JacksonParser.java:55)
at com.google.api.client.json.JsonParser.startParsing(JsonParser.java:213)
at com.google.api.client.json.JsonParser.parse(JsonParser.java:372)
at com.google.api.client.json.JsonParser.parse(JsonParser.java:328)
at com.google.api.client.json.JsonParser.parseAndClose(JsonParser.java:158)
at com.google.api.client.json.JsonParser.parseAndClose(JsonParser.java:140)
at com.google.api.client.json.JsonFactory.fromInputStream(JsonFactory.java:206)
at com.google.api.client.json.webtoken.JsonWebSignature$Parser.parse(JsonWebSignature.java:480)
at com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.parse(GoogleIdToken.java:57)
at com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier.verify(GoogleIdTokenVerifier.java:190)
By debugging JsonWebSignature it seems that token payload is just 1.
Android 4.4.2
com.google.http-client:google-http-client-jackson2:1.17.0-rc
com.fasterxml.jackson.core:jackson-core:2.3.0 (also tried included 2.1.3 from transient dependencies of google-http-client-jackson)
Also tried GsonFactory, exception is different, but also clearly cannot be parsed by JsonWebSignature.parse().
What I did wrong? Are there different tokens formats out there?

There are indeed different token formats out there.
The OAuth2 token you have there is the access_token -- it says that your software is authorized with the scope you requested, but it doesn't say anything about which user is actually making the request.
There is another type of token which the GoogleIdTokenVerifier expects to verify: an OpenID Connect id_token. A lot of services use that one, because it means some third party is authenticating that the traffic you're looking at came from that human (more or less!).
There's a little more background here, but the short version is that you should consider using GoogleAuthUtil#getToken(Context, String, String) -- it will return the id_token as a String -- or else consider exactly which scopes you need, and consider requesting the openid scope instead of the current oauth2:https://www.googleapis.com/auth/userinfo.email.
I know this answer is probably too late to help you :)

Related

JWT.decode is returning nil unless validation is set to false

I'm following the Auth0 blog for decoding a JWT and I'm trying to grab the user information from it.
Create a web token class
def self.verify(token)
decoded_token = JWT.decode(token, nil,
true, # Verify the signature of this token
algorithms: 'RS256',
iss: 'https://YOUR_DOMAIN/',
verify_iss: true,
aud: Rails.application.secrets.auth0_api_audience,
verify_aud: true) do |header|
jwks_hash[header['kid']]
end
end
But when I inspect decoded_token its nil.
Looking at the jwt gem, in decode I see:
# Set password to nil and validation to false otherwise this won't work
decoded_token = JWT.decode token, nil, false
So I changed true to false in the auth0 code and I can see the pertinent user data that I need.
(byebug) decoded_token
[{"email"=>"xxx#example.com", "iss"=>"https://example.com/", "sub"=>"auth0|123456", "aud"=>["https://example.com", "https://example.auth0.com/userinfo"], "iat"=>123456, "exp"=>123456, "azp"=>"123456abc", "scope"=>"openid profile email"}, {"alg"=>"RS256", "typ"=>"JWT", "kid"=>"abcdefg"}]
I don't fully understand the code, what is happening here. Seems like I would want to keep verification on right?
Your token is invalid. At the very least the issuer (iss) doesn't match (and you've specifically set verify_iss: true) and the expiration claim (exp) is (very far) in the past, which automatically invalidates a JWT.
If you give an invalid token to decode and specify that it should validate its input, it returns nil, per its documentation.
Seems like I would want to keep verification on right?
Yes. You certainly want to reject invalid tokens.
I awarded user229044 with the right answer because they were correct, the token was invalid, but wanted to add context for anyone who may run into something similar.
Check your env vars. The problem turned out to be the string interpolation in json_web_token.rb: iss: "https://#{Rails.application.credentials.auth0[:DOMAIN]}/", and in the domain in the env file also included the full domain (including https://), so JWT.decode was looking for https://https.//... and the issuer was invalid. This was hard to see because the env vars matched my frontend 100% so cross checking those didn't help.
I had littered my call stack with byebugs trying to figure this out which messed with the implicit returns in ruby. The decoded token should be available in your secured_controller.rb as it gets returned implicitly in authorization_service.rb and json_web_token.rb.

Ebay getOffer API token problems

I need help in understanding a problem of token when trying to get an offer's information using https://api.ebay.com/sell/inventory/v1/offer/OFFER_ID endpoint.
I've got 2 types of tokens:
User token and Access token.
Documentation clearly gives me the explanation that I must use the USER TOKEN.
I made it using Auth'n'Auth method, I agreed to give permission as a seller. Now I am trying to use this token like that:
public void getOffer(#RequestBody Map<String, String> args) throws IOException {
HttpClient client = HttpClientBuilder.create().build();
HttpGet httpGet = new HttpGet("https://api.ebay.com/sell/inventory/v1/offer/OFFER_ID");
httpGet.setHeader("Authorization", "Bearer " + args.get("token"));
httpGet.setHeader("Content-Language", "en-US");
HttpResponse response = client.execute(httpGet);
System.out.print(EntityUtils.toString(response.getEntity()));
}
It keeps returning me:
{
"errors" : [ {
"errorId" : 1001,
"domain" : "OAuth",
"category" : "REQUEST",
"message" : "Invalid access token",
"longMessage" : "Invalid access token. Check the value of the Authorization HTTP request header."
} ]
}
What can be wrong and what steps I missed?
Thank you in advance
Well it's giving you the exact error that is occurring:
"Invalid access token. Check the value of the Authorization HTTP request header."
That means you have an issue with this line of code...
httpGet.setHeader("Authorization", "Bearer " + args.get("token"));
Something is wrong with the Authorization header, so either the token is wrong, the value isn't correct, or it's just not receiving it. Is the word "Bearer" supposed to be send with the header? And does the value of the args.get("token") contain the correct value?
Another issue that happens a lot with REST services, sometimes you have to use an HTML entities function to encode the strings/ldata you are attaching to a web request. Because you have a space in the authorization, perhaps the space isn't interpreting correctly from eBay and you have to use HTML entities on the string. Something like this...
httpGet.setHeader("Authorization", HttpUtility.HtmlEncode("Bearer " + args.get("token"));
I made it using Auth'n'Auth method
There are two versions of an User Token. eBay provides an older style called "Auth 'n' Auth" and a newer OAuth version. The "Auth 'n' Auth" token does not work with the new RESTFul APIs. You need to generate an OAuth User Token following the process found in the documentation.

Django-allauth stores an empty token_secret with linkedin_oauth2

I'm trying the make further requests to linkedinAPI and to do so I need both token and token_secret.
I have several test accounts in linkedin, the login process success with all of them, however the token_secret stores (for all of them is empty).
Is that an error? I suspect so because using the pair token/token_secret in subsecuent oauth2 calls I get the following from linkedin
{ "errorCode": 0, "message": "[unauthorized]. The token used in the OAuth request is not valid. AQVvM2f2qefU3vULPS-R46DXN8Mnra9ImG14hzeTvMMcXvBVOEiUl4RTZCJrdFZoTfGGN1fFzLvxG-O_UWB8s8EDr35ZsgwW59y4KilndoEkr105Sg2GR90jmUxpqxU572IiARjN5gxAjfoWC4-_UupKlEtafQn23XQqvXeuLvE-FsPAaSA", "requestId": "VOAL1ULK4X", "status": 401, "timestamp": 1395348629428 }
Further details:
I check these tokens using the shell:
from allauth.socialaccount.models import SocialToken
map(lambda st: st.token_secret, SocialToken.objects.all())
And I get empty output:
[u'', u'', u'']
I found a solution myself so I'll explain it.
I'm not very into oauth2 so I don't know about the process neither if it was normal to have an empty secret_token. So I debugged a bit into the django-allauth code, and I saw that the requests they perform use only the token (no secret token)
Then I changed the library and started using the same they do: requests. And with the following simple script I can make any other request to the linkedin API:
def see_my_groups_json(request, url):
import requests
token = SocialToken.objects.get(account__user_id=request.user.pk)
resp = requests.get(url, params={'oauth2_access_token': token.token})
return resp.json()
You should check the SCOPE parameter for linkedin provider. For example, the next configuration requests permission for accessing user's email address, basic profile and to read and share updates on behalf of the user.
SOCIALACCOUNT_PROVIDERS = {
'linkedin_oauth2': {
'SCOPE': ['r_emailaddress', 'r_basicprofile', 'rw_nus'],
'PROFILE_FIELDS': ['id', 'first-name', 'last-name', 'email-address', 'picture-url', 'public-profile-url']
}
}
If after the token generation, we try to make an API call that requires some other privilege, we will get a 401 status code HTTP response.
django-allauth, by default, r_emailaddress scope or none at all, depending on whether or not SOCIALACCOUNT_QUERY_EMAIL is enabled.
Hope this helps you.

Best way to upload files to Box.com programmatically

I've read the whole Box.com developers api guide and spent hours on the web researching this particular question but I can't seem to find a definitive answer and I don't want to start creating a solution if I'm going down the wrong path. We have a production environment where as once we are finished working with files our production software system zips them up and saves them into a local server directory for archival purposes. This local path cannot be changed. My question is how can I programmatically upload these files to our Box.com account so we can archive these on the cloud? Everything I've read regarding this involves using OAuth2 to gain access to our account which I understand but it also requires the user to login. Since this is an internal process that is NOT exposed to outside users I want to be able to automate this otherwise it would not be feasable for us. I have no issues creating the programs to trigger everytime a new files gets saved all I need is to streamline the Box.com access.
I just went through the exact same set of questions and found out that currently you CANNOT bypass the OAuth process. However, their refresh token is now valid for 60 days which should make any custom setup a bit more sturdy. I still think, though, that having to use OAuth for an Enterprise setup is a very brittle implementation -- for the exact reason you stated: it's not feasible for some middleware application to have to rely on an OAuth authentication process.
My Solution:
Here's what I came up with. The following are the same steps as outlined in various box API docs and videos:
use this URL https://www.box.com/api/oauth2/authorize?response_type=code&client_id=[YOUR_CLIENT_ID]&state=[box-generated_state_security_token]
(go to https://developers.box.com/oauth/ to find the original one)
paste that URL into the browser and GO
authenticate and grant access
grab the resulting URL: http://0.0.0.0/?state=[box-generated_state_security_token]&code=[SOME_CODE]
and note the "code=" value.
open POSTMAN or Fiddler (or some other HTTP sniffer) and enter the following:
URL: https://www.box.com/api/oauth2/token
create URL encoded post data:
grant_type=authorization_code
client_id=[YOUR CLIENT ID]
client_secret=[YOUR CLIENT SECRET]
code= < enter the code from step 4 >
send the request and retrieve the resulting JSON data:
{
"access_token": "[YOUR SHINY NEW ACCESS TOKEN]",
"expires_in": 4255,
"restricted_to": [],
"refresh_token": "[YOUR HELPFUL REFRESH TOKEN]",
"token_type": "bearer"
}
In my application I save both auth token and refresh token in a format where I can easily go and replace them if something goes awry down the road. Then, I check my authentication each time I call into the API. If I get an authorization exception back I refresh my token programmatically, which you can do! Using the BoxApi.V2 .NET SDK this happens like so:
var authenticator = new TokenProvider(_clientId, _clientSecret);
// calling the 'RefreshAccessToken' method in the SDK
var newAuthToken = authenticator.RefreshAccessToken([YOUR EXISTING REFRESH TOKEN]);
// write the new token back to my data store.
Save(newAuthToken);
Hope this helped!
If I understand correctly you want the entire process to be automated so it would not require a user login (i.e run a script and the file is uploaded).
Well, it is possible. I am a rookie developer so excuse me if I'm not using the correct terms.
Anyway, this can be accomplished by using cURL.
First you need to define some variables, your user credentials (username and password), your client id and client secret given by Box (found in your app), your redirect URI and state (used for extra safety if I understand correctly).
The oAuth2.0 is a 4 step authentication process and you're going to need to go through each step individually.
The first step would be setting a curl instance:
curl_setopt_array($curl, array(
CURLOPT_URL => "https://app.box.com/api/oauth2/authorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "content-type: application/x-www-form-urlencoded",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS =>
"response_type=code&client_id=".$CLIENT_ID."&state=".$STATE,
));
This will return an html text with a request token, you will need it for the next step so I would save the entire output to a variable and grep the tag with the request token (the tag has a "name" = "request_token" and a "value" which is the actual token).
Next step you will need to send another curl request to the same url, this time the post fields should include the request token, user name and password as follows:
CURLOPT_POSTFIELDS => "response_type=code&client_id=".$CLIENT_ID."&state=".$STATE."&request_token=".$REQ_TOKEN."&login=".$USER_LOGIN."&password=".$PASSWORD
At this point you should also set a cookie file:
CURLOPT_COOKIEFILE => $COOKIE, (where $COOKIE is the path to the cookie file)
This will return another html text output, use the same method to grep the token which has the name "ic".
For the next step you're going to need to send a post request to the same url. It should include the postfields:
response_type=code&client_id=".$CLIENT_ID."&state=".$STATE."&redirect_uri=".$REDIRECT_URI."&doconsent=doconsent&scope=root_readwrite&ic=".$IC
Be sure to set the curl request to use the cookie file you set earlier like this:
CURLOPT_COOKIEFILE => $COOKIE,
and include the header in the request:
CURLOPT_HEADER => true,
At step (if done by browser) you will be redirected to a URL which looks as described above:
http://0.0.0.0(*redirect uri*)/?state=[box-generated_state_security_token]&code=[SOME_CODE] and note the "code=" value.
Grab the value of "code".
Final step!
send a new cur request to https//app.box.com/api/oauth2/token
This should include fields:
CURLOPT_POSTFIELDS => "grant_type=authorization_code&code=".$CODE."&client_id=".$CLIENT_ID."&client_secret=".$CLIENT_SECRET,
This will return a string containing "access token", "Expiration" and "Refresh token".
These are the tokens needed for the upload.
read about the use of them here:
https://box-content.readme.io/reference#upload-a-file
Hope this is somewhat helpful.
P.S,
I separated the https on purpuse (Stackoverflow wont let me post an answer with more than 1 url :D)
this is for PHP cURL. It is also possible to do the same using Bash cURL.
For anyone looking into this recently, the best way to do this is to create a Limited Access App in Box.
This will let you create an access token which you can use for server to server communication. It's simple to then upload a file (example in NodeJS):
import box from "box-node-sdk";
import fs from "fs";
(async function (){
const client = box.getBasicClient(YOUR_ACCESS_TOKEN);
await client.files.uploadFile(BOX_FOLDER_ID, FILE_NAME, fs.createReadStream(LOCAL_FILE_PATH));
})();
Have you thought about creating a box 'integration' user for this particular purpose. It seems like uploads have to be made with a Box account. It sounds like you are trying to do an anonymous upload. I think box, like most services, including stackoverflow don't want anonymous uploads.
You could create a system user. Go do the Oauth2 dance and store just the refresh token somewhere safe. Then as the first step of your script waking up go use the refresh token and store the new refresh token. Then upload all your files.

what is id_token google oauth

I just got the following result when I tried to do oauth2 to googleapi. Only one thing: I couldn't find what is id_token used for in documentation.
{
"access_token": "xxxx",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "veryverylongstring",
"refresh_token": "abcdefg"
}
id_token is a JSON Web Token (JWT). If you decode it, you'll see it contains multiple assertions, including the ID of the user. See this answer for more details.
The id_token is used in OpenID Connect protocol, where the user is authenticated as well as authorized. (There's an important distinction
between authentication and authorization.) You will get id_token and access_token.
The id_token value contains the information about the user's authentication. The ID token resembles the concept of an identity card, in a standard JWT format, signed by the OpenID Provider (OIDP). To obtain one, the client needs to send the user to their OIDP with an authentication request.
Features of the ID token:
Asserts the identity of the user, called subject in OpenID (sub).
Specifies the issuing authority (iss).
Is generated for a particular audience, i.e. client (aud).
May contain a nonce (nonce).
May specify when (auth_time) and how, in terms of strength (acr), the user was authenticated.
Has an issue (iat) and expiration time (exp).
May include additional requested details about the subject, such as name and email address.
Is digitally signed, so it can be verified by the intended
recipients. May optionally be encrypted for confidentiality.
The ID token statements, or claims, are packaged in a simple JSON object:
{
"sub" : "alice",
"iss" : "https://openid.c2id.com",
"aud" : "client-12345",
"nonce" : "n-0S6_WzA2Mj",
"auth_time" : 1311280969,
"acr" : "c2id.loa.hisec",
"iat" : 1311280970,
"exp" : 1311281970
}

Resources