Not receiving Google OAuth refresh token - gdata

I want to get the access token from Google. The Google API says that to get the access token, send the code and other parameters to token generating page, and the response will be a JSON Object like :
{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}
However, I'm not receiving the refresh token. The response in my case is:
{
"access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}

The refresh_token is only provided on the first authorization from the user. Subsequent authorizations, such as the kind you make while testing an OAuth2 integration, will not return the refresh_token again. :)
Go to the page showing Apps with access to your account:
https://myaccount.google.com/u/0/permissions.
Under the Third-party apps menu, choose your app.
Click Remove access and then click Ok to confirm
The next OAuth2 request you make will return a refresh_token (providing that it also includes the 'access_type=offline' query parameter.
Alternatively, you can add the query parameters prompt=consent&access_type=offline to the OAuth redirect (see Google's OAuth 2.0 for Web Server Applications page).
This will prompt the user to authorize the application again and will always return a refresh_token.

In order to get the refresh token you have to add both approval_prompt=force and access_type="offline"
If you are using the java client provided by Google it will look like this:
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
.build();
AuthorizationCodeRequestUrl authorizationUrl =
flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
.setApprovalPrompt("force")
.setAccessType("offline");

I'd like to add a bit more info on this subject for those frustrated souls who encounter this issue. The key to getting a refresh token for an offline app is to make sure you are presenting the consent screen. The refresh_token is only returned immediately after a user grants authorization by clicking "Allow".
The issue came up for me (and I suspect many others) after I'd been doing some testing in a development environment and therefore already authorized my application on a given account. I then moved to production and attempted to authenticate again using an account which was already authorized. In this case, the consent screen will not come up again and the api will not return a new refresh token. To make this work, you must force the consent screen to appear again by either:
prompt=consent
or
approval_prompt=force
Either one will work but you should not use both. As of 2021, I'd recommend using prompt=consent since it replaces the older parameter approval_prompt and in some api versions, the latter was actually broken (https://github.com/googleapis/oauth2client/issues/453). Also, prompt is a space delimited list so you can set it as prompt=select_account%20consent if you want both.
Of course you also need:
access_type=offline
Additional reading:
Docs: https://developers.google.com/identity/protocols/oauth2/web-server#request-parameter-prompt
Docs: https://developers.google.com/identity/protocols/oauth2/openid-connect#re-consent
Discussion about this issue: https://github.com/googleapis/google-api-python-client/issues/213

I searched a long night and this is doing the trick:
Modified user-example.php from admin-sdk
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$authUrl = $client->createAuthUrl();
echo "<a class='login' href='" . $authUrl . "'>Connect Me!</a>";
then you get the code at the redirect url
and the authenticating with the code and getting the refresh token
$client()->authenticate($_GET['code']);
echo $client()->getRefreshToken();
You should store it now ;)
When your accesskey times out just do
$client->refreshToken($theRefreshTokenYouHadStored);

This has caused me some confusion so I thought I'd share what I've come to learn the hard way:
When you request access using the access_type=offline and approval_prompt=force parameters you should receive both an access token and a refresh token. The access token expires soon after you receive it and you will need to refresh it.
You correctly made the request to get a new access token and received the response that has your new access token. I was also confused by the fact that I didn't get a new refresh token. However, this is how it is meant to be since you can use the same refresh token over and over again.
I think some of the other answers assume that you wanted to get yourself a new refresh token for some reason and sugggested that you re-authorize the user but in actual fact, you don't need to since the refresh token you have will work until revoked by the user.

Rich Sutton's answer finally worked for me, after I realized that adding access_type=offline is done on the front end client's request for an authorization code, not the back end request that exchanges that code for an access_token. I've added a comment to his answer and this link at Google for more info about refreshing tokens.
P.S. If you are using Satellizer, here is how to add that option to the $authProvider.google in AngularJS.

In order to get the refresh_token you need to include access_type=offline in the OAuth request URL. When a user authenticates for the first time you will get back a non-nil refresh_token as well as an access_token that expires.
If you have a situation where a user might re-authenticate an account you already have an authentication token for (like #SsjCosty mentions above), you need to get back information from Google on which account the token is for. To do that, add profile to your scopes. Using the OAuth2 Ruby gem, your final request might look something like this:
client = OAuth2::Client.new(
ENV["GOOGLE_CLIENT_ID"],
ENV["GOOGLE_CLIENT_SECRET"],
authorize_url: "https://accounts.google.com/o/oauth2/auth",
token_url: "https://accounts.google.com/o/oauth2/token"
)
# Configure authorization url
client.authorize_url(
scope: "https://www.googleapis.com/auth/analytics.readonly profile",
redirect_uri: callback_url,
access_type: "offline",
prompt: "select_account"
)
Note the scope has two space-delimited entries, one for read-only access to Google Analytics, and the other is just profile, which is an OpenID Connect standard.
This will result in Google providing an additional attribute called id_token in the get_token response. To get information out of the id_token, check out this page in the Google docs. There are a handful of Google-provided libraries that will validate and “decode” this for you (I used the Ruby google-id-token gem). Once you get it parsed, the sub parameter is effectively the unique Google account ID.
Worth noting, if you change the scope, you'll get back a refresh token again for users that have already authenticated with the original scope. This is useful if, say, you have a bunch of users already and don't want to make them all un-auth the app in Google.
Oh, and one final note: you don't need prompt=select_account, but it's useful if you have a situation where your users might want to authenticate with more than one Google account (i.e., you're not using this for sign-in / authentication).

1. How to get 'refresh_token' ?
Solution: access_type='offline' option should be used when generating authURL.
source : Using OAuth 2.0 for Web Server Applications
2. But even with 'access_type=offline', I am not getting the 'refresh_token' ?
Solution: Please note that you will get it only on the first request, so if you are storing it somewhere and there is a provision to overwrite this in your code when getting new access_token after previous expires, then make sure not to overwrite this value.
From Google Auth Doc : (this value = access_type)
This value instructs the Google authorization server to return a
refresh token and an access token the first time that your application
exchanges an authorization code for tokens.
If you need 'refresh_token' again, then you need to remove access for your app as by following the steps written in Rich Sutton's answer.

I'm using nodejs client for access to private data
The solution was add the promp property with value consent to the settings object in oAuth2Client.generateAuthUrl function.
Here is my code:
const getNewToken = (oAuth2Client, callback) => {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: SCOPES,
})
console.log('Authorize this app by visiting this url:', authUrl)
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
rl.question('Enter the code from that page here: ', (code) => {
rl.close()
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error while trying to retrieve access token', err)
oAuth2Client.setCredentials(token)
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err)
console.log('Token stored to', TOKEN_PATH)
})
callback(oAuth2Client)
})
})
}
You can use the online parameters extractor to get the code for generate your token:
Online parameters extractor
Here is the complete code from google official docs:
https://developers.google.com/sheets/api/quickstart/nodejs
I hope the information is useful

Setting this will cause the refresh token to be sent every time:
$client->setApprovalPrompt('force');
an example is given below (php):
$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("email");
$client->addScope("profile");
$client->setAccessType('offline');
$client->setApprovalPrompt('force');

For me I was trying out CalendarSampleServlet provided by Google. After 1 hour the access_key times out and there is a redirect to a 401 page. I tried all the above options but they didn't work. Finally upon checking the source code for 'AbstractAuthorizationCodeServlet', I could see that redirection would be disabled if credentials are present, but ideally it should have checked for refresh token!=null. I added below code to CalendarSampleServlet and it worked after that. Great relief after so many hours of frustration . Thank God.
if (credential.getRefreshToken() == null) {
AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
authorizationUrl.setRedirectUri(getRedirectUri(req));
onAuthorization(req, resp, authorizationUrl);
credential = null;
}

Using offline access and prompt:consent worked well to me:
auth2 = gapi.auth2.init({
client_id: '{cliend_id}'
});
auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback);

In order to get new refresh_token each time on authentication the type of OAuth 2.0 credentials created in the dashboard should be "Other". Also as mentioned above the access_type='offline' option should be used when generating the authURL.
When using credentials with type "Web application" no combination of prompt/approval_prompt variables will work - you will still get the refresh_token only on the first request.

To get a refresh token using postman, here is an example of the configurations
Expected Response

now google had refused those parameters in my request (access_type, prompt)... :( and there is no "Revoke Access" button at all. I'm frustrating because of getting back my refresh_token lol
UPDATE:
I found the answer in here :D you can get back the refresh token by a request
https://developers.google.com/identity/protocols/OAuth2WebServer
curl -H "Content-type:application/x-www-form-urlencoded" \
https://accounts.google.com/o/oauth2/revoke?token={token}
The token can be an access token or a refresh token. If the token is an access token and it has a corresponding refresh token, the refresh token will also be revoked.
If the revocation is successfully processed, then the status code of the response is 200. For error conditions, a status code 400 is returned along with an error code.

#!/usr/bin/env perl
use strict;
use warnings;
use 5.010_000;
use utf8;
binmode STDOUT, ":encoding(utf8)";
use Text::CSV_XS;
use FindBin;
use lib $FindBin::Bin . '/../lib';
use Net::Google::Spreadsheets::V4;
use Net::Google::DataAPI::Auth::OAuth2;
use lib 'lib';
use Term::Prompt;
use Net::Google::DataAPI::Auth::OAuth2;
use Net::Google::Spreadsheets;
use Data::Printer ;
my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
client_id => $ENV{CLIENT_ID},
client_secret => $ENV{CLIENT_SECRET},
scope => ['https://www.googleapis.com/auth/spreadsheets'],
);
my $url = $oauth2->authorize_url();
# system("open '$url'");
print "go to the following url with your browser \n" ;
print "$url\n" ;
my $code = prompt('x', 'paste code: ', '', '');
my $objToken = $oauth2->get_access_token($code);
my $refresh_token = $objToken->refresh_token() ;
print "my refresh token is : \n" ;
# debug p($refresh_token ) ;
p ( $objToken ) ;
my $gs = Net::Google::Spreadsheets::V4->new(
client_id => $ENV{CLIENT_ID}
, client_secret => $ENV{CLIENT_SECRET}
, refresh_token => $refresh_token
, spreadsheet_id => '1hGNULaWpYwtnMDDPPkZT73zLGDUgv5blwJtK7hAiVIU'
);
my($content, $res);
my $title = 'My foobar sheet';
my $sheet = $gs->get_sheet(title => $title);
# create a sheet if does not exit
unless ($sheet) {
($content, $res) = $gs->request(
POST => ':batchUpdate',
{
requests => [
{
addSheet => {
properties => {
title => $title,
index => 0,
},
},
},
],
},
);
$sheet = $content->{replies}[0]{addSheet};
}
my $sheet_prop = $sheet->{properties};
# clear all cells
$gs->clear_sheet(sheet_id => $sheet_prop->{sheetId});
# import data
my #requests = ();
my $idx = 0;
my #rows = (
[qw(name age favorite)], # header
[qw(tarou 31 curry)],
[qw(jirou 18 gyoza)],
[qw(saburou 27 ramen)],
);
for my $row (#rows) {
push #requests, {
pasteData => {
coordinate => {
sheetId => $sheet_prop->{sheetId},
rowIndex => $idx++,
columnIndex => 0,
},
data => $gs->to_csv(#$row),
type => 'PASTE_NORMAL',
delimiter => ',',
},
};
}
# format a header row
push #requests, {
repeatCell => {
range => {
sheetId => $sheet_prop->{sheetId},
startRowIndex => 0,
endRowIndex => 1,
},
cell => {
userEnteredFormat => {
backgroundColor => {
red => 0.0,
green => 0.0,
blue => 0.0,
},
horizontalAlignment => 'CENTER',
textFormat => {
foregroundColor => {
red => 1.0,
green => 1.0,
blue => 1.0
},
bold => \1,
},
},
},
fields => 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)',
},
};
($content, $res) = $gs->request(
POST => ':batchUpdate',
{
requests => \#requests,
},
);
exit;
#Google Sheets API, v4
# Scopes
# https://www.googleapis.com/auth/drive View and manage the files in your Google D# # i# rive
# https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
# https://www.googleapis.com/auth/drive.readonly View the files in your Google Drive
# https://www.googleapis.com/auth/spreadsheets View and manage your spreadsheets in Google Drive
# https://www.googleapis.com/auth/spreadsheets.readonly View your Google Spreadsheets

My solution was a bit weird..i tried every solution i found on internet and nothing. Surprisely this worked: delete the credentials.json, refresh, vinculate your app in your account again. The new credentials.json file will have the refresh token. Backup this file somewhere.
Then keep using your app until the refresh token error comes again. Delete the crendetials.json file that now is only with an error message (this hapenned in my case), then paste you old credentials file in the folder, its done!
Its been 1 week since ive done this and had no more problems.

Adding access_type=offline to the authorisation Google authorisation URL did the trick for me. I am using Java and Spring framework.
This is the code that creates the client registration:
return CommonOAuth2Provider.GOOGLE
.getBuilder(client)
.scope("openid", "profile", "email", "https://www.googleapis.com/auth/gmail.send")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline")
.clientId(clientId)
.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
.clientSecret(clientSecret)
.build();
The important part here is the authorization URI, to which ?access_type=offline is appended.

Related

using postman to access firebase REST API

I'm trying to use postman to do REST API calls to firebase. I've managed to read from firebase when my security rule is to permit all users including unauthorized ones.
but when I use this rule :
{"rules":{".read": "auth != null", ".write": "auth != null"}}
I get 'error' : 'permission denied' from postman.
I did the request token for google's web oauth2.0 client and got the authorization_code token back.
I tried to use token in the URL and in the header, tried it with GET & POST request and still get denied.
please help.
Thanks in advance
The answers above did not work for me.
What did work for me was going to
Project Settings (top left corner gear) -> Service Accounts (far right tab) -> Database Secrets (left menu) -> Scroll down, hover over the bulltets and click Show
Use this as the auth key, i.e. .../mycollection.json?auth=HERE
For me it worked like this:
https://your-database-url/users.json?auth=YOUR_AUTH_KEY
Where can you get this AUTH_KEY?
you get this key from your Project Settings -> Database -> Secret Key
Try something like this
https://your-database-url/users.json?auth=YOUR_AUTH_KEY
Respone is a JSON of your USERS node
I created a Postman pre-request script for helping create a Authentication: Bearer JWT. Should save a lot of copy pasting when testing APIs with Firebase Auth. https://gist.github.com/moneal/af2d988a770c3957df11e3360af62635
Copy of script at time of posting:
/**
* This script expects the global variables 'refresh_token' and 'firebase_api_key' to be set. 'firebase_api_key' can be found
* in the Firebase console under project settings then 'Web API Key'.
* 'refresh_token' as to be gathered from watching the network requests to https://securetoken.googleapis.com/v1/token from
* your Firebase app, look for the formdata values
*
* If all the data is found it makes a request to get a new token and sets a 'auth_jwt' environment variable and updates the
* global 'refresh_token'.
*
* Requests that need authentication should have a header with a key of 'Authentication' and value of '{{auth_jwt}}'
*
* Currently the nested assertions silently fail, I don't know why.
*/
pm.expect(pm.globals.has('refresh_token')).to.be.true;
pm.expect(pm.globals.has('firebase_api_key')).to.be.true;
var sdk = require('postman-collection'),
tokenRequest = new sdk.Request({
url: 'https://securetoken.googleapis.com/v1/token',
method: 'POST',
body: {
mode: 'urlencoded',
urlencoded: [{
type: 'text',
key: 'key',
value: pm.globals.get('firebase_api_key')
},
{
type: 'text',
key: 'grant_type',
value: 'refresh_token'
},
{
type: 'text',
key: 'refresh_token',
value: pm.globals.get('refresh_token')
},
]
}
});
pm.sendRequest(tokenRequest, function(err, response) {
pm.test('request for access token was ok', function() {
pm.expect(response).to.be.ok();
});
const json = response.json();
pm.expect(json).to.an('object');
pm.test('response json has needed properties', function() {
pm.expect(json).to.have.own.property('access_token');
pm.expect(json).to.have.own.property('token_type');
pm.expect(json).to.have.own.property('refresh_token');
const accessToken = json.access_token;
const tokenType = json.token_type;
const refreshToken = json.refresh_token;
pm.environment.set('auth_jwt', tokenType + ' ' + accessToken);
pm.globals.set('refresh_token', refreshToken);
});
});
Note: Adding this answer as all Options listed here is either deprecated or not working(mostly due to missing steps).
Best way to make it work with Postman is to use Google OAuth2 access tokens. The provided link described in full length but I have added quick steps.
Step 1: Download Service-Accounts.json
Step 2: Generate Access token in Java (provided link described support in other language for this)
make sure to include this dependency:
implementation 'com.google.api-client:google-api-client:1.25.0'
OR
<dependency>
<groupId>com.google.api-client</groupId>
<artifactId>google-api-client</artifactId>
<version>1.25.0</version>
</dependency>
Run this code to generate token(Copied from google's javadocs)
// Load the service account key JSON file
FileInputStream serviceAccount = new FileInputStream("path/to/serviceAccountKey.json");
GoogleCredential scoped = GoogleCredential
.fromStream(serviceAccount)
.createScoped(
Arrays.asList(
"https://www.googleapis.com/auth/firebase.database",
"https://www.googleapis.com/auth/userinfo.email"
)
);
// Use the Google credential to generate an access token
scoped.refreshToken();
String token = scoped.getAccessToken();
System.out.println(token);
Step 3: Use the token in Postman
It is very simple to fetch data via Postman:
Here is how I did it
1 Your DB URL
https://YOUR_PROJECT_URL.firebaseio.com/YOUR_STRUCTURE/CLASS.json
2 Add API key in header as auth
auth = Value of your API_KEY
example:
We can use Firebase with postman for rest APIs
How we use Firebase in postman?
Copy your Firebase database URL
Paste it into your postman URL
Add .json at the last like shown in the image
Then play with your firebase database and make REST API

Net::Google::AuthSub login failed with new Google Drive version

This code in Perl was working for years and now my Spreadsheets logins failed, when I logged into my account I noticed switch to a new Drive version. Probably some authentication methods deprecated?
my $auth = Net::Google::AuthSub->new;
my $response = $auth->login('LOGIN#gmail.com', 'PASS');
if ($response->is_success) {
print "Hurrah! Logged in\n";
} else {
die "Login failed: ".$response->error."\n";
}
The result is:
Login failed:
And the code:
use Net::Google::Spreadsheets;
my $service = Net::Google::Spreadsheets->new(
username => 'LOGIN#gmail.com',
password => 'PASS'
);
The result is:
Net::Google::AuthSub login failed at /usr/local/share/perl/5.18.2/Net/Google/Spreadsheets.pm line 42.
As suggested somewhere I tried to skip SSL certificate checking with:
$ENV{PERL_LWP_SSL_VERIFY_HOSTNAME} = 0;
but this doesn't help too.
What can I do to make it work? Thanks.
I have to answer my question as I was happy to find a solution. Google changed their authentication algorithm, so we have to use OAuth 2.0.
You will need to create Credentials at: https://console.developers.google.com/
APIs & auth -> Credentials -> OAuth -> Client ID -> Installed application -> Other
and enable your API i.e.: APIs & auth -> APIs -> Google Apps APIs > Drive API
The following code works fine:
use Net::Google::DataAPI::Auth::OAuth2;
use Net::Google::Spreadsheets;
use Storable; #to save and restore token for future use
my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
client_id => 'ID.apps.googleusercontent.com',
client_secret => 'SECRET',
scope => ['http://spreadsheets.google.com/feeds/'],
);
#you can skip URL if you have your token saved and continue from RESTORE label
my $url = $oauth2->authorize_url();
#you will need to put code here and receive token
print "OAuth URL, get code: $url\n";
use Term::Prompt;
my $code = prompt('x', 'paste the code: ', '', '');
my $token = $oauth2->get_access_token($code) or die;
#save token for future use
my $session = $token->session_freeze;
store($session, 'google_spreadsheet.session');
RESTORE:
my $session = retrieve('google_spreadsheet.session');
my $restored_token = Net::OAuth2::AccessToken->session_thaw($session,
auto_refresh => 1,
profile => $oauth2->oauth2_webserver,
);
$oauth2->access_token($restored_token);
my $service = Net::Google::Spreadsheets->new(auth => $oauth2);
# and then as before..
Save and restore token session example found at https://gist.github.com/hexaddikt
Thanks for this - very helpful. One addition is that the code in the original poster's answer will generate a token that expires in an hour. If you want to keep using it, you need to be sure to formulate the initial request so that it generates not only the access token, but also the refresh token. To get this, change the initial
my $url = $oauth2->authorize_url();
to
my $url = $oauth2->authorize_url(access_type => 'offline',
approval_prompt => 'force');
Then the returned token will have the necessary refresh_token included.
That code above absolutely would not work for me. After looking around for several hours, reading the documentation and trying different things I finally ran across some code that worked. 90% of it is the same but it was the missing 10% and the accompanying explanations that made all the difference.
http://pastebin.com/8LeMyLW4
(Originally from this Stack Overflow post: Authenticating in a Google sheets application)
#!/usr/bin/perl
# Code to get a web-based token that can be stored
# and used later to authorize our spreadsheet access.
# Based on code from https://gist.github.com/hexaddikt/6738162
#-------------------------------------------------------------------
# To use this code:
# 1. Edit the lines below to put in your own
# client_id and client_secret from Google.
# 2. Run this script and follow the directions on
# the screen, which will give step you
# through the following steps:
# 3. Copy the URL printed out, and paste
# the URL in a browser to load the page.
# 4. On the resulting page, click OK (possibly
# after being asked to log in to your Google
# account).
# 5. You will be redirected to a page that provides
# a code that you should copy and paste back into the
# terminal window, so this script can exchange it for
# an access token from Google, and store the token.
# That will be the the token the other spreadsheet access
# code can use.
use Net::Google::DataAPI::Auth::OAuth2;
use Net::Google::Spreadsheets;
use Storable; #to save and restore token for future use
use Term::Prompt;
# Provide the filename in which we will store the access
# token. This file will also need to be readable by the
# other script that accesses the spreadsheet and parses
# the contents.
my $session_filename = "stored_google_access.session";
# Code for accessing your Google account. The required client_id
# and client_secret can be found in your Google Developer's console
# page, as described in the detailed instruction document. This
# block of code will also need to appear in the other script that
# accesses the spreadsheet.
# Be sure to edit the lines below to fill in your correct client
# id and client secret!
my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
client_id => 'your_client_id.apps.googleusercontent.com',
client_secret => 'your_client_secret',
scope => ['http://spreadsheets.google.com/feeds/'],
redirect_uri => 'https://developers.google.com/oauthplayground',
);
# We need to set these parameters this way in order to ensure
# that we get not only an access token, but also a refresh token
# that can be used to update it as needed.
my $url = $oauth2->authorize_url(access_type => 'offline',
approval_prompt => 'force');
# Give the user instructions on what to do:
print <<END
The following URL can be used to obtain an access token from
Google.
1. Copy the URL and paste it into a browser.
2. You may be asked to log into your Google account if you
were not logged in already in that browser. If so, go
ahead and log in to whatever account you want to have
access to the Google doc.
3. On the next page, click "Accept" when asked to grant access.
4. You will then be redirected to a page with a box in the
left-hand column labeled "Authorization code".
Copy the code in that box and come back here.
Here is the URL to paste in your browser to get the code:
$url
END
;
# Here is where we get the code from the user:
my $code = prompt('x', 'Paste the code obtained at the above URL here: ', '', '');
# Exchange the code for an access token:
my $token = $oauth2->get_access_token($code) or die;
# If we get to here, it worked! Report success:
print "nToken obtained successfully!n";
print "Here are the token contents (just FYI):nn";
print $token->to_string, "n";
# Save the token for future use:
my $session = $token->session_freeze;
store($session, $session_filename);
print <<END2
Token successfully stored in file $session_filename.
Use that filename in your spreadsheet-access script to
load the token as needed for access to the spreadsheet data.
END2
;
use Net::Google::Spreadsheets;
use Net::Google::DataAPI::Auth::OAuth2;
use Net::OAuth2::AccessToken;
use Storable;
# Authentication code based on example from gist at
# https://gist.github.com/hexaddikt/6738247
# Get the token that we saved previously in order to authenticate:
my $session_filename = "stored_google_access.session";
# Be sure to edit the lines below to fill in your correct client
# id and client secret!
my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
client_id => 'your_client_id.apps.googleusercontent.com',
client_secret => 'your_client_secret',
scope => ['http://spreadsheets.google.com/feeds/'],
redirect_uri => 'https://developers.google.com/oauthplayground',
);
# Deserialize the file so we can thaw the session and reuse the refresh token
my $session = retrieve($sessionfile);
my $restored_token = Net::OAuth2::AccessToken->session_thaw($session,
auto_refresh => 1,
profile => $oauth2->oauth2_webserver,
);
$oauth2->access_token($restored_token);
# Now we can use this token to access the spreadsheets
# in our account:
my $service = Net::Google::Spreadsheets->new(
auth => $oauth2);

Facebook access tokens format

I am manually building a login system to my application using facebook. Following this tutorial I am told to do some get requests to obtain user information. Echoing an returned token I obtain this
(using FILE_GET_CONTENTS() ):
$access_token = "graph.facebook.com/oauth/access_token?client_id=$client_id&redirect_uri=$redirect_uri&client_secret=$client_secret&code=$user_code") :
echoing $access_token:
access_token = CAAKlMMCiHp(...)ZByh3faLzh &expires=5125282
or this for an app_token ("graph.facebook.com/oauth/access_token?client_id=$client_id&client_secret=$client_secret&grant_type=client_credentials") :
echoing $app_token:
access_token = 744(...)90|vZU2(...)e0-s
The last one to obtain the user information that I have to do is this:
$last_one = "graph.facebook.com/debug_token?access_token=$access_token&app_token=$app_token";
Check above that I have to use $access_token and app_token. The problem is that when I echo $access_token what I obtain is literally access_token = xxxx..., including the "access_token" and the "=" sign instead of just the token.
So, when I try to do my last request ($last_one) I have (with variable replacement):
$last_one = "graph.facebook.com/debug_token?access_token=access_token=xxx&app_token=app_token=xxxx";
To avoid this I removed the first "access_token=" and "app_token=" and successfully eliminated the duplicate. Unfortunately I now have access_token=xxx&access_token=xxx because although one is literally for access and another is to the app both are treated as access_token and their echo also explicits "access_token =" as shown above.
My question is if this is supposed to happen and how can I get only the token? I find it kind of ridiculous to have the need of using substr() just to eliminate the first part of the token "access_token =". Am I supposed to reach the token by a getter part of file_get_contents()? And should I include &expires=xxxx of the first token in the last request? Please refer and shortly read the tutorial from "Confirming identities" to understand what is my dilema. I hope I made myself through, thank you very much!

Unable to get a long term access token using facebook graph api

I'm new to integrating facebook into the websites I'm working on and trying to get a long term access token by following the instructions here: https://developers.facebook.com/docs/facebook-login/access-tokens/
Even when using the Graph API Explorer here: https://developers.facebook.com/tools/explorer/
I enter the following and populate it with my AppID and AppSecret and current token I get when I press Get Access Token...
GET /oauth/access_token?
grant_type=fb_exchange_token&
client_id={app-id}&
client_secret={app-secret}&
fb_exchange_token={short-lived-token}
I get the return
{ "error": "Invalid response" }
Can someone elaborate on what I might be doing wrong, or the steps in greater detail that works for you in acquiring this long term token.
I've tried to follow what's happening in this thread Facebook Page Access Tokens - Do these expire? with no more success. Any help would be greatly appreciated.
Thanks for your time and help.
Cheers,
-Ryan
You can't get the long-lived user token using the Graph API Explorer. You have to make a GET request to:
https://graph.facebook.com/oauth/access_token?
grant_type=fb_exchange_token&
client_id={app-id}& client_secret={app-secret}& fb_exchange_token={short-lived-token}
You can check it in the browser.
If you need the page access token, you can have a never expiring token. Check out the accepted answer here: What are the Steps to getting a Long Lasting Token For Posting To a Facebook Fan Page from a Server
So I thought I'd revisit this and provide the documentation I wrote that will hopefully help someone else get this happening!
ONE. Create Application
Create an application associated with the user of the page you want to have access to.
TWO. Get Required Pieces of Info
After creating an App we should have two key pieces of info:
App ID: AAAAA (should be about ~15 characters long)
App Secret: BBBBB (should be about ~32 characters long)
With these by going to https://developers.facebook.com/tools/explorer
Making sure to select the correct Application from the Dropdown Box at the top.
Click on Get Access Token and get a ‘fresh’ token.
Here you'll need to select appropriate permissions for your specific app's purpose.
CCCCC (should be ~200 characters long)
THREE. Get Long Life Token (2 Month)
You should then have the pieces of info needed to run the query to get a long-term (2 month) token:
https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id={app-id}& client_secret={app-secret}& fb_exchange_token={short-lived-token}
Replace {app-id}, {app-secret} and {short-lived-token} with the three bits of info you’ve taken note of so far.
You should get a request like the following:
https://graph.facebook.com/oauth/access_token?%20grant_type=fb_exchange_token&%20client_id=AAAAA&%20client_secret=BBBBB&%20fb_exchange_token=CCCCC
Place this query into the url bar of an internet browser. You should get a response in the window that looks something like the following:
access_token=DDDDD&expires=5184000
DDDDD (should be ~200 characters long)
FOUR. Test Token (Part 1)
If you enter the highlighted part into the input on the following debug site:
https://developers.facebook.com/tools/debug/
It should give you an expiry of approximately 2 months.
FIVE. Get Non Expiring Page Token
Now taking note of this new long-live-token we’ll use this to get a token that doesn’t expire, unless the associated application is removed from a user’s access or deleted.
We use either the page name or preferably page-id when making the request:
You can get your facebook page id using something like http://findmyfacebookid.com/
We'll refer to your page id as EEEEE
https://graph.facebook.com/{page-id}/?fields=access_token&access_token={long-live-token}
You should get a request like the following:
https://graph.facebook.com/EEEEE/?fields=access_token&access_token=DDDDD
This will return something like the following:
{
"access_token": "FFFFF",
"id": "131062838468"
}
FFFFF (should be ~200 characters long)
SIX. Test Token (Part 2)
Take the highlighted part and enter it into the debug page and you should get something that shows the token never expires and you’ve been successful in acquiring your never expiring page token.
SEVEN. High Five!
Sorry for the long list of how to achieve this, but I find it better to give the whole process instead of just a small snippet. Let me know if you find this helpful or you have a better way of achieving any of the steps.
Facebook PHP SDK has already implemented method to get long-lived token using short-lived token, after login successfully and got the short-lived token, simple calling
$result = $facebook->setExtendedAccessToken();
if $result is null, it means you got the long-lived access token.
client side
if (response.status === 'connected') {
{
event.preventDefault();
FB.login(function (response) {
if (response.authResponse) {
var profileId = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
var e = response.authResponse.accessToken;
document.getElementById('token').innerHTML = e;
var profileName = "";
var pagesList = "";
var isPage = 0;
var type = "fb";
$.ajax({
url: "WebService1.asmx/getlonToken",
type: "POST",
dataType: 'json',
data: '{accessToken:"' + accessToken + '"}',
contentType: "application/json; charset=utf-8",
async: true,
success: function (response) {
accessToken = response.d;
document.getElementById('status').innerHTML = accessToken;
},
error: function (e) {
alert('Error' + e);
}
});
}
}, { scope: 'user_about_me,friends_about_me,user_activities,friends_activities,user_birthday,friends_birthday,user_education_history,friends_education_history,user_events,friends_events,user_groups,friends_groups,user_hometown,friends_hometown,user_interests,friends_interests,user_likes,friends_likes,user_location,friends_location,user_notes,friends_notes,user_photos,friends_photos,user_relationships,friends_relationships,user_relationship_details,friends_relationship_details,user_status,friends_status,user_videos,friends_videos,user_website,friends_website,email,manage_pages,publish_stream,read_stream,read_page_mailboxes,read_insights, read_mailbox' });
}
Server side
[WebMethod]
public string getlonToken(string accessToken)
{
var fb = new FacebookClient(accessToken);
dynamic result = fb.Get("oauth/access_token", new
{
client_id = 123,
client_secret = "123fff45",
grant_type = "fb_exchange_token",
fb_exchange_token = accessToken,
scope = "user_about_me,friends_about_me,user_activities,friends_activities,user_birthday,friends_birthday,user_checkins,friends_checkins,user_education_history,friends_education_history,user_events,friends_events,user_groups,friends_groups,user_hometown,friends_hometown,user_interests,friends_interests,user_likes,friends_likes,user_location,friends_location,user_notes,friends_notes,user_photos,friends_photos,user_relationships,friends_relationships,user_relationship_details,friends_relationship_details,user_religion_politics,friends_religion_politics,user_status,friends_status,user_videos,friends_videos,user_website,friends_website,email,manage_pages,publish_stream,read_stream,read_page_mailboxes,read_insights,ads_management"
});
fb.AccessToken = (string)result["access_token"];
return fb.AccessToken;
}
}
You can generate Short-Lived Token from Graph API Explorer.
Then you can extend it in Long-Lived Token from Access Token Debugger.
I do this every time.

Can the server get a long-lived access token with the code in `signedRequest`?

Is the code property of authResponse.signedRequest (in the Facebook JavaScript API) useful? I'm generating one like this:
FB.login({ scope: "email" }, function(r) {
console.log([
function(d){ return d.split('.')[1]; },
function(d){ return atob(d.replace('-', '+').replace('_', '/')); },
JSON.parse,
function(d){ return d.code; }
].reduce(
function(acc, f) { return f(acc); },
r.authResponse.signedRequest
));
});
The docs say this:
code: an OAuth Code which can be exchanged for a valid user access token via a subsequent server-side request
…but that link redirects to Facebook Login home page. I found the /oauth/access_token endpoint documented here, but it requires a redirect_uri parameter, and there isn't one in this case.
As CBroe suggested, the code in this response appears to have already been used by the JavaScript SDK to create a short-lived token.
I know it's a bit of a necro-bump, but for anyone else who stumbles upon this...
You can POST the code to:
https://graph.facebook.com/oauth/access_token
In the form body, you want:
client_id: your_app_client_id
client_secret: you_app_client_secret
grant_type: "authorization_code"
redirect_uri: ""
code: code_from_signed_request
The blank redirect_uri is the key. You must supply a blank string ... omitting the field doesn't work.
Since you need to supply your client secret, you don't want to do this client-side. Send the code to some method on your server to do it, and return the access code to your client.