OAuth2.0認証をしたい

正直な所、他のウェブサービス(ツイッターやらなにやら)は、業務では使用しないので、OAuth認証系に関しては、必要ない・もしくは必要となっても、oAuthConfigとUrlfetchAppを使ったOAuth1の認証で十分と思ってました。特に業務では、PDF化などに於いて、Google Appsなどでは使う機会があるのですが、既に紹介した方法で可能です。が、しかし、1点問題があるのです。この認証は、スクリプトエディタに入って1度実行しないと認証画面が出てくれない&実行ユーザ毎に認証が必要(但し、ScriptTriggerによる自動実行は設置者が1回やれば問題ない)、更にはユーザにそんな作業をやらせないといけないと、問題がオオアリなわけです。普通の認証画面の用に実行時に出てくれたら良いのですが、なぜか、このOAuth認証は出てくれないから困るわけで。

ということで、調べて見たところ、この問題の解決およびついでに外部のAPIを叩いてGoogle Apps ScriptでOAuth2.0認証を画面認証させる&PDFを作れるようにする為に四苦八苦して、なんとか出来るようになりました。これによって、ダイアログで認証作業をさせることが出来、また、これまでのようにPDFを作れるので、少しこれで様子を見てみようと思っています。また、Googleの様々なAPI自体もUrlfetchAppのように、外部からのアクセスという形の場合には、このOAuthの認証が必要になることがあります。

OAuth2.0認証に必要な作業

PDF作成では、UrlfetchAppに続けて、oauthconfigのパラメータを渡して、認証を行わせてPDFを作りました。

今回、OAuth2.0認証の場合には、少々面倒な作業が待っています。しかし、Googleも推奨しているようにOAuth1.0から2.0へ移行しなさいという警告および、他のウェブサービスが2.0へ移行していることもあって、これから先のGASアプリケーションを作る上では、結構避けられない部分があったりします。しかし、このOAuth2.0は仕組みも結構複雑なので、少々理解するのに時間が必要かもしれません。

ウェブアプリケーションとして公開する

OAuth2.0を使用する為にはいくつかの情報が必要です。その1つがウェブアプリケーションとして公開した時のURLです。スプレッドシートなどのURLとは異なり、このURLは次の項目であるクライアントIDの作成上必要なものです。以下の手順で公開し、URLをメモしておきます。

  1. スクリプトエディタを開き、【公開】 -> 【ウェブアプリケーションとして導入】をクリックする。
  2. バージョンを保存して、更新ボタンを押すとURLが出てきます。
  3. URLは、https://script.google.com/a/macros/s/ここにはスクリプト毎に用意されたURLが入っています/exec というURLです。最後の文字列がexecである必要性があります。これをメモして起きます。

クライアントIDを作成する

Google の各種APIを今回は使用するので、Google Developer Consoleにて、クライアントIDを作成します。以下の手順で作成します。

  1. スクリプトエディタを開き、【リソース】 -> 【Googleの拡張サービス】をクリックする。
  2. 表示されたダイアログの下にある【Googleデベロッパーコンソール】のリンクをクリックする
  3. 追加で使用したいAPIがあれば、それらをONにする。今回はPDF作成のみなので、APIに関しては何もONにはせずスルーします。
  4. 左のペインにある【APIと認証】の中にある【認証情報】を開きます。
  5. OAuthの所にある「新しいクライアントIDを作成」をクリックします。
  6. 以下の設定で、クライアントIDを作成します。
  • アプリケーションの種類:ウェブアプリケーション
  • 承認済みのJAVASCRIPT生成元:http://script.google.com/
  • 承認済みのリダイレクト URI:ウェブアプリケーションとして公開するの3.にて取得したURLを入力する。
  1. 作成ボタンを押すと、下の画面のように、クライアントID、クライアントシークレット、リダイレクトURIが手に入る。Google Apps Scriptのコードの中で必要になる。

図:クライアントIDの作成中の画面

アクセストークン取得のソースコード

いくつか、認証してアクセストークンを取得するコードがネットにあるのですが、自分としてはこれがいちばんフィットしたというのがありましたので、今回はこのサイトのコードを利用させていただきました。そのコードについて見てゆきたいと思います。

//クライアントID関係のグローバル変数
var CLIENT_ID = 'ここにクライアントIDを入力する';
var CLIENT_SECRET='ここにクライアントシークレットコードを入力する';
var REDIRECT_URL= ScriptApp.getService().getUrl();
//OAuth関係のグローバル変数
var AUTHORIZE_URL = 'https://accounts.google.com/o/oauth2/auth'; 
var TOKEN_URL = 'https://accounts.google.com/o/oauth2/token'; 
var tokenPropertyName = 'GOOGLE_OAUTH_TOKEN'; 
var baseURLPropertyName = 'GOOGLE_INSTANCE_URL'; 
//プログラムのメインコード
function doGet(e) {
  var HTMLToOutput;
  if(e.parameters.code){//if we get "code" as a parameter in, then this is a callback. we can make this more explicit
    getAndStoreAccessToken(e.parameters.code);
    HTMLToOutput = '<html><h1>OAuth認証が完了しました。</h1></html>';
  }
  else if(isTokenValid()){//if we already have a valid token, go off and start working with data
    HTMLToOutput = '<html><h1>既にOAuth認証完了してますよ</h1></html>';
  }
  else {//we are starting from scratch or resetting
    return HtmlService.createHtmlOutput("<html><h1>Lets start with oAuth</h1><a href='"+getURLForAuthorization()+"'>click here to start</a></html>");
  }
  
  HTMLToOutput += getData();
  return HtmlService.createHtmlOutput(HTMLToOutput);
}

//データの取得

function getData(){
  var getDataURL = 'https://www.googleapis.com/oauth2/v1/userinfo';
  var dataResponse = UrlFetchApp.fetch(getDataURL,getUrlFetchOptions()).getContentText();  
  return dataResponse;
}
//URLの組み立てとスコープの指定
function getURLForAuthorization(){
  return AUTHORIZE_URL + '?response_type=code&client_id='+CLIENT_ID+'&redirect_uri='+REDIRECT_URL +
    '&scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&state=/profile https://docs.google.com/feeds';  
}
//組み立てたURLをPOSTで投げて、コールバックデータをJSON形式で受けて取り出す。
function getAndStoreAccessToken(code){
  var parameters = {
     method : 'post',
     payload : 'client_id='+CLIENT_ID+'&client_secret='+CLIENT_SECRET+'&grant_type=authorization_code&redirect_uri='+REDIRECT_URL+'&code=' + code
   };
  
  var response = UrlFetchApp.fetch(TOKEN_URL,parameters).getContentText();   
  var tokenResponse = JSON.parse(response);
  
  //アクセストークンをユーザプロパティに格納
  UserProperties.setProperty(tokenPropertyName, tokenResponse.access_token);
}
//Urlfetchオプションの指定
function getUrlFetchOptions() {
  var token = UserProperties.getProperty(tokenPropertyName);
  return {
            "contentType" : "application/json",
            "headers" : {
                         "Authorization" : "Bearer " + token,
                         "Accept" : "application/json"
                        }
         };
}
//トークン取得しているかどうかの確認
function isTokenValid() {
  var token = UserProperties.getProperty(tokenPropertyName);
  if(!token){ //if its empty or undefined
    return false;
  }
  return true; //naive check
  
}
  • 今回のソースコードはHTML Servicesでやり取りを行うようになっています。ただ、非常に簡素な作りなので、自分がプログラムで組み込む時には、もう少し、見た目を格好の良いものにしたいと思ってます。
  • 取得したアクセストークンは、ユーザ別にユーザプロパティに格納されています。
  • 冒頭のdoGetにて、状態によって分岐させています。取得していない場合には、取得に関するHTMLが表示され、取得済みの場合には取得済みということでスクリプトが終了する仕組みです。
  • このコードは、コードを実行するのではなくウェブ経由でアクセスさせる仕組みになっていますので、exec付きのURLをアクセスすると、認証系の画面が出てきます。

試しに自分はこの認証をユーザ側で簡単に行える用に、インターフェースを用意してみました。

予め、特定のユーザプロパティを取得して、トークンを持っているかどうかを確認し、トークンを持っていない場合には、起動時にこの画面を出すようにしています。もちろん、持っている場合には、特に何もしません。もちろん、インターフェースはCSSなどでちょっと格好良くして、表示させてそれっぽく見せます。承認ボタンを押すと、新しいタブでURLが開かれて、hrefに指定しておいたURLが開くようになっています。

図:試しにつくってみたインターフェース(HTML Servicesで作成したダイアログ)

OAuth2.0認証でPDFを作ってみる

既に、上記のコードでアクセストークンを取得してたら、以降はその取得済みアクセストークンをパラメータとしてUrlfetchAppへ渡して上げれば良いです。これまでは、この段階でUrlfetchAppの持つOAuth1認証機能を利用して、oAuthConfigにパラメータやオプションを設定していましたが、今回は不要です。

function sheet2pdf2mail2() {   
  //アクティブシートのIDとGIDを取得する
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = ss.getActiveSheet();
  var sheetID = sheet.getSheetId();
  var key = ss.getId();
  var token = ScriptApp.getOAuthToken();
  //PDF生成するURLをfetchする
  var newFileId = "ここにPDF化したいファイルのIDを入れる";
  var url = "https://docs.google.com/a/spreadsheets/d/"+newFileId+"/export?format=pdf&portrait=true&size=A4&gridlines=false&fitw=true";
                               
  var pdf = UrlFetchApp.fetch(url, {headers: {'Authorization': 'Bearer ' +  token}}).getBlob().setName("test" + ".pdf");
    
  //作成したPDFファイルをメールに添付して送る
  var mail = '送信先メールアドレスをここに入れます'
  var subject = 'PDF送るよ'
  var body = 'まぁ、送るんで見てくれ'
  MailApp.sendEmail(mail, subject, body, {attachments:pdf});
}
  • 既に取得済みのアクセストークンを取得するのは、ScriptApp.getOAuthToken()で行います。
  • アクセストークンはUrlFetchAppの引数パラメータに渡してあげますが、ここがちょっと独特な形式なので、こういうもんだとして覚えてしまいましょう。今回は、アクセストークン以外必要ありません。
  • 渡してあげるURLはこれまで通り、Spreadsheet APIのURLとして色々オプションをくっつけて渡します。今回はgid指定無しなので、全ページがPDF印刷対象としています。
  • 当然、mailに添付して送るので、結果は、Blobでゲットして、名前はtest.pdfにリネームしてあります。

参考URL