Handling Facebook API Permission skip / revoked per OAuth 2.0 RFC - facebook-graph-api

Assume my application wishes to request permissions A and B on behalf of a user from a third party.
Per the OAuth 2.0 RFC, the third party need not grant those permissions. However, if the permissions returned are not equivalent to what my application requested for, the scopes that were granted would be returned in the third party response.
From reading the Facebook Documentation and examining responses back from Facebook when I simulate the skip / deny flow, it appears that granted scopes are not returned and that my application would need to make a follow up api call to see what permissions were granted.
Is this correct?

Short answer: yes, you do.
Longer answer:
Make an API call to https://graph.facebook.com/me/permissions?access_token=OAUTH_ACCESS_TOKEN_HERE - in the PHP or JS SDK you can simplify this as just /me/permissions as the SDK will wrap the server and access token for you.
This shows all scopes currently granted to your application by that user, example output is:
{
"data": [
{
"installed": 1,
"manage_friendlists": 1,
"status_update": 1,
"photo_upload": 1,
"video_upload": 1,
"create_event": 1,
"create_note": 1,
"share_item": 1,
"publish_stream": 1,
"publish_actions": 1,
"user_about_me": 1,
"friends_activities": 1
}
]
}
Because users can retroactively remove previously-granted permissions, you'll need to do this from time to time anyway, because even if you had a callback with the scopes granted in a particular permissions request, the user could revoke some/all of them almost immediately afterwards.

Related

Revoking 1 token revokes all tokens of same OAuth Client ID + User Consent Pair

I'm experiencing a situation where:
I have a Google project, using an OAuth 2.0 Client (for web applications), to get consent for some scopes from users. The authorization parameters used in the redirect to Google uses the following values for the parameters (only including the possibly relevant params):
access_type - offline
response_type - code
include_granted_scopes - true
If a user consents access to the app, the app gets an access token to access the scopes granted.
If the same user consents again (while the previous access token hasn't been revoked yet) to the same app (using the same OAuth 2.0 Client), a new access token gets issued to the project.
The Google project has 2 different tokens now, and both access tokens work for accessing the scopes granted. Oddly though, the user would see only a single entry for the Google project/app in the users Apps with access to your account page.
The issue is that if any of the token gets revoked, all of the active tokens get revoked (attempts to use the access token results in an invalid_grant error, with the Token has been expired or revoked. error description). While I haven't tested beyond having 2 live/valid tokens at the same time, I suspect the behavior would be the same for more than 2.
I've been looking through the Google OAuth 2.0 docs to find some documentation regarding this behavior, or find anything referencing what happens to companion tokens when one gets revoked, but was unable to find anything explaining this behavior.
I, at first, thought that it was maybe due to my usage of incremental authorization, and thought that maybe revoking the latest access token revokes all preceding ones, but after experimenting with include_granted_scopes=false, the behavior was still the same.
For now, I've restricted users to be able to consent only once (unless a token expires), but I'm curious about the explanation for this behavior - where revoking 1 token revokes all of them.
Actually access tokens are independent. An access token by design will work for one hour. In theory even if the user revokes your access the access token will still work for the remainder of the hour it was originally valid for. This is standard Oauth2 functionality they are intended to give access for an hour that is why it is called a bearer token the bearer of that token is granted access for an hour.
What i suspect that you are seeing is the refresh token being revoked as this will cause a invalid_grant error. If you request access of the user using offline access you are granted a refresh token. If you request consent of the user again you get another refresh token. There can be up to fifty outstanding refresh tokens for a single user.
If the user revokes the access via their google account, or if your application revokes the access. Then yes all of the outstanding refresh tokens will be revoked. As your applications access to the users account has been revoked not the single refresh token. Note there are actually serval reasons why a refresh token can expire they can be found here refresh token experation
This is standard Oauth2 behavior not google specific.
Remove third-party account access
If you gave Google Account access to a third-party app or service you no longer trust or want to use, you can remove its access to your Google Account. The app or service won’t be able to access any more info from your Google Account, but you may need to request that they delete the data they already have.

How to check for Cognito permissions in API Gateway

Trying to understand how to use Cognito and API Gateway to secure an API.
Here is what I understand so far from AWS documentation and the Cognito user interface:
Clients
www-public - public facing website
www-admin - administrators website
Resource Servers
Prices - for this simple example the API will provide secured access to this resource.
Scopes
prices.read
prices.write
Again, very simple permissions on the API. Public www users can read prices, administrators can write them.
API Gateway
GET /prices - accessible to authenticated users that can read prices.
POST /prices - only accessible to administrators
Users
Administrators - can update prices via the POST method.
Non-administrators - cannot update prices.
Based on this...
Each client will request the scopes it is interested in. So for the public www site it will request prices.read and for the administration site both prices.read and prices.write.
The API Gateway will use two Cognito Authorisers, one for each HTTP Verb. So the GET method must check the user can read prices and the POST method that they can write prices.
The bit I don't see is how to put all of this together. I can make the clients request scopes but how do they now connect to user permissions?
When the token is generated, where is the functionality that says "Ok, you requested these scopes, now I'm going to check if this user has this permission and give you the right token?"
I understand that scopes ultimately related to the claims that will be returned in the token.For example, requesting the profile scope means that the token will contain certain claims e.g. email, surname etc.
I think based on this that my permissions will ultimately end up being claims that are returned when specific scopes are asked for. The fact that the two clients differ in what they request means that the prices write claim an never be returned to the public www client. It would never issue a token if the prices.write claim was requested.
What I can't see is where this fits in Cognito. There is the option to put users into groups but that is pretty much it. Likewise, there is nothing (that I could see) to relate scopes to claims.
I'm coming from a .Net and Identity Server background. Certainly in the last version of Identity Server I looked at there was a handler method where you would work out which claims to put into a token. I guess this would map into one of the custom handler lambda functions in Cognito. From there this would need to query Cognito and work out what claims to issue?
The final piece of the puzzle is how the API Gateway checks the claims. Can this be done in API Gateway or does the token need to be inspected in the Lambda function I will write to handle the API Gateway request?
Certainly using Identity Server and .Net there was a client library you would use in the API to inspect the claims and redact permissions accordingly. Guessing there is something similar in a Node JS Lambda function?
A few assumptions there as I'm basically in the dark. I think the basics are there but not sure how to connect everything together.
Hoping someone has figured this out.

How to block Facebook webhook calls for app specific users? [duplicate]

There is documentation for test users in the Facebook Developer online documentation but how do you delete actual users where the application doesn't show in their app list anymore? This is with the knowledge of the access_token and facebook_user_id.
Used to delete Test Users:
https://graph.facebook.com/893450345999?method=delete&access_token=A2ADI1YMySweBABBGrWPNwKMlubZA5ZCrQbxwhtlEd9FIQUrOVjsGD3mnIWEbUhzDz7dkuBekMFdHvjvJ9CZAU7EMSSaZBsgN60FkMCi3AAZDZD
Running the test user link produces the following error:
"error": {
"message": "(#100) Can only call this method on valid test users for your app",
"type": "OAuthException",
"code": 100
}
You seek for application de-authorization:
You can de-authorize an application or revoke a specific extended permissions on behalf of a user by issuing an HTTP DELETE request to PROFILE_ID/permissions with a user access_token for that app.
permission - The permission you wish to revoke. If you don't specify a permission then this will de-authorize the application completely.
To achieve this issue request to:
https://graph.facebook.com/me/permissions?method=delete&access_token=...
Once application de-authorized it will not appear in the list of user's applications.
Update December 2021
Follow the reference for Requesting & Revoking Permissions:
To remove single permission issue a DELETE request to /{user-id}/permissions/{permission-name} passing user access token or an app access token
To de-authorize an app completely issue similar request to the /{user-id}/permissions endpoint
Real users 'delete' themselves from your app when they remove your app from their account, you don't have to do anything.
If you would like to know when users de-authorize your app like this, you can specify a Deauthorize Callback URL in your app's settings. As described in the docs at https://developers.facebook.com/docs/authentication/:
Upon app removal we will send an HTTP POST request containing a single parameter, signed_request, which, once decoded, will yield a JSON object containing the user_id of the user who just deauthorized your app. You will not receive an user access token in this request and all existing user access tokens that were previously issued on behalf of that user will become invalid.
UPDATE: To remove your own app from the user's authorized applications, issue an HTTP DELETE to https://graph.facebook.com/[userid]/permissions?access_token=... as per https://developers.facebook.com/docs/reference/api/user/.
Typically Graph API calls also support doing an HTTP POST with an extra parameter, method=DELETE, in case DELETE calls are not possible/supported.
To do it:
You must have the user access token.
Visit https://developers.facebook.com/tools/debug/accesstoken/ and debug the user access token.
Copy App-Scoped User ID
Via API call HTTP DELETE to https://graph.facebook.com/[App-Scoped User ID]/permissions?method=delete&access_token=[YOUR-APP-ACCESS-TOKEN]

Use App Access Token for ads stats?

I need to access Facebook API to retrieve ads stats. All my requests will be handled on server side. I already know how to access those data by obtaining User Access Token with ads_read permission, where user has admin role in the ad account.
My application will use cron jobs to retrieve ads stats, so I would like to use App Access Token instead, because user won't need to authenticate with the dialog, right? I assigned application to the business, but it doesn't seem to work. After trying to get ads stats with App Access Token, I'm getting following error:
{
"error": {
"message": "The entity backed by id 1234567890123 cannot be seen
by the viewer with (ViewerID 0, AccountID 0):
DENY_RULE:InlinePrivacyPolicy:AlwaysDenyRule:4
(EntID: 1234567890123)",
"type": "OAuthException",
"code": 1
}
}
The application is assigned to the business, but I'm not sure if it's enough. I don't see any option to assign it to the ads account.
Am I doing it right or maybe there's another way to skip dialog part?
No, you need to use a user access token from a user who manages the ad account.
App access tokens are only useful for a narrow range of use cases, primarily for updating app settings or proving a call came from the app specifically, rather than an arbitrary user of the app

How can I verify a Google authentication API access token?

How can I verify a Google authentication access token?
I need to somehow query Google and ask: Is [given access token] valid for the [example#example.com] Google account?
Short version
It's clear how an access token supplied through the Google Authentication Api :: OAuth Authentication for Web Applications can be used to then request data from a range of Google services. It is not clear how to check if a given access token is valid for a given Google account. I'd like to know how.
Long version
I'm developing an API that uses token-based authentication. A token will be returned upon provision of a valid username+password or upon provision of a third-party token from any one of N verifiable services.
One of the third-party services will be Google, allowing a user to authenticate against my service using their Google account. This will later be extended to include Yahoo accounts, trusted OpenID providers and so on.
Schematic example of Google-based access:
The 'API' entity is under my full control. The 'public interface' entity is any web- or desktop-based app. Some public interfaces are under my control, others will not be and others still I may never even know about.
Therefore I cannot trust the token supplied to the API in step 3. This will be supplied along with the corresponding Google account email address.
I need to somehow query Google and ask: Is this access token valid for example#example.com?
In this case, example#example.com is the Google account unique identifier - the email address someone uses to log in to their Google account. This cannot be assumed to be a Gmail address - someone can have a Google account without having a Gmail account.
The Google documentation clearly states how, with an access token, data can be retrieved from a number of Google services. Nothing seems to state how you can check if a given access token is valid in the first place.
Update
The token is valid for N Google services. I can't try a token against a Google service as means of verifying it as I won't know which subset of all Google's services a given user actually uses.
Furthermore, I'll never be using the Google authentication access token to access any Google services, merely as a means of verifying a supposed Google user actually is who they say they are. If there is another way of doing this I'm happy to try.
For user check, just post
get the access token as accessToken and post it and get the response
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=accessToken
you can try in address bar in browsers too, use httppost and response in java also
response will be like
{
"issued_to": "xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"audience": "xxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com",
"user_id": "xxxxxxxxxxxxxxxxxxxxxxx",
"scope": "https://www.googleapis.com/auth/userinfo.profile https://gdata.youtube.com",
"expires_in": 3340,
"access_type": "offline"
}
The scope is the given permission of the accessToken. you can check the scope ids in this link
Update:
New API
post as below
https://oauth2.googleapis.com/tokeninfo?id_token=XYZ123
Response will be as
{
// These six fields are included in all Google ID Tokens.
"iss": "https://accounts.google.com",
"sub": "110169484474386276334",
"azp": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
"aud": "1008719970978-hb24n2dstb40o45d4feuo2ukqmcc6381.apps.googleusercontent.com",
"iat": "1433978353",
"exp": "1433981953",
// These seven fields are only included when the user has granted the "profile" and
// "email" OAuth scopes to the application.
"email": "testuser#gmail.com",
"email_verified": "true",
"name" : "Test User",
"picture": "https://lh4.googleusercontent.com/-kYgzyAWpZzJ/ABCDEFGHI/AAAJKLMNOP/tIXL9Ir44LE/s99-c/photo.jpg",
"given_name": "Test",
"family_name": "User",
"locale": "en"
}
For more info, https://developers.google.com/identity/sign-in/android/backend-auth
Ok, most answers are valid but not quite right. The idea of JWT is that you can validate the token without the need to contact the issuer everytime. You must check the id and verify the signature of the token with the known public key of the certificate google used to sign the token.
See the next post why and how to do this.
http://ncona.com/2015/02/consuming-a-google-id-token-from-a-server/
you can verify a Google authentication access token by using this endpoint:
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=<access_token>
This is Google V3 OAuth AccessToken validating endpoint, you can refer from google document below: (In OAUTH 2.0 ENDPOINTS Tab)
https://developers.google.com/identity/protocols/OAuth2UserAgent#validate-access-token
function authenticate_google_OAuthtoken($user_id)
{
$access_token = google_get_user_token($user_id); // get existing token from DB
$redirecturl = $Google_Permissions->redirecturl;
$client_id = $Google_Permissions->client_id;
$client_secret = $Google_Permissions->client_secret;
$redirect_uri = $Google_Permissions->redirect_uri;
$max_results = $Google_Permissions->max_results;
$url = 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token='.$access_token;
$response_contacts = curl_get_responce_contents($url);
$response = (json_decode($response_contacts));
if(isset($response->issued_to))
{
return true;
}
else if(isset($response->error))
{
return false;
}
}
Use the below endpoint to get user info such as name, email, photo etc.
https://www.googleapis.com/oauth2/v3/userinfo?access_token=<access token>
Use the below endpoint to get token info, such as expiry time, token scope etc.
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=<access token>
Google oauth code flow response in addition to access_token also returns id_token that contains useful for validation info in encrypted form.
One thing that makes ID tokens useful is that fact that you can pass
them around different components of your app. These components can use
an ID token as a lightweight authentication mechanism authenticating
the app and the user. But before you can use the information in the ID
token or rely on it as an assertion that the user has authenticated,
you must validate it.
Validation of an ID token requires several steps:
Verify that the ID token is a JWT which is properly signed with an appropriate Google public key.
Verify that the value of aud in the ID token is equal to your app’s client ID.
Verify that the value of iss in the ID token is equal to accounts.google.com or https://accounts.google.com.
Verify that the expiry time (exp) of the ID token has not passed.
If you passed a hd parameter in the request, verify that the ID token has a hd claim that matches your Google Apps hosted domain.
https://developers.google.com/identity/protocols/OpenIDConnect#validatinganidtoken link has code samples for validation of ID tokens.
See also https://security.stackexchange.com/questions/37818/why-use-openid-connect-instead-of-plain-oauth.
As per Google's documentation, you should use Google's AP Client Library that makes this (token verification, claim extraction etc.) much easier than writing your own custom code.
From a performance perspective, the token should be parsed locally without making a call to Google again. Off-course Google's public key is needed and retrieval of that key is done using a caching strategy, implemented in the Google's client library from #1 above.
FYI only. Google also uses a JWT token. See image below for reference.
Here's an example using Guzzle:
/**
* #param string $accessToken JSON-encoded access token as returned by \Google_Client->getAccessToken() or raw access token
* #return array|false False if token is invalid or array in the form
*
* array (
* 'issued_to' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
* 'audience' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
* 'scope' => 'https://www.googleapis.com/auth/calendar',
* 'expires_in' => 3350,
* 'access_type' => 'offline',
* )
*/
public static function tokenInfo($accessToken) {
if(!strlen($accessToken)) {
return false;
}
if($accessToken[0] === '{') {
$accessToken = json_decode($accessToken)->access_token;
}
$guzzle = new \GuzzleHttp\Client();
try {
$resp = $guzzle->get('https://www.googleapis.com/oauth2/v1/tokeninfo', [
'query' => ['access_token' => $accessToken],
]);
} catch(ClientException $ex) {
return false;
}
return $resp->json();
}
I need to somehow query Google and ask: Is this access token valid for example#example.com?
No. All you need is request standard login with Federated Login for Google Account Users from your API domain. And only after that you could compare "persistent user ID" with one you have from 'public interface'.
The value of realm is used on the Google Federated Login page to identify the requesting site to the user. It is also used to determine the value of the persistent user ID returned by Google.
So you need be from same domain as 'public interface'.
And do not forget that user needs to be sure that your API could be trusted ;) So Google will ask user if it allows you to check for his identity.
Try making an OAuth-authenticated request using your token to https://www.google.com/accounts/AuthSubTokenInfo. This is only documented to work for AuthSub, but it works for OAuth too. It won't tell you which user the token is for, but it will tell you which services it's valid for, and the request will fail if the token is invalid or has been revoked.
An arbitrary OAuth access token can't be used for authentication, because the meaning of the token is outside of the OAuth Core spec. It could be intended for a single use or narrow expiration window, or it could provide access which the user doesn't want to give. It's also opaque, and the OAuth consumer which obtained it might never have seen any type of user identifier.
An OAuth service provider and one or more consumers could easily use OAuth to provide a verifiable authentication token, and there are proposals and ideas to do this out there, but an arbitrary service provider speaking only OAuth Core can't provide this without other co-ordination with a consumer. The Google-specific AuthSubTokenInfo REST method, along with the user's identifier, is close, but it isn't suitable, either, since it could invalidate the token, or the token could be expired.
If your Google ID is an OpenId identifier, and your 'public interface' is either a web app or can call up the user's browser, then you should probably use Google's OpenID OP.
OpenID consists of just sending the user to the OP and getting a signed assertion back. The interaction is solely for the benefit of the RP. There is no long-lived token or other user-specific handle which could be used to indicate that a RP has successfully authenticated a user with an OP.
One way to verify a previous authentication against an OpenID identifier is to just perform authentication again, assuming the same user-agent is being used. The OP should be able to return a positive assertion without user interaction (by verifying a cookie or client cert, for example). The OP is free to require another user interaction, and probably will if the authentication request is coming from another domain (my OP gives me the option to re-authenticate this particular RP without interacting in the future). And in Google's case, the UI that the user went through to get the OAuth token might not use the same session identifier, so the user will have to re-authenticate. But in any case, you'll be able to assert the identity.
Check below URL. It works well. Its official document from Google itself.
Using one of the Google API Client Libraries (e.g. Java, Node.js, PHP, Python) is the recommended way to validate Google ID tokens.
https://developers.google.com/identity/sign-in/android/backend-auth#using-a-google-api-client-library