Skip to content

Instantly share code, notes, and snippets.

@tmclnk
Forked from entaq/Google_oAuth.js
Last active August 29, 2015 14:02
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tmclnk/2f4af6f14d6aeadb7611 to your computer and use it in GitHub Desktop.
Save tmclnk/2f4af6f14d6aeadb7611 to your computer and use it in GitHub Desktop.
OAuth2 end-to-end example for client auth using newStateToken
FAQ
Why open a new window for the authorization?
Because if you load the contents of the auth page directly IN a sidebar (or script) using HtmlService, its buttons won't work because they've been cajoled.
Why not just the the access token provided by GAS Libraries?
Because lord only knows what that thing is scoped for.
How do I clear my token?
Pass ?reset parameter to the url, or call clearMyAuthToken() directly.
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons.css">
<div class='sidebar'>
<h3 id='header'>Authorization Required</h3>
<div id="info">If you are seeing this page, it is because you don't currently have an access token.
</div>
<div>
<button onclick="closeMe();">Nope</button>
<a href="asdf" class="action button" href="<?= authUrl ?>">Login</a>
</div>
<script>
function closeMe(){
if(google && google.script && google.script.host){
google.script.host.close();
} else if(window && window.close){
window.close();
}
document.getElementById('header').innerHTML = "<span class='error'>Oh SNAP!</span>";
document.getElementById('info').innerHTML = "<span class='error'>I tried to close this window but failed. It wasn't because I didn't try, though.</span>";
}
</script>
</div>
// --------------------- YOUR CONFIGURATION HERE----------------------
/* get CLIENT_ID_ and CLIENT_SECRET_ as listed in the google developers console for your project */
var CLIENT_ID_ = PropertiesService.getScriptProperties().getProperty("CLIENT_ID");
var CLIENT_SECRET_= PropertiesService.getScriptProperties().getProperty("CLIENT_SECRET");
/* the required scope of the request being made in getData(); if this
* value is changed between invocations, it will invalidate any
* cached tokens the next time they are requested */
var SCOPE = ['https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile'].join(' ');
var REQ_REDIRECT_URI_ = (function (){
var tmp = ScriptApp.getService().getUrl();
/* this is kind of ham-fisted, but we swap out the trailing /dev or /exec here */
var uri = tmp.substring(0,tmp.lastIndexOf('/')) + '/usercallback';
return uri;
})();
/* this is listed as a separate variable just as a reminder that ScriptApp.getService.getUrl()
does not return consistent urls within this app depending on whether you are hitting
a dev url or a versioned one; versioned urls are consistent across invocations, dev ones
are not, so when the second redirect fails, get that url and paste it here
tl;dr if you are in dev mode, expect to have to hard code TOKEN_REDIRECT_URI_ AND add it to the console */
var TOKEN_REDIRECT_URI_='https://script.google.com/macros/s/AKfycbzYSe8FhE2flpacv3Lxh6cVi5MEx61Is0Mj_0FBARIx/usercallback';
// -------------------- YOUR GOOGLE API CALL HERE ---------------------
function getData(){
var getDataURL = 'https://www.googleapis.com/oauth2/v1/userinfo';
var requestProps = {
"contentType" : "application/json",
"headers" : {
"Authorization" : getAuthorizationHeader_(), //required oAuth value
"Accept" : "application/json"
}
};
var dataResponse = UrlFetchApp.fetch(getDataURL,requestProps).getContentText();
return dataResponse;
}
// -------------------- THE AUTHORIZATION CODE HERE -------------------
var TOKEN_PREFIX_ = "mcg_wflow";
var TOKEN_CACHE_KEY_ = TOKEN_PREFIX_ + '_auth_token';
var TOKEN_SCOPE_KEY_ = TOKEN_PREFIX_ + '_scope';
function doGet(e) {
/* uncomment this and visit the dev page to figure out what to set REQ_REDIRECT_URI_ to */
//return ContentService.createTextOutput(REQ_REDIRECT_URI_);
try {
if(typeof e.parameter.reset !== 'undefined'){
clearMyAuthToken();
return getLoginPage();
}
var authHeader = getAuthorizationHeader_();
if(authHeader){
var HTMLToOutput = '<h4>'+ authHeader+'</h4>';
//getData() here
return HtmlService.createHtmlOutput(HTMLToOutput);
}
else {//we are starting from scratch or resetting
return getLoginPage();
}
} catch (ex){
return ContentService.createTextOutput(ex + "\n" + ex.stack);
}
}
function getLoginPage(){
var loginPage = HtmlService.createTemplateFromFile("login");
loginPage.authUrl = getURLForAuthorization();
return loginPage.evaluate();
}
/** run this to figure out which redirect_uri to put in google developer's console;
don't use this if you are trying to get a dev uri, it will only work for the currently
published (non-dev) version */
function printRedirectURI(){
Logger.log(REQ_REDIRECT_URI_);
}
/**
* Remove's the calling user's AuthToken from the cache. Useful if you need to
* yank a token during development or if you need to switch the outgoing account
* you have linked to.
*/
function clearMyAuthToken(){
CacheService.getPrivateCache().remove(TOKEN_CACHE_KEY_);
}
/**
* Called by the auth services; serves the redirect_uri responses.
*/
function usercallback_( request ){
/* if the state token isn't valid, the StateToken redirect url will reject before it gets here */
try {
var code = request.parameter.code;
if(code){
var parameters = {
method : 'post',
contentType:'application/x-www-form-urlencoded',
muteHttpExceptions:false,
followRedirects:false,
payload : 'client_id='+CLIENT_ID_+'&client_secret='+CLIENT_SECRET_+'&grant_type=authorization_code&code=' +code+ '&redirect_uri='+ TOKEN_REDIRECT_URI_
};
var httpResponse = UrlFetchApp.fetch('https://accounts.google.com/o/oauth2/token',parameters);
var tokenResponse = JSON.parse(httpResponse.getContentText());
CacheService.getPrivateCache().put(TOKEN_SCOPE_KEY_, SCOPE);
CacheService.getPrivateCache().put(TOKEN_CACHE_KEY_, tokenResponse.access_token, 3600-120);
}
return doGet(request);
}catch(e){
Logger.log(e + "\n" + e.stack);
var simulatedRequest = UrlFetchApp.getRequest('https://accounts.google.com/o/oauth2/token',parameters);
Logger.log("Failed request looked something like this:\n" + JSON.stringify(simulatedRequest));
return ContentService.createTextOutput().append("ERRORS RETRIEVING AUTH TOKEN\n").append(Logger.getLog());
}
}
function getURLForAuthorization(){
var stateToken = ScriptApp.newStateToken().withMethod('usercallback_').withTimeout(180).createToken();
var authUrl = Utilities.formatString('https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&state=%s&approval_prompt=force',CLIENT_ID_, REQ_REDIRECT_URI_, encodeURIComponent(SCOPE),stateToken);
return authUrl;
}
/**
* Fetches the cached authorization token or null if non exist. If the
* SCOPE has changed, clears out the token value as a side-effect.
*/
function getAuthorizationHeader_(){
/* Invalidate the cached token if the scope has changed. */
var cachedScope = CacheService.getPrivateCache().get(TOKEN_SCOPE_KEY_);
if(cachedScope !== SCOPE){
CacheService.getPrivateCache().remove(TOKEN_SCOPE_KEY_);
CacheService.getPrivateCache().remove(TOKEN_CACHE_KEY_);
return null;
} else {
var token = CacheService.getPrivateCache().get(TOKEN_CACHE_KEY_);
return token ? 'Bearer ' + token : null;
}
}
@tsanidas
Copy link

Thanks for this!

@cika
Copy link

cika commented May 3, 2015

not working!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment