Apps Script公式リファレンス: Apps Script Reference |障害・課題追跡: IssueTracker |Google Workspace: Status Dashboard - Summary

2022年12月18日日曜日

JIRA API でOAuth2.0を利用してみる(add "offline_access" to get refresh token)


この記事では、JIRA APIのOAuth2.0における
  • アクセストークンの取得方法と使い方
  • リフレッシュトークンの取得方法と使い方
について手元で実行した手順を書いていきます。

この記事では、コールバックURLにGoogle Apps Scriptを利用します。

今回refresh tokenを取得する際に使う offline_access をどう追加するかにちょっと時間がかかりました。
offline_accessの記述例については、STEP16以降に書きました。
おそらく同じところでつまづく人もいると思うので、お役に立てると幸いです。



JIRAでOAuth 2.0を利用する方法については、

OAuth 2.0 (3LO) apps

に書かれています。

その中にある Enabling OAuth 2.0 (3LO)  のステップを手元で進めながら書いていこうと思います。



STEP1
まずは、developer console を開きます。

Before you can implement OAuth 2.0 (3LO) for your app, you need to enable it for your app using the developer console. 

From any page on developer.atlassian.com, select your profile icon in the top-right corner, and from the dropdown, select Developer console.

STEP2
「Create」の中の「OAuth 2.0 integration」をクリックします。


STEP3
任意の名前を入れて「Create」をクリックします。


STEP4
「Permissions」を開きます。
Note, if you haven't already added an API to your app, you should do this now:

Select Permissions in the left menu.
Next to the API you want to add, select Add.


STEP5
Jira APIの「Add」をクリックすると「Configure」となるので、再度クリックします。


STEP6
「Edit Scopes」をクリックします。


STEP7
APIでやりたいことに合わせて、必要なスコープを選択して「Save」します。


STEP8
「Authorization」を選択して、「Add」をクリックします。


STEP9
「Callback URL」の入力欄があるので、ここではApps ScriptでWebアプリを作り、デプロイしたアプリのURLを貼り付けて「Save changes」をクリックします。


Apps ScriptのWebアプリ側はこんなコードにしてデプロイしました↓


コード.gs
function doGet(e) {
  return ContentService.createTextOutput(JSON.stringify(e));
}

manifest.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "MYSELF"
  }
}

デプロイの手順





STEP10
Save changeしたあと、authorization URLが表示されるので、別のタブで開きます。
(これが後にrefresh tokenの取得プロセスでキーポイントになるauthorization URL)


STEP11
「Accept」をクリックします。

すると、このような形でauthorization codeが返ってきます。


{"queryString":
  "code=ここにauthorization codeが入っている&state=%24%7BYOUR_USER_BOUND_VALUE%7D",
  "contextPath":"",
  "contentLength":-1,
  "parameter":{
    "code":"ここにauthorization codeが入っている",
    "state":"${YOUR_USER_BOUND_VALUE}"
  },
  "parameters":{
    "code":["ここにauthorization codeが入っている"],
    "state":["${YOUR_USER_BOUND_VALUE}"]
    }
}



STEP12
そのcodeの値を使って、アクセストークンを取得します。
その際に必要な「client_id」「client_secret」はSettingsからコピーできます。


STEP13
curlで --data '{〜}'内にあるこれらの値を代入して以下のrequestを実行します。
YOUR_CLIENT_ID
YOUR_CLIENT_SECRET
YOUR_AUTHORIZATION_CODE
YOUR_APP_CALLBACK_URL


curl --request POST \
  --url 'https://auth.atlassian.com/oauth/token' \
  --header 'Content-Type: application/json' \
  --data '{"grant_type": "authorization_code","client_id": "YOUR_CLIENT_ID","client_secret": "YOUR_CLIENT_SECRET","code": "YOUR_AUTHORIZATION_CODE","redirect_uri": "YOUR_APP_CALLBACK_URL"}'


するとこのような形でアクセストークンが返ってきます。

{
  "access_token":"アクセストークン",
  "scope":"write:jira-work read:jira-work read:jira-user",
  "expires_in":3600,
  "token_type":"Bearer"
}%



STEP14
そのアクセストークンを使って以下のrequestを送ります。

curl --request GET \
  --url https://api.atlassian.com/oauth/token/accessible-resources \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Accept: application/json'



すると、このような結果が返ってきます。

[
  {
    "id":"CLOUD_ID",
    "url":"https://SITE_NAME.atlassian.net",
    "name":"SITE_NAME",
    "scopes":[
      "write:jira-work",
      "read:jira-work",
      "read:jira-user"
    ],
  "avatarUrl":"https://site-admin-avatar-cdn.prod.public.atl-paas.net/avatars/240/star.png"
  }
]%




STEP15
そのCLOUD_IDを使って以下のrequestを実行すると、プロジェクトのデータが返ってきます。

curl --request GET \
  --url https://api.atlassian.com/ex/jira/CLOUD_ID/rest/api/2/project \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Accept: application/json'



STEP16
offline_accessを追加して、リフレッシュトークンを取得してみる。

ここがちょっと情報少なくて時間かかりました。

この記事を書いている時点では、公式ドキュメントにはこう書かれていました。
To get a refresh token in your initial authorization flow, add offline_access to the scope parameter of the authorization URL. 
authorization URLのscopeのパラメータにoffline_accessを追加するとのことですが、さて、どのように追加しようか。
…というのがわからず、ググったり試行錯誤する時間を経て、refresh tokenを返すことができたやり方を書きます。

ここからはSTEP10〜13とかぶる部分もあります。

STEP17
STEP10でも触れたAuthorizationページの下部に書かれているauthorization URLのscope=に「offline_access%20」を追加してブラウザの別タブで開きます。
実際どのように追加するかというと

もともと書かれているscopeの中身を見ると
read%3Ajira-work%20read%3Ajira-user%20write%3Ajira-work
となっており、これらはSTEP7のスコープ設定画面でチェックを入れて選択したものです。
そこに「offline_access」を追加して、半角スペース(%20)で以下のようにつなげます。

「offline_access%20」を追加したURLの例
https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=CLIENT_ID&scope=offline_access%20read%3Ajira-work%20read%3Ajira-user%20write%3Ajira-work&redirect_uri=CALLBACK_URL&state=${YOUR_USER_BOUND_VALUE}&response_type=code&prompt=consent

上記URLをブラウザの別タブで開きます。

STEP18
STEP11と同じく「Accept」をクリックします。
すると、このような形で、authorization codeが返ってきます。


{
  "parameter":
    {
      "state":"${YOUR_USER_BOUND_VALUE}",
      "code":"ここにauthorization codeが入っている"
}, "contextPath":"", "contentLength":-1, "queryString":"code=ここにauthorization codeが入っている&state=%24%7BYOUR_USER_BOUND_VALUE%7D",
"parameters":{ "state":["${YOUR_USER_BOUND_VALUE}"], "code":["ここにauthorization codeが入っている"] } }



STEP19
STEP12と同じく、そのcodeの値を使って、アクセストークンを取得します。
その際に必要な「client_id」「client_secret」はSettingsからコピーできます。


STEP20
STEP13と同じく
curlで --data '{〜}'内にあるこれらの値を代入して実行します。
YOUR_CLIENT_ID
YOUR_CLIENT_SECRET
YOUR_AUTHORIZATION_CODE
YOUR_APP_CALLBACK_URL


curl --request POST \
  --url 'https://auth.atlassian.com/oauth/token' \
  --header 'Content-Type: application/json' \
  --data '{"grant_type": "authorization_code","client_id": "YOUR_CLIENT_ID","client_secret": "YOUR_CLIENT_SECRET","code": "YOUR_AUTHORIZATION_CODE","redirect_uri": "YOUR_APP_CALLBACK_URL"}'


するとこのような形でアクセストークンとリフレッシュトークンが返ってきます。

{
  "access_token":"アクセストークン",
  "refresh_token":"リフレッシュトークン",
  "scope":"write:jira-work read:jira-work read:jira-user offline_access",
  "expires_in":3600,
  "token_type":"Bearer"
}%

STEP13ではアクセストークンだけでしたが、今回はリフレッシュトークンも返ってきました。


STEP21
リフレッシュトークンを使ってアクセストークンを更新してみる。

curlで --data '{〜}'内にあるこれらの値を代入して実行します。
CLIENT_ID
CLIENT_SECRET
REFRESH_TOKEN


curl --request POST \
  --url 'https://auth.atlassian.com/oauth/token' \
 --header 'Content-Type: application/json' \
 --data '{ "grant_type": "refresh_token", "client_id": "CLIENT_ID", "client_secret": "CLIENT_SECRET", "refresh_token": "REFRESH_TOKEN" }'


すると、STEP20の結果と同じくこのように値が返ってきます。

{
  "access_token":"アクセストークン",
  "refresh_token":"リフレッシュトークン",
  "scope":"write:jira-work read:jira-work read:jira-user offline_access",
  "expires_in":3600,
  "token_type":"Bearer"
}%


STEP22
JQLの結果を取得してみる

特定のPROJECT_KEYにある課題を取得したい場合

JQLはこう書くと思います
project=PROJECT_KEY

それをAPIでこのように渡すために
search?jql=project=PROJECT_KEY

パラメータ内で使えない?や=の文字をエンコードしてこのように表現します。
search%3Fjql%3Dproject%3DPROJECT_KEY


そしてrequestの中にあるこれらの値を代入して実行すると、PROJECT_KEYの中にある課題情報が返ってきます。
CLOUD_ID
PROJECT_KEY
ACCESS_TOKEN



curl --request GET \
  --url https://api.atlassian.com/ex/jira/CLOUD_ID/rest/api/2/search%3Fjql%3Dproject%3DPROJECT_KEY \
  --header 'Authorization: Bearer ACCESS_TOKEN' \
  --header 'Accept: application/json'



この記事ではJIRA APIのOAuth2.0における
  • アクセストークンの取得方法と使い方
  • リフレッシュトークンの取得方法と使い方
について手元で実行した手順を書いてきました。

仕様やドキュメントは将来変わっていくものだと思うので、ここに書いた方法がいつまで有効かわかりませんが、この記事が同じところでつまずいている開発者のお役に立てれば幸いです。


おまけ:認可コード取得→アクセストークン取得をGoogle Apps Script側でやってみる

Webアプリとして利用するApps Scriptのコード.gsを以下のように書いて、STEP9同様にデプロイし、そのURLを使います。(JIRAのAuthorizationのCallback URLと、Apps Scriptのredirect_uriに入れるURL)

STEP 10, 11同様、authorization URLを別のタブで開いて Accept をクリックすると、Apps Script側ではauthorization codeを e['parameter']['code'] で取得して、一気にアクセストークンを返してくれます。




Webアプリとしてデプロイするコードは以下の通り

コード.gs
var client_id = 'CLIENT_ID';
var client_secret = 'CLIENT_SECRET';

var redirect_uri = "WebアプリとしてデプロイしたURL";

function doGet(e) {// Web認証用URLを開いたときに動く処理
  var response = getAccessToken(e);
  return ContentService.createTextOutput(response);// ブラウザに表示する
}

var token_url = 'https://auth.atlassian.com/oauth/token';

function getAccessToken(e) {// 認可コードを利用してトークン情報を取得して返す
  var code = e['parameter']['code'];
  var payload = {
    'grant_type': 'authorization_code',
    'client_id': client_id,
    'client_secret': client_secret,
    'code': code,
    'redirect_uri': redirect_uri
  }
  
  var options = {
    'method': 'post',
    'header': {'Content-Type': 'application/json'},
    'payload': payload
  };
  var response = UrlFetchApp.fetch(token_url, options);
  Logger.log(response);
  return response;
}


manifest.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "webapp": {
    "executeAs": "USER_DEPLOYING",
    "access": "MYSELF"
  }
}


参考

Basic auth for REST APIs


Latest post

Google Apps Scriptの障害時はIssueTrackerを見てみる - Incidents for Apps Script are reported on Issue Tracker

IssueTracker > Apps Script issues https://issuetracker.google.com/savedsearches/566234 Google Apps Scriptの障害時は IssueTracker に課題が上がっていることが...