I've followed the guide on https://github.com/gsuitedevs/apps-script-oauth2 for setting up the Oauth2 token for a google services very closely but i'm still not able to get the access token to work.
The error I'm receiving is Access not granted or expired. (line 454, file "Service", project "OAuth2")
Note* My project has already been whitelisted for the GMB API library and I have enabled it in the API console console. I retrieved a ClientID and ClientSecret from my project aswell.
//Oauth 2 Flow Start
function getGmbService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth2.createService('gmb')
// Set the endpoint URLs, which are the same for all Google services.
.setAuthorizationBaseUrl('https://accounts.google.com/o/oauth2/auth')
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the client ID and secret, from the Google Developers Console.
.setClientId('...')
.setClientSecret('...')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties())
.setCache(CacheService.getUserCache())
// Set the scopes to request (space-separated for Google services).
.setScope('https://www.googleapis.com/auth/business.manage')
// Below are Google-specific OAuth2 parameters.
// Sets the login hint, which will prevent the account chooser screen
// from being shown to users logged in with multiple accounts.
.setParam('login_hint', Session.getActiveUser().getEmail())
// Requests offline access.
.setParam('access_type', 'offline')
// Forces the approval prompt every time. This is useful for testing,
// but not desirable in a production application.
.setParam('approval_prompt', 'force');
}
function showSidebar() {
var gmbService = getGmbService();
if (!gmbService.hasAccess()) {
var authorizationUrl = gmbService.getAuthorizationUrl();
var template = HtmlService.createTemplate(
'Authorize. ' +
'Reopen the sidebar when the authorization is complete.');
template.authorizationUrl = authorizationUrl;
var page = template.evaluate();
DocumentApp.getUi().showSidebar(page);
} else {
Logger.log("No Access")
}
}
function authCallback(request) {
var gmbService = getGmbService();
var isAuthorized = gmbService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this tab.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this tab');
}
}
//Oauth2 Flow Finish
function testRequest() {
var gmbService = getGmbService();
var payload = {
"pageSize": 5
}
var options = {
"headers": {
"Authorization": 'Bearer ' + gmbService.getAccessToken()
},
"method": 'GET',
"payload": payload,
"muteHttpExceptions": true
};
var response = UrlFetchApp.fetch("https://mybusiness.googleapis.com/v4/accounts",options)
Logger.log(response);
}
Related
I have a pure Javascript app which attempts to get an access token from Azure using OAuth Authorization Flow with PKCE.
The app is not hosted in Azure. I only use Azure as an OAuth Authorization Server.
//Based on: https://developer.okta.com/blog/2019/05/01/is-the-oauth-implicit-flow-dead
var config = {
client_id: "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx",
redirect_uri: "http://localhost:8080/",
authorization_endpoint: "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize",
token_endpoint: "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token",
requested_scopes: "openid api://{tenant-id}/user_impersonation"
};
// PKCE HELPER FUNCTIONS
// Generate a secure random string using the browser crypto functions
function generateRandomString() {
var array = new Uint32Array(28);
window.crypto.getRandomValues(array);
return Array.from(array, dec => ('0' + dec.toString(16)).substr(-2)).join('');
}
// Calculate the SHA256 hash of the input text.
// Returns a promise that resolves to an ArrayBuffer
function sha256(plain) {
const encoder = new TextEncoder();
const data = encoder.encode(plain);
return window.crypto.subtle.digest('SHA-256', data);
}
// Base64-urlencodes the input string
function base64urlencode(str) {
// Convert the ArrayBuffer to string using Uint8 array to convert to what btoa accepts.
// btoa accepts chars only within ascii 0-255 and base64 encodes them.
// Then convert the base64 encoded to base64url encoded
// (replace + with -, replace / with _, trim trailing =)
return btoa(String.fromCharCode.apply(null, new Uint8Array(str)))
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
// Return the base64-urlencoded sha256 hash for the PKCE challenge
async function pkceChallengeFromVerifier(v) {
const hashed = await sha256(v);
return base64urlencode(hashed);
}
// Parse a query string into an object
function parseQueryString(string) {
if (string == "") { return {}; }
var segments = string.split("&").map(s => s.split("="));
var queryString = {};
segments.forEach(s => queryString[s[0]] = s[1]);
return queryString;
}
// Make a POST request and parse the response as JSON
function sendPostRequest(url, params, success, error) {
var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.onload = function () {
var body = {};
try {
body = JSON.parse(request.response);
} catch (e) { }
if (request.status == 200) {
success(request, body);
} else {
error(request, body);
}
}
request.onerror = function () {
error(request, {});
}
var body = Object.keys(params).map(key => key + '=' + params[key]).join('&');
request.send(body);
}
function component() {
const element = document.createElement('div');
const btn = document.createElement('button');
element.innerHTML = 'Hello'+ 'webpack';
element.classList.add('hello');
return element;
}
(async function () {
document.body.appendChild(component());
const isAuthenticating = JSON.parse(window.localStorage.getItem('IsAuthenticating'));
console.log('init -> isAuthenticating', isAuthenticating);
if (!isAuthenticating) {
window.localStorage.setItem('IsAuthenticating', JSON.stringify(true));
// Create and store a random "state" value
var state = generateRandomString();
localStorage.setItem("pkce_state", state);
// Create and store a new PKCE code_verifier (the plaintext random secret)
var code_verifier = generateRandomString();
localStorage.setItem("pkce_code_verifier", code_verifier);
// Hash and base64-urlencode the secret to use as the challenge
var code_challenge = await pkceChallengeFromVerifier(code_verifier);
// Build the authorization URL
var url = config.authorization_endpoint
+ "?response_type=code"
+ "&client_id=" + encodeURIComponent(config.client_id)
+ "&state=" + encodeURIComponent(state)
+ "&scope=" + encodeURIComponent(config.requested_scopes)
+ "&redirect_uri=" + encodeURIComponent(config.redirect_uri)
+ "&code_challenge=" + encodeURIComponent(code_challenge)
+ "&code_challenge_method=S256"
;
// Redirect to the authorization server
window.location = url;
} else {
// Handle the redirect back from the authorization server and
// get an access token from the token endpoint
var q = parseQueryString(window.location.search.substring(1));
console.log('queryString', q);
// Check if the server returned an error string
if (q.error) {
alert("Error returned from authorization server: " + q.error);
document.getElementById("error_details").innerText = q.error + "\n\n" + q.error_description;
document.getElementById("error").classList = "";
}
// If the server returned an authorization code, attempt to exchange it for an access token
if (q.code) {
// Verify state matches what we set at the beginning
if (localStorage.getItem("pkce_state") != q.state) {
alert("Invalid state");
} else {
// Exchange the authorization code for an access token
// !!!!!!! This POST fails because of CORS policy.
sendPostRequest(config.token_endpoint, {
grant_type: "authorization_code",
code: q.code,
client_id: config.client_id,
redirect_uri: config.redirect_uri,
code_verifier: localStorage.getItem("pkce_code_verifier")
}, function (request, body) {
// Initialize your application now that you have an access token.
// Here we just display it in the browser.
document.getElementById("access_token").innerText = body.access_token;
document.getElementById("start").classList = "hidden";
document.getElementById("token").classList = "";
// Replace the history entry to remove the auth code from the browser address bar
window.history.replaceState({}, null, "/");
}, function (request, error) {
// This could be an error response from the OAuth server, or an error because the
// request failed such as if the OAuth server doesn't allow CORS requests
document.getElementById("error_details").innerText = error.error + "\n\n" + error.error_description;
document.getElementById("error").classList = "";
});
}
// Clean these up since we don't need them anymore
localStorage.removeItem("pkce_state");
localStorage.removeItem("pkce_code_verifier");
}
}
}());
In Azure I only have an App registration (not an app service).
Azure App Registration
The first step to get the authorization code works.
But the POST to get the access token fails. (picture from here)
OAuth Authorization Code Flow with PKCE
Access to XMLHttpRequest at
'https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token' from
origin 'http://localhost:8080' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource.
Where in Azure do I configure the CORS policy for an App Registration?
Okay, after days of banging my head against the stupidity of Azure's implementation I stumbled upon a little hidden nugget of information here: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-browser#prerequisites
If you change the type of the redirectUri in the manifest from 'Web' to 'Spa' it gives me back an access token! We're in business!
It breaks the UI in Azure, but so be it.
You should define the internal url with your local host address.
https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/application-proxy-understand-cors-issues
When I first posted, the Azure AD token endpoint did not allow CORS requests from browsers to the token endpoint, but it does now. Some Azure AD peculiarities around scopes and token validation are explained in these posts and code in case useful:
Code Sample
Blog Post
I am using php. and want to use some javascript api function in my application. so can any one know that how can I auto authenticate this process:
<!--Add a button for the user to click to initiate auth sequence -->
<button id="authorize-button" style="visibility: hidden">Authorize</button>
<script type="text/javascript">
var clientId = '837050751313';
var apiKey = 'AIzaSyAdjHPT5Pb7Nu56WJ_nlrMGOAgUAtKjiPM';
var scopes = 'https://www.googleapis.com/auth/plus.me';
function handleClientLoad() {
// Step 2: Reference the API key
gapi.client.setApiKey(apiKey);
window.setTimeout(checkAuth,1);
}
function checkAuth() {
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: true}, handleAuthResult);
}
function handleAuthResult(authResult) {
var authorizeButton = document.getElementById('authorize-button');
if (authResult && !authResult.error) {
authorizeButton.style.visibility = 'hidden';
makeApiCall();
} else {
authorizeButton.style.visibility = '';
authorizeButton.onclick = handleAuthClick;
}
}
function handleAuthClick(event) {
// Step 3: get authorization to use private data
gapi.auth.authorize({client_id: clientId, scope: scopes, immediate: false}, handleAuthResult);
return false;
}
// Load the API and make an API call. Display the results on the screen.
function makeApiCall() {
// Step 4: Load the Google+ API
gapi.client.load('plus', 'v1').then(function() {
// Step 5: Assemble the API request
var request = gapi.client.plus.people.get({
'userId': 'me'
});
// Step 6: Execute the API request
request.then(function(resp) {
var heading = document.createElement('h4');
var image = document.createElement('img');
image.src = resp.result.image.url;
heading.appendChild(image);
heading.appendChild(document.createTextNode(resp.result.displayName));
document.getElementById('content').appendChild(heading);
}, function(reason) {
console.log('Error: ' + reason.result.error.message);
});
});
}
</script>
// Step 1: Load JavaScript client library
<script src="https://apis.google.com/js/client.js?onload=handleClientLoad"></script>
I have .json and also .p12 file for authenticate but I only want to direct access to api function instead of redirecting to google for permission access.
When using the Google+ API or other API that accesses user-specific data (e.g. Gmail, Calendar), you need to either use an OAuth client and ask the user for permission to access their data (i.e. the "redirect to google for permission access"), or use a service account.
A service account is a robot account that is used in place of a real user. If you decide to use a service account, you'll need to use the JSON or P12 key as you mentioned. Using a service account from JavaScript code requires a lot of work and is generally insecure since you would be making your service account key accessible to everyone. If you're going to use a service account, you should call the API from the server using a Google API Client Library.
I'm pretty new to coding. I'm using Google App Script, which is supposed to be javascript based and a library to manage OAuth1 api authentication. I'm trying to authenticate with the Khan Academy. This script which I got from the google apps site works to a point. The function 'listTweets' takes me to the 'else' branch and logs the url to take me to Khan Academy to grant the script permission to make the call. I accept and am supposed to rerun the function and end up in the 'then' branch of the 'if-then-else' statement. I just keep getting sent down the else. Does anyone know what gives? Thanks in advance for any help.
var CONSUMER_KEY = 'my key';
var CONSUMER_SECRET = 'my secret';
var PROJECT_KEY = 'my google project key';
function listTweets() {
var service = getTwitterService();
if (service.hasAccess()) {
var response = service.fetch('https://www.khanacademy.org//api/v1/user/exercises');
var tweets = JSON.parse(response.getContentText());
Logger.log(tweets);
} else {
var authorizationUrl = service.authorize();
Logger.log('Please visit the following URL and then re-run the script: ' + authorizationUrl);
}
}
function getTwitterService() {
var service = OAuth1.createService('twitter');
service.setAccessTokenUrl('https://www.khanacademy.org/api/auth2/access_token')
service.setRequestTokenUrl('https://www.khanacademy.org/api/auth2/request_token')
service.setAuthorizationUrl('https://www.khanacademy.org/api/auth2/authorize')
service.setConsumerKey(CONSUMER_KEY);
service.setConsumerSecret(CONSUMER_SECRET);
service.setProjectKey(PROJECT_KEY);
service.setCallbackFunction('authCallback');
service.setPropertyStore(PropertiesService.getScriptProperties());
service.setOAuthVersion('1.0');
return service;
}
function authCallback(request) {
var service = getTwitterService();
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this page.');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this page');
}
}
I'm not familiar with this particular API, but reading through their documentation, and looking the PHP example, it appears that they are expecting the OAuth parameters to be passed in the URL rather than the Authorization Header.
By default, the OAuth services use the Authorization header, but this can be over-ridden with service.setParamLocation('uri-query').
I was able to reproduce and track down your problem. Funny enough, it ends up being a single-character fix (after the setParamLocation fix already mentioned): you just need to use OAuth version "1.0a" instead of "1.0". OAuth version 1.0a changed some of the details of how the OAuth callback works to fix a security issue, and I guess this OAuth library only includes the callback URL in the request_token step when using OAuth 1.0a. The KA API always uses the callback specified in the request_token step, so the previous version of the app script was never running the callback.
Here's some code that works for me:
var CONSUMER_KEY = 'FILL ME IN';
var CONSUMER_SECRET = 'FILL ME IN';
var PROJECT_KEY = 'FILL ME IN';
function listExercises() {
var service = getKhanAcademyService();
if (service.hasAccess()) {
var response = service.fetch('https://www.khanacademy.org/api/v1/user/exercises');
var exercises = JSON.parse(response.getContentText());
Logger.log(exercises);
} else {
var authorizationUrl = service.authorize();
Logger.log('Please visit the following URL and then re-run the script: ' + authorizationUrl);
}
}
function getKhanAcademyService() {
var service = OAuth1.createService('khanAcademy');
service.setAccessTokenUrl('https://www.khanacademy.org/api/auth2/access_token')
service.setRequestTokenUrl('https://www.khanacademy.org/api/auth2/request_token')
service.setAuthorizationUrl('https://www.khanacademy.org/api/auth2/authorize')
service.setConsumerKey(CONSUMER_KEY);
service.setConsumerSecret(CONSUMER_SECRET);
service.setProjectKey(PROJECT_KEY);
service.setCallbackFunction('authCallback');
service.setPropertyStore(PropertiesService.getScriptProperties());
service.setOAuthVersion('1.0a');
service.setParamLocation('uri-query');
return service;
}
function authCallback(request) {
var service = getKhanAcademyService();
var isAuthorized = service.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('Success! You can close this page');
} else {
return HtmlService.createHtmlOutput('Denied. You can close this page');
}
}
/**
* Example of usage oDeskAPI
*
* #package oDeskAPI
* #since 09/22/2014
* #copyright Copyright 2014(c) oDesk.com
* #author Maksym Novozhylov <mnovozhilov#odesk.com>
* #license oDesk's API Terms of Use {#link https://developers.odesk.com/api-tos.html}
*/
var config = {
'consumerKey' : '571a5ff21bded617e499965f9cf013a0',
'consumerSecret' : 'e3df4989efed7761',
// 'accessToken' : 'xxxxxxxx', // assign if known
// 'accessSecret' : 'xxxxxxxx', // assign if known
'debug' : false
};
//var oDeskApi = require('../') // uncomment to use inside current package/sources
var oDeskApi = require('odesk-api') // use if package is installed via npm
// , Auth = require('../lib/routers/auth').Auth // uncomment to use inside current package/sources
, Auth = require('odesk-api/lib/routers/auth').Auth // use if package is installed via npm
, rl = require('readline');
// you can use your own client for OAuth routine, just identify it here
// and use as a second parameter for oDeskApi constructor (see the example of usage below)
// note: your client must support the following methods:
// 1. getAuthorizationUrl - gets request token/secret pair, creates and returns
// authorization url, based on received data
// 2. getAccessToken(requestToken, requestTokenSecret, verifier, callback) -
// requests access token/secret pair using known request token/secret pair and verifier
// 3. setAccessToken(token, secret, callback) - sets known access token/secret pair
// 4. get|post|put|delete(path, data, callback) - for GET, POST, PUT and DELETE methods respectively
// 5. setEntryPoint(entryPoint) - allows setup different entry point for base url
//
// var MyClient = require('../lib/myclient').MyClient;
//
// by default predefined lib/client.js will be used that works with other odesk oauth library
// a function to get access token/secret pair
function getAccessTokenSecretPair(api, callback) {
// get authorization url
api.getAuthorizationUrl('http://localhost/complete', function(error, url, requestToken, requestTokenSecret) {
if (error) throw new Error('can not get authorization url, error: ' + error);
debug(requestToken, 'got a request token');
debug(requestTokenSecret, 'got a request token secret');
// authorize application
var i = rl.createInterface(process.stdin, process.stdout);
i.question('Please, visit an url ' + url + ' and enter a verifier: ', function(verifier) {
i.close();
process.stdin.destroy();
debug(verifier, 'entered verifier is');
// get access token/secret pair
api.getAccessToken(requestToken, requestTokenSecret, verifier, function(error, accessToken, accessTokenSecret) {
if (error) throw new Error(error);
debug(accessToken, 'got an access token');
debug(accessTokenSecret, 'got an access token secret');
callback(accessToken, accessTokenSecret);
});
});
});
};
// get my data
function getUserData(api, callback) {
// make a call
var auth = new Auth(api);
auth.getUserInfo(function(error, data) {
// check error if needed and run your own error handler
callback(error, data);
});
}
(function main() {
// uncomment only if you want to use your own client
// make sure you know what you're doing
// var client = new MyClient(config);
// var api = new oDeskApi(null, client);
// use a predefined client for OAuth routine
var api = new oDeskApi(config);
if (!config.accessToken || !config.accessSecret) {
// run authorization in case we haven't done it yet
// and do not have an access token-secret pair
getAccessTokenSecretPair(api, function(accessToken, accessTokenSecret) {
debug(accessToken, 'current token is');
// store access token data in safe place!
// get my auth data
getUserData(api, function(error, data) {
debug(data, 'response');
console.log('Hello: ' + data.auth_user.first_name);
});
});
} else {
// setup access token/secret pair in case it is already known
api.setAccessToken(config.accessToken, config.accessSecret, function() {
// get my auth data
getUserData(api, function(error, data) {
debug(data, 'response');
// server_time
console.log('Hello: ' + data.auth_user.first_name);
});
});
}
})();
This is my example.js Currently Its giving the response url of odesk .Just will i try to access that url Error comes (You are not authorized to access this page.) I have installed the app successfully by using node.js .I have the main issue in configuration Right .
You have to provide the same call back Url Which you have provide Odesk While making app your Here
api.getAuthorizationUrl('http://localhost/complete', function(error, url, requestToken, requestTokenSecret) {
if (error) throw new Error('can not get authorization url, error: ' + error);
I am using google login for custom website. here i wrote the code for it
var sOAuthServiceEndPoint = "https://accounts.google.com/o/oauth2/auth?scope=http://gdata.youtube.com https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email&response_type=token&";
var sOAuthRedirectURL = "http://example.com/testpage/test.html";
var termsAndCondURL = "termsandcondition.html";
var sOAuthClientID = "294016263542.apps.googleusercontent.com";
var sAuthenticationURL = sOAuthServiceEndPoint + "redirect_uri=" + sOAuthRedirectURL + "&client_id=" + sOAuthClientID;
even i got an access token using below function
function fnOnLoad() {
//alert("Form Loaded");
var sAccessToken = '';
var params = {}, queryString = location.hash.substring(1),regex = /([^&=]+)=([^&]*)/g, m;
while (m = regex.exec(queryString)) {
params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
}
if(params.error){
if(params.error == "access_denied"){
sAccessToken = "access_denied";
alert(sAccessToken);
}
}else{
sAccessToken = params.access_token;
alert(sAccessToken);
}
window.opener.fnAuthorisationSuccess(sAccessToken);
window.close();
}
It's working succesfully and redirect into the other page where I want. but my problem is how to retrive user login name..?
I am using javascript for it.
Thanks in Advance
This can be found in the documentation.
After your application acquires an access token and has (if necessary) verified it, you can use that access token when making requests to a Google API. If the https://www.googleapis.com/auth/userinfo.profile scope was included in the access token request, then you may use the access token to acquire the user's basic profile information by calling the UserInfo endpoint.
Endpoint: https://www.googleapis.com/oauth2/v1/userinfo
Returns basic user profile information, including name, userid, gender, birthdate, photo, locale, and timezone. If the https://www.googleapis.com/auth/userinfo.email scope was present in the request, then the user's email will also be present in the response. If the email has been verified, then there is also a field that indicates the email is a verified address.