Google+ API error - Invalid Credentials - coldfusion
I am signing users in using their Google+ account. I sign them in and grab basic information and store it in the database. In this process, I store the access_token in the session and move on.
However, today I am trying to write a script that allows me to post to their 'moments' on Google+ using their in session access_token.
I am getting an error and the response looks like:
{ "error": { "errors": [ { "domain": "global", "reason": "authError", "message": "Invalid Credentials", "locationType": "header", "location": "Authorization" } ], "code": 401, "message": "Invalid Credentials" } }
I am not sure why this is happening, below is the code I am using to make the request (it is in ColdFusion script, but you should be able to see the principles behind it even if you do not know this syntax).
local.http = new http();
local.http.setMethod("post");
local.http.setCharset("utf-8");
local.http.setUseragent(cgi.http_user_agent);
local.http.setResolveurl(true);
local.http.setTimeout(20);
local.http.setUrl("https://www.googleapis.com/plus/v1/people/" & session.user.sourceid & "/moments/vault");
local.target = {};
local.target["kind"] = "plus##moment";
local.target["type"] = "http://schemas.google.com/AddActivity";
local.target["description"] = params.pin["description"];
local.target["image"] = session.user.image;
local.target["name"] = params.pin["title"];
local.target["url"] = URLfor(route="pinShow", key=obfuscateParam(pin.id), onlyPath=false);
local.target["latitude"] = session.user.latitude;
local.target["longitude"] = session.user.longitude;
local.http.addParam(type="formField", name="target", value=serialize(local.target));
local.http.addParam(type="formField", name="kind", value="plus##moment");
local.http.addParam(type="formField", name="type", value="http://schemas.google.com/AddActivity");
local.http.addParam(type="formField", name="access_token", value=session.access_token);
local.result = local.http.send().getPrefix();
As you can see, it all seems straight-forward.
I have test this straight-after signing in and despite that, it says the token is invalid in the header response:
HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer realm="https://www.google.com/accounts/AuthSubRequest", error=invalid_token Content-Type: application/json; charset=UTF-8 Content-Encoding: gzip Date: Wed, 11 Sep 2013 13:12:28 GMT Expires: Wed, 11 Sep 2013 13:12:28 GMT Cache-Control: private, max-age=0 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Content-Length: 162 Server: GSE Alternate-Protocol: 443:quic
Does anyone have any idea why this is happening and how to solve it?
I am not using any kind of library, as there isn't one for ColdFusion. In addition, I didn't want to, as my needs are only very basic and I wanted to see how it all worked.
Am I missing something obvious here?
Any help would be greatly appreciated as it's driving me nuts!
Thanks,
Mikey.
PS - I have removed the app from my account, cleared all cookies and sessions and then signed in again granting all permissions, so it seems that side of it has been eliminated.
UPDATE 1:
After some light shone from other users here, it turns out I should be posting a JSON response in the HTTP body, to make the request. So I changed my code to this:
local.request = {}
local.request["kind"] = "plus##moment";
local.request["type"] = "http://schemas.google.com/AddActivity";
local.request["target"] = {};
local.request.target["kind"] = "plus##itemScope";
local.request.target["type"] = "http://schemas.google.com/AddActivity";
local.request.target["description"] = params.pin["description"];
local.request.target["image"] = session.user.image;
local.request.target["name"] = params.pin["title"];
local.request.target["url"] = URLfor(route="pinShow", key=obfuscateParam(pin.id), onlyPath=false);
local.request.target["latitude"] = session.user.latitude;
local.request.target["longitude"] = session.user.longitude;
local.http = new http();
local.http.setMethod("post");
local.http.setCharset("utf-8");
local.http.setUseragent(cgi.http_user_agent);
local.http.setResolveurl(true);
local.http.setTimeout(20);
local.http.setUrl("https://www.googleapis.com/plus/v1/people/" & session.user.sourceid & "/moments/vault?debug=true&fields=kind%2Ctype%2Cdescription%2Cimage%2Curl&key={GOOLE_API_KEY}" );
local.http.addParam(type="header", name="Content-Type", value="application/json");
local.http.addParam(type="header", name="Authorization", value="Authorization: Bearer " & session.access_token);
local.http.addParam(type="body", value=serializeJSON(local.request));
local.result = local.http.send().getPrefix();
However, now I get another error (401 unauthorized):
{ "error": { "errors": [ { "domain": "global", "reason": "required", "message": "Login Required", "locationType": "header", "location": "Authorization" } ], "code": 401, "message": "Login Required" } }
Does anybody know how I can pass the access_token using my new method above?
UPDATE 2
It has been highlighted to me that this could originate from my original OAuth 2 process. I have removed the app from my Google+ account and started the confirmation / signin process again. Here is the generated URL:
https://accounts.google.com/ServiceLogin?service=lso&passive=1209600&continue=https://accounts.google.com/o/oauth2/auth?response_type%3Dcode%26scope%3Dhttps://www.googleapis.com/auth/userinfo.email%2Bhttps://www.googleapis.com/auth/userinfo.profile%2Bhttps://www.googleapis.com/auth/plus.login%26redirect_uri%3Dhttp://{MY_DOMAIN}.com/oauth/google/?do%253Dredirect%26state%3D2BFFBC14-29F9-4488-ABBF661C0E9E53DB%26client_id%3D{MY_CLIENT_ID}%26hl%3Den-GB%26from_login%3D1%26as%3D-593afcc82466f5f<mpl=popup&shdf=CnALEhF0aGlyZFBhcnR5TG9nb1VybBoADAsSFXRoaXJkUGFydHlEaXNwbGF5TmFtZRoIVW5pYmFuZHMMCxIGZG9tYWluGghVbmliYW5kcwwLEhV0aGlyZFBhcnR5RGlzcGxheVR5cGUaB0RFRkFVTFQMEgNsc28iFOyetn24YRlbdWKLAKGXFCH5C1p9KAEyFPquOHBH18K6iV1GTAg_P9zB2x60&sarp=1&scc=1
Am I missing something here? Is it a scope that is missing that should allow me to post to their AddActivity stream?
UPDATE 3
My OAuth login URL (I've tried to split on to new lines to improve readability):
https://accounts.google.com/o/oauth2/auth? scope=
https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+
https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+
https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fplus.login&
request_visible_actions=
https%3A%2F%2Fschemas.google.com%2FAddActivity& state=
65B4A4D1-0C49-4C65-9B46163E67D88EAD& redirect_uri=
http%3A%2F%2FXXXXXXX.com%2Foauth%2Fgoogle%2F%3Fdo%3Dredirect&
response_type= code& client_id= XXXXXXXX.apps.googleusercontent.com
And on the permissions screen on Google+, this is what I see:
When I try to post an addActivity now, I get a bad request error as before.
The error JSON returned:
{ "error": { "errors": [ { "domain": "global", "reason": "invalid", "message": "Invalid Value" } ], "code": 400, "message": "Invalid Value" } }
The header returned:
HTTP/1.1 400 Bad Request Content-Type: application/json; charset=UTF-8 Content-Encoding: gzip Date: Fri, 13 Sep 2013 11:38:20 GMT Expires: Fri, 13 Sep 2013 11:38:20 GMT Cache-Control: private, max-age=0 X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Content-Length: 123 Server: GSE Alternate-Protocol: 443:quic
There's a definite problem with at least one header:
local.http.addParam(type="header", name="Authorization", value="Authorization: Bearer " & session.access_token);
You will have authorization in there twice, should be.
local.http.addParam(type="header", name="Authorization", value="Bearer " & session.access_token);
What's probably happening with the people.get call is it is picking up the API key and returning publicly rather than as the signed in user.
As Brett says though, to write an app activity you need to a &request_visible_actions=http://schemas.google.com/AddActivity to the auth URL you are sending users to in the first place (if not using the JS sign in button), in order to have the user approve you to write that kind of action (it's like another type of scope).
Although I'm not completely familiar with ColdFusion, it looks like you may be mixing two concepts in here that can't be mixed. The access_token is usually specified either in the HTTP header, or as a URL query parameter. It is not typically set as a POST parameter, and that may be part of the problem.
If done in a header, the HTTP header should be
Authorization: Bearer 1/fFBGRNJru1FQd44AzqT3Zg
and if done with the URL query parameter, your URL might need to look something like
https://www.googleapis.com/plus/v1/people/123456789012345/moments/vault?access_token=1/fFBGRNJru1FQd44AzqT3Zg
See https://developers.google.com/accounts/docs/OAuth2UserAgent#callinganapi for details
Not sure if this is related or not, although it might be, but I'm not sure if the body of your POST is correct either. You're not setting a content-type for the body, and I don't know if CF will use something like "application/www-form-url-encoded" or "multipart/form-data", neither of which is valid. The body should be "application/json".
Update
So given your changed code, you probably want the setUrl line to be something like
local.http.setUrl("https://www.googleapis.com/plus/v1/people/" & session.user.sourceid & "/moments/vault?access_token=" & session.access_token );
The URL I provided above gives the definitive documentation for OAuth2 at Google. I'm not sure what other parameters you were trying to add to the URL, or why, but if they are essential you can add them after the access token in this way.
How exactly are you getting the Access Token? Are you using OAuth?
Are you using new Access Token each time? Not the one stored once in the DB? You need to keep in mind that tokens can expire quite quickly.
This bit:
"reason": "authError", "message": "Invalid Credentials", "locationType": "header", "location": "Authorization"
may mean that your Authorization header is incorrect - not existing I suspect, as I can't see sending any signature in the bit of code you provided.
I haven't used the G+ API, but it seems you need to use proper OAuth 2.0 to get any data:
https://developers.google.com/+/api/latest/people/get#examples
There's a CF library for OAuth here http://oauth.riaforge.org/
UPDATE:
If you go here: https://developers.google.com/+/api/latest/moments/insert#try-it and try running the example (using "me") as userid you'll see that the request looks like this:
POST https://www.googleapis.com/plus/v1/people/me/moments/vault?debug=true&key={YOUR_API_KEY}
Content-Type: application/json
Authorization: Bearer ya29.XXXXXXXXXXXXXXXXXXXXXXXXXXXX
You need to provide the "Authorization" header.
Edited in response to update 2
This sounds like a problem with setting up the OAuth flow. You appear to be missing the Based on your OAuth generated URL, you are missing the request_visible_actions parameter either being missing or malformed.
Add the following query parameter to your OAuth URL and this should start working:
&request_visible_actions=http%3A%2F%2Fschemas.google.com%2FAddActivity
Another problem with your request is that you should remove the userinfo.profile scope from your request. That scope is not necessary because you have plus.login included and can result in some weird issues.
Lastly, ensure that your page at the target URL has the minimal level schema.org metadata that is required for the AddActivity moment type. This isn't your problem now, but could be the next problem you run into.
Related
PowerBI fetch data with GET request with body
I am trying to query an API where I have to pass username and password in the body. I have researched multiple methods but have not found any method which seems reasonable. My query looks something like this: URL: localhost:8080/v1/project HEADERS: Content-Type: application/json BODY: { "email": "test#test.com", "password": "123456" } In the docs I have found WebActions.Request, which is aparently disabled in most contexts (https://learn.microsoft.com/en-us/powerquery-m/webaction-request). Web.Contents only allows a body in POST requests (https://learn.microsoft.com/en-us/powerquery-m/web-contents). I'd rather not write a Python script to do it as it requires a Python installation. I tried this possible solution, but there was no success, as it would still perform a POST request: https://community.powerbi.com/t5/Power-Query/How-to-send-a-GET-method-with-content-data-with-M/m-p/943033/highlight/true#M32541 What reasonable way is there to achieve my goal to do a GET request with a body containing the credentials?
Getting Unable to determine service/operation name to be authorized - AccessDeniedException Error on CreateParticipantConnection
I am trying to test the Amazon Connect Chat API in POSTMAN, The first part StartChatContact tested/executed and i have got the ParticipantTokenback in response with ContactId & ParticipantId by using PUT: https://connect.us-east-1.amazonaws.com/contact/chat But i am unable to run the POST: https://connect.us-east-1.amazonaws.com/participant/connection. here is the Request Syntax AWS has given: POST /participant/connection HTTP/1.1 X-Amz-Bearer: ParticipantToken Content-type: application/json { "ConnectParticipant": boolean, "Type": [ "string" ] } i have tried the ParticipantToken as header parameter with name mentioned in documentation (X-Amz-Bearer: ParticipantToken) as well as used in Authorization as Type API Key & AWS Signature. But can't get rid of this error. In response Headers i got x-amzn-ErrorType: AccessDeniedException and in body { "message": "Unable to determine service/operation name to be authorized" } How to remove this error? any solution please.
InvalidParameterException Doing Refresh Token Operation
I'm facing one issue doing a refresh token post request for amazon cognito. But I already search for the error message and not found any solutions for it. POST / HTTP/1.1 Host: cognito-idp.us-east-1.amazonaws.com Content-Type: application/x-amz-json-1.1 X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth Host: cognito-idp.us-east-1.amazonaws.com BODY: { "ClienteId":"<userPoolClientId>", "AuthFlow":"REFRESH_TOKEN_AUTH", "AuthParameters":{ "REFRESH_TOKEN":"<refreshToken>" } } RESPONSE: { "__type": "InvalidParameterException", "message": "1 validation error detected: Value at 'clientId' failed to satisfy constraint: Member must not be null" }
The error message is suggesting that you have not provided a clientId. While I understand you think you have, if you look closely you in fact have not. There is a misspelling of the field name: ClienteId
After some time testing I solved, the problem has the AcessToken is required to do this request. Now the body for this request is: { "ClienteId":"<userPoolClientId>", "AuthFlow":"REFRESH_TOKEN_AUTH", "AuthParameters":{ "REFRESH_TOKEN":"<refreshToken>" } }
Google Photos REST API "pageSize" and "pageToken" parameters causing 400 Bad Request
I'm trying to get all media items in my Google Photos library and referred following documentation link. https://developers.google.com/photos/library/guides/list Documentation says client can request pages using pageSize and provided following example. GET https://photoslibrary.googleapis.com/v1/mediaItems Content-type: application/json Authorization: Bearer OAUTH2_TOKEN { "pageSize":"100", } i think the comma after 100 is a documentation error and i removed it from request, but whenever i add pageSize (or pageToken) parameter, server always return with 400 Bad Request <p>Your client has issued a malformed or illegal request.<ins>That’s all we know.</ins> Here are some example REST API calls i tried GET /v1/mediaItems HTTP/1.1 Host: photoslibrary.googleapis.com Content-Type: application/json Authorization: Bearer xxx { "pageSize":10 } GET /v1/mediaItems HTTP/1.1 Host: photoslibrary.googleapis.com Content-Type: application/json Authorization: Bearer xxx { "pageSize":"10" } GET /v1/mediaItems HTTP/1.1 Host: photoslibrary.googleapis.com Content-Type: application/json Authorization: Bearer xxx { "pageToken":"blha blha" } Please note that whenever i removed the json from request body, it start returning 200 OK with predefined pageSize. but i would like to control the pageSize and request next pages using pageToken. Thanks for any guidance on this matter.
I just started looking at this too. Those parameters shouldn't be passed in the body. They are query parameters. So something like this: https://photoslibrary.googleapis.com/v1/mediaItems?pageSize=100 That worked for me at least. Check out further documentation at https://developers.google.com/photos/library/reference/rest/v1/mediaItems/list to review information about the query parameters.
Cognito logout does not work as documented
I have a Cognito user pool configured with a SAML identity provider (ADFS) and I'm able to sign it as a federated user (AD) but sign out does not work. Following the documentation, I make a GET request to https://my-domain.auth.us-west-2.amazoncognito.com/logout?client_id=63...ng&logout_uri=http:%2F%2Fyahoo.com (using some public logout uri), from my client (an AngularJS 1.x app), and I get back a 302 with a Location header like https://my-domain.auth.us-west-2.amazoncognito.com/login?client_id=63...ng&logout_uri=http:%2F%2Fyahoo.com (In fact there I see 2 requests like the above). When I log back in (thru ADFS) it does not prompt for my AD credentials, i.e. seems that I'm not logged out. My user pool is configured as described here (see step 7), where the Enable IdP sign out flow is checked, which is supposed to log the user out from ADFS as well. Any suggestions? Thanks. General ------- Request URL: https://my-domain.auth.us-west-2.amazoncognito.com/logout?client_id=63...ng&logout_uri=http:%2F%2Fyahoo.com Request Method: GET Status Code: 302 Remote Address: 54.69.30.36:443 Referrer Policy: no-referrer-when-downgrade Response Headers ---------------- cache-control: private content-length: 0 date: Fri, 20 Apr 2018 21:31:12 GMT expires: Thu, 01 Jan 1970 00:00:00 UTC location: https://my-domain.auth.us-west-2.amazoncognito.com/login?client_id=63...ng&logout_uri=http:%2F%2Fyahoo.com server: Server set-cookie: XSRF-TOKEN=...; Path=/; Secure; HttpOnly set-cookie: XSRF-TOKEN=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; Secure; HttpOnly status: 302 strict-transport-security: max-age=31536000 ; includeSubDomains x-content-type-options: nosniff x-frame-options: DENY x-xss-protection: 1; mode=block Request Headers --------------- :authority: my-domain.auth.us-west-2.amazoncognito.com :method: GET :path: /logout?client_id=63...ng&logout_uri=http:%2F%2Fyahoo.com :scheme: https accept: application/json, text/plain, */* accept-encoding: gzip, deflate, br accept-language: en-US,en;q=0.9 authorization: Bearer eyJra... cache-control: no-cache origin: https://localhost:8443 pragma: no-cache referer: https://localhost:8443/logout user-agent: Mozilla/5.0...
This redirect happens whenever logout_uri parameter doesn't match exactly what's listed among Sign out URL(s) in AWS Cognito User Pools App client settings configuration. Cognito allows logout with either logout_uri or with the same arguments as login (i.e. redirect_uri and response_type) to log out and take the user back to the login screen. It seems that whenever logout_uri is invalid, it assume the re-login flow, does this redirect, and then reports an error about missing login arguments. As for SAML, I don't know, but guessing that it doesn't work because there was actually an error, just not properly reported.
The /logout endpoint signs the user out.It only supports HTTPS GET. It is working Sample Requests - Logout and Redirect Back to Client It clears out the existing session and redirects back to the client. Both parameters are required. GET https://<YOUR DOMAIN NAME>/logout? client_id=xxxxxxxxxxxx& logout_uri=com.myclientapp://myclient/logout Also make sure that Logout URL is same as SIGNOUT URL in AWS Cognito APP too. for more information, refer AWS LOGOUT Endpoint
Finally I was able to fix this issue. I found the actual cause of the issue from the event viewer of my windows server 2012 R2. It says the following details about the failed sign out flow. The SAML Single Logout request does not correspond to the logged-in session participant. Requestor: urn:amazon:cognito:sp:userpoolid Request name identifier: Format: urn:oasis:names:tc:SAML:2.0:nameid-format:persistent, NameQualifier: SPNameQualifier: , SPProvidedId: Logged-in session participants: Count: 1, [Issuer: urn:amazon:cognito:sp:userpoolid, NameID: (Format: , NameQualifier: SPNameQualifier: , SPProvidedId: )] User Action Verify that the claim provider trust or the relying party trust configuration is up to date. If the name identifier in the request is different from the name identifier in the session only by NameQualifier or SPNameQualifier, check and correct the name identifier policy issuance rule using the AD FS 2.0 Management snap-in. The Error clearly says that the name identifier in the request is different from the name identifier in the session only by NameQualifier. I have corrected this error in the claim issuance tab of relying party trusts by adding the rule as below. The below rule replace the user#myadfsdomain to simply user when issuing the claim. c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"] => issue(Type = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", Issuer = c.Issuer, OriginalIssuer = c.OriginalIssuer, Value = RegExReplace(c.Value, "(?i)^myadfsdomain*\\", ""), ValueType = c.ValueType, Properties["http://schemas.xmlsoap.org/ws/2005/05/identity/claimproperties/format"] = "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"); Besides this i have forgot to check in the enable signout flow in the cognito configuration which caused the problem. Logout started working seamlessly for me.
From documentation here https://docs.aws.amazon.com/cognito/latest/developerguide/logout-endpoint.html If you want And your login url is like "https://xxxx.auth.eu-west-1.amazoncognito.com/login?client_id=1234&response_type=token&scope=aws.cognito.signin.user.admin+email+openid+phone+profile&redirect_uri=http://localhost:3000/" Then your logout url is like "https://xxxx.auth.eu-west-1.amazoncognito.com/logout?client_id=1234&logout_uri=http://localhost:3000/"; Note the difference login > login? and redirect_uri logout > logout? and logout_uri Those redirect / logout uri must match what you have configured inside Cognito. If you don't do it right, you may get strange errors like error=unauthorized_client or Required String parameter 'response_type' is not present and who knows what else. :o)
My solution below is based on - https://github.com/nextauthjs/next-auth/discussions/3938#discussioncomment-2165155 I am able to solve the issue with few changes- in signout button upon clicking i will call handleSignOut function handleSignOut() { const clientId = 'paste your AWS COGNITO CLIENT ID'; const domain = 'paste your AWS COGNITO DOMAIN'; window.location.href = `${domain}/logout?client_id=${clientId}&logout_uri=http://localhost:3000/logout` } In Aws cognito-> app integration -> app client settings -> signout url keep the following url- http://localhost:3000/logout in pages create a seperate page called logout and keep following code- import { useEffect } from "react" import { signOut } from 'next-auth/react' export default function SignoutPage() { useEffect(() => { setTimeout(() => { signOut({ callbackUrl: window.location.origin }); }, 1000); }, []); return ( <> <div className="loader"> Loading.. </div> <style jsx>{` .loader{ position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); transform: -webkit-translate(-50%, -50%); transform: -moz-translate(-50%, -50%); transform: -ms-translate(-50%, -50%); } `}</style> </> ) } click signout it will log you out
#RequestMapping(value = "/logout", method = RequestMethod.GET) public void logout(HttpServletRequest request, HttpServletResponse response) { LOGGER.info("Logout controller"); try { Cookie awsCookie = new Cookie("AWSELBAuthSessionCookie-0", "deleted"); awsCookie.setMaxAge(-1); awsCookie.setPath("/"); response.addCookie(awsCookie); Properties appProperties = new Properties(); applicationPropertyConfigurer.loadProperties(appProperties); response.sendRedirect(appProperties.getProperty("logout.url")); response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", -1); request.getSession().invalidate(); } catch (IOException e) { LOGGER.error("Exception in redirecting to logout.url URL", e); } } //https:/domain_name.auth.us-west-x.amazoncognito.com/logout?response_type=code&client_id=&redirect_uri=redirect_uri_should_be_present_in_cognito_user_pool_Callback URL&state=STATE&scope=openid