Adobe API 認証トークンの交換方法 (JWT/OAuth)

# はじめに
Adobe が提供する Creative Cloud、Document Cloud、Experience Cloud 等の各種製品では、デスクトップアプリケーションだけでなく、Web アプリケーション向けにも利用できる API 機能を公開していて、活用できると、アプリケーション間の連携を実現できたり、作業の自動化や効率化にもすごく便利に使えます。

API 自体は開発に使われるものなので、そのドキュメントもすべて開発者向けとなっており英語のみの提供だったりします。その一方で、管理者の目線からすると、API を開発者に利用させるために認証トークンの発行を許可させたりすることも必要です。

この記事では、API を利用する前段となる認証トークンの発行・管理手順についてまとめておきます。

# Project とは
管理者は Adobe Developer Console (https://developer.adobe.com/console) の中で Project という設定を作成して、その情報を API を利用する開発者に渡します。この Project には、どの製品 API を使用するか、その API を利用するための資格情報が設定されます。開発者は、この資格情報を使って認証トークンを取得し、API を利用できるようになります。

以下が Project を構成するイメージです。製品 API と資格情報を Project にて管理し、API の用途や利用する部門・担当者などに応じて Project を分けて管理できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/9a81a0fe-874c-9c3d-f285-9806e0d8720f.png)

## 認証トークンの使い分け
製品を利用する場合にログインが必要なように、API を利用する場合も悪用を避けるために与えられた資格情報に従って認証トークンを取得することになります。Adobe では二つのタイプの認証トークンを交換する方式をサポートしていて、管理者は Project を作成する際に **どちらの交換方式を使うか** を指定することになります。それぞれ、何の API を使うか、どのように使うかによっても異なってきます。以下はその二つのタイプの使い分けの例です。

| 認証トークンの交換方式 | 認証トークン交換に必要なもの | 主な使い方 | 主に使用する製品 |
|:-|:-|:-|:-|
| JWT | Project x1<br>公開鍵・秘密鍵 x1  | サーバー同士の連携、スクリプトによる自動実行等の比較的長期の利用  | Adobe Campaign, Adobe Target, Adobe Analytics, Adobe Experience Platform Data Collection (旧称 Launch) など
| OAuth | Project x1<br>ログインアカウント x1  | Adobe 製品にログインするユーザーのログイン資格を代用する対話式の操作 (他のアプリケーションからの呼び出しなど) | Creative SDK, Photoshop, Adobe Analytics など|

この後に JWT の場合・OAuth の場合、それぞれの認証トークンの交換例も紹介しますので、各交換方式の違いの参考にしていただけたらと思います。

## Project を作成する
認証を行うための Project を作成してみます。認証トークンの交換方式については、Project を作成する過程で指定しますが、作成した後にも適宜編集・追加できます。

1. https://developer.adobe.com/console にログインします。ログインには、システム管理者か開発者の権限を持っている必要がありますので、ログインできない場合には権限の有無について管理者に確認してみてください。
1. [Create new project] をクリックして Project の作成に進みます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/3e89d515-c461-026f-3ac3-c470ea454ce8.png)
1. この時点で新たなプロジェクトが作成されます。既定では、[Project 1] のように、何のプロジェクトなのか分からない名前 (Project Title) が割り当てられますので、[Edit project] から変更することがオススメです。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/b065bb6f-fefe-ba58-360f-1c1bf4f0e5ee.png)
    * Project Title と Description が設定できますが、残念ながら日本語を利用できません。英字・スペースが利用できます。(アンダースコアやハイフン等の記号も利用できません)
    * Project を作成した後のあるあるとして「このプロジェクト、どこで使っているんだっけ」という場面に遭遇することもあります。どのシステム・アプリケーション用の API なのか、あるいは、その API 利用を管理する部門名などを命名に取り入れると管理上わかりやすくなります。
    * 検証用に作る場合も、後々に他の管理者が見て困らないように命名ルールを定めておくことをオススメします。以下は一例です。
        * 命名のフォーマット例：`{所有ユーザー名} {製品API} {初期作成日付}`
        * 命名の例：`ashimizu AnalyticsAPI 20220222` 
1. [Add API] から利用する製品 API を追加します。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/e2083558-5c64-58f8-1820-c81139b6e6b9.png)
1. ここでは、例として Adobe Analytics を選択していきます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/41a53470-a50c-9631-45e2-81883d1bf9fd.png)
1. 次は、認証トークンの交換方式です。選択した製品 API で使用する認証トークン交換方式を選択します。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/3c0c0ab5-c44d-7d84-b22b-40fa88b4dc28.png)
1. まず、JWT を利用する場合は [Service Account (JWT)] を選択します。JWT では、認証トークンの交換に使用する鍵を設定します。鍵の設定方法として OPTION 1 と OPTION2 を選ぶことができます。
    * [OPTIONS 1 Generate a key pair] では、この Adobe Developer Console 上で公開鍵と秘密鍵を生成します。手元に鍵ファイルがダウンロードされますので保管しておいてください。
    * [OPTIONS 2 Upload your public key] では、自前で公開鍵と秘密鍵を生成しておいて、公開鍵だけをアップロードします。鍵の取り扱いに厳密なルールがある場合などはこちらをご利用ください。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/2afcc6b0-020a-10f5-31fb-07165ac302a8.png)
1. 鍵の設定完了後、選択した製品 API に応じて追加設定が必要となる場合があります。Adobe Analytics の場合は、以下のように Project に適用する製品プロファイルを選択します。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/6b5f2765-2b7c-2638-c412-692bba31d369.png)
1. OAuth を利用する場合は [OAuth] を選択します。OAuth を使用して外部アプリケーションと連携する場合は、連携先アプリケーションでどのオプションを指定するかを指示している場合がありますので、外部アプリケーションのヘルプなども併せて参照ください。
    * OAuth では、連携するアプリケーションのタイプを指定します。ここでは一例として Web を選択します。[Default redirect URI] や [Redirect URI pattern] は連携先アプリケーションの指示に合わせて指定します。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/259d516c-b8b3-f39d-368f-51118d23f5fd.png)
1. JWT か OAuth か、いずれかの交換方式を選択したら、Project の作成は一旦完了です。

# 認証トークンを交換してみる

作成した Project の資格情報から、Postman という API クライアントソフトウェア交換を使って API を利用するための認証トークンを交換する簡易手順にも触れてみます。この手順を見ていただくことで、JWT と OAuth の主な利用シーンが変わってくることもお分かりいただけるのではと思います。また、単発で API を使いたい場合等の簡易的な操作の参考としてもお使いいただけます。

:::note info
Postman とは UI ベースで Web API リクエストを投げたり、レスポンスを確認したりを簡単に行える Web API クライアントソフトウェアです。以下より無料でダウンロード・利用できます。(サインインは必要です)
https://www.postman.com/
:::
## JWT による認証トークンの交換

1. まず、Project 作成時に生成した鍵ファイルを手元に用意します。private.key ファイルをテキストエディタで開き、全範囲をコピーします。`-----BEGIN PRIVATE KEY-----`、`-----END PRIVATE KEY-----` と記載されている行もコピーの対象範囲です。
1. Adobe Developer Console から Project を選択し、①[Service Account (JWT)] から ②[Generate JWT] タブに進みます。③[Generate custom JWT] に private.key ファイルの全文を貼り付けし、④[Generate Token] をクリックします。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/364e0d84-cf8a-fe95-9b94-b5a55cb9be9e.png)
1. JWT トークンが [Sample cURL command] に表示されます。この内容をコピーします。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/66e1f2bd-ecfd-7c36-33e7-69ea1b7ec905.png)
1. Postman を立ち上げ、画面左上の [Import] をクリックします。[Raw text] タブに、[Sample cURL command] からコピーした curl コマンド全文を貼り付けてインポートします。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/d671f0ee-8c7c-dda2-2330-54950e696c18.png)
1. インポートした curl コマンドが API リクエストとして開かれます。そのまま [Send] します。下部に API リクエストの応答が表示され、[access_token] が取得された認証トークンです。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/f246e683-2825-6910-ae94-850eab19ba34.png)

## OAuth による認証トークンの交換

1. まず、Adobe Developer Console から Project に設定した OAuth 資格情報を取得します。Project を選択し、①[OAuth タイプ] から ②[Credential details] タブ内の各情報を取得します。
    * Client Secret は [Retrieve client secret] をクリックすることで表示されます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/2ae933ca-8498-2ccd-528b-2ec032e8ca9f.png)
1. 次に利用する製品 API のスコープ情報を取得します。スコープ情報は製品 API によって決まった値を以下から参照します。
    * OAuth 2.0 Scopes (英語のみ)｜https://developer.adobe.com/developer-console/docs/guides/authentication/OAuth/Scopes/
    * この例では、Adobe Analytics を対象として `openid, AdobeID, read_organizations, additional_info.projectedProductContext, additional_info.job_function` が該当します。
1. Postman を立ち上げて、[File] > [New Tab] から新しいタブを開きます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/2aea4a0c-ea98-0fc3-41fb-8a81f338c26a.png)
1. [Authorization] タブの [Type] より "OAuth 2.0" を選択します。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/c2903abb-e60b-116b-614a-583541d80e2a.png)
1. 取得した資格情報を [Configure New Token] に設定し、[Get New Access Token] をクリックします。
    * Token Name: 任意の名前を指定
    * Grant Type: "Authorization Code" を選択
    * Callback URL: Project に設定したリダイレクト URL を指定
    * Auth URL: `https://ims-na1.adobelogin.com/ims/authorize/v1` を指定
    * Access Token URL: `https://ims-na1.adobelogin.com/ims/token/v1` を指定
    * Client ID: Project より取得した OAuth 資格情報 "Client ID" の値を指定
    * Client Secret: Project より取得した OAuth 資格情報 "Client Secret" の値を指定
    * Scope: [OAuth 2.0 Scopes](https://developer.adobe.com/developer-console/docs/guides/authentication/OAuth/Scopes/) より確認した値を指定 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/c03a46b9-f4d2-fc3c-aba1-40d430967b9f.png)
1. [Get New Access Token] のクリック後、製品へのサインインを求められます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/5b0ede5d-8192-18d7-607a-87a3bf37be3d.png)
1. サインイン後、以下のように認証トークンが取得できます。![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1025787/711b1af0-3036-678f-c9d3-70cc7af8b328.png)

# 認証トークンの有効期限

JWT の場合も OAuth の場合も交換して取得した認証トークンは、**有効期限が 24 時間まで** となっており、予め有効期限をそれ以上に拡張することはできません。24 時間を経過して API を利用する場合は、再度認証トークンを取得し直す必要があります。

OAuth の場合、Postman を使った交換手順として紹介したように、ユーザーがアプリケーションを使おうとするタイミングで手動ログインする対話式のやり取りが前提になるため、24 時間の有効期限が課題になるようなこともあまり無いと思います。一方で、主にサーバー間連携に使われる JWT の場合、API の利用頻度に合わせて定期的に認証トークンを交換する必要もあります。

## JWT 用の認証トークン交換スクリプト例 (Python)

以下は、JWT で認証トークンを交換する Python のスクリプト例になります。事前に設定ファイルに必要な情報を埋め込んでおいて、スクリプトを呼び出すことで認証トークンを取得できます。

スクリプトの実行例：
```
> python ims_client.py
INFO:root:JWT Token: xxxxxxxxxxxx (一部省略) xxxxxxxxxxxx
INFO:root:Sending 'POST' request to https://ims-na1.adobelogin.com/ims/exchange/jwt
INFO:root:Post body: {'client_id': 'xxxxxxxxxxxxxxxxxxxxxxx', 'client_secret': 'xxxxxxxxxxxxxxxxxxxxxxx', 'jwt_token': 'xxxxxxxxxxxx (一部省略) xxxxxxxxxxxx'}
INFO:root:Access Token: xxxxxxxxxxxx (一部省略) xxxxxxxxxxxx
```

事前に、作成した Project の設定に合わせて設定ファイル config.ini を作成します。Project を参照して、資格情報を config.ini ファイルの各行に貼り付けます。metascope の値は、Project に設定する製品 API によっても変わります。

```config.ini
[default]

# CLIENT ID
# 1. Go to Project and [Service Account (JWT)] > [Credential details] tab.
# 2. Get [CLIENT ID]
apiKey=CLIENT_ID

# TECHNICAL ACCOUNT ID
# 1. Go to Project and [Service Account (JWT)] > [Credential details] tab.
# 2. Get [TECHNICAL ACCOUNT ID]
technicalAccountId=TECH_ACCT_ID@techacct.adobe.com

# ORGANIZATION ID
# 1. Go to Project and [Service Account (JWT)] > [Credential details] tab.
# 2. Get [ORGANIZATION ID]
orgId=IMS_ORG_ID

# CLIENT SECRET
# 1. Go to Project and [Service Account (JWT)] > [Credential details] tab.
# 2. Click [Retrieve client secret], then get [ORGANIZATION ID].
secret=SECRET

# Scope
# 1. Go to Project and [Service Account (JWT)] > [Generate JWT] tab.
# 2. Get scope from [JWT Payload].
# e.g. ent_analytics_bulk_ingest_sdk from "https://ims-na1.adobelogin.com/s/ent_analytics_bulk_ingest_sdk"
metascopes=ent_analytics_bulk_ingest_sdk

# Path to private key file which set in JWT credentials.
key_path=secret.key

# URL Endpoints
# Fixed endpoints for exchange access token.
imsHost=ims-na1.adobelogin.com
imsExchange=https://ims-na1.adobelogin.com/ims/exchange/jwt
```

```ims_client.py
import configparser
import logging
import datetime
import requests
import jwt

logging.basicConfig(level="INFO")
logger = logging.getLogger()

def get_jwt_token(config):
    with open(config["key_path"], 'r') as file:
        private_key = file.read()

    return jwt.encode({
        "exp": datetime.datetime.utcnow() + datetime.timedelta(seconds=30),
        "iss": config["orgid"],
        "sub": config["technicalaccountid"],
        "https://{}/s/{}".format(config["imshost"], config["metascopes"]): True,
        "aud": "https://{}/c/{}".format(config["imshost"], config["apikey"])
    }, private_key, algorithm='RS256')


def get_access_token(config, jwt_token):
    post_body = {
        "client_id": config["apikey"],
        "client_secret": config["secret"],
        "jwt_token": jwt_token
    }

    logger.info("Sending 'POST' request to {}".format(config["imsexchange"]))
    logger.info("Post body: {}".format(post_body))

    response = requests.post(config["imsexchange"], data=post_body)
    return response.json()["access_token"]


config_parser = configparser.ConfigParser()
config_parser.read("config.ini")

config = dict(config_parser["default"])
jwt_token = get_jwt_token(config)
logger.info("JWT Token: {}".format(jwt_token))
access_token = get_access_token(config, jwt_token)
logger.info("Access Token: {}".format(access_token))
```

# まとめ
認証トークンの交換方式に二つのタイプがあり、特に Adobe Analytics で API を活用しようとした場合はどちらの方式もサポートしているため、他のアプリケーションと連携する場合に混乱が起きることもしばしばあり、今回は手順を中心にそれぞれの違いをまとめてみました。

Adobe Developer Console でプロジェクトを管理する方や初めて API を使ってみようという方向けの参考にもなりましたら嬉しいです。

:::note warn
この記事の内容は 2022 年 5 月時点の情報に基づいて執筆しています。UI の変更や API の仕様等は適宜変更されている場合がありますのでご注意ください。Adobe Developer Console の最新情報はこちらです。
https://developer.adobe.com/developer-console/docs/guides/

また、Adobe が提供する各種 API のご案内はこちらです。
https://developer.adobe.com/apis/
:::

:::note info
最後に、私たちが働くアドビのカスタマーサポート部門では製品・技術のエキスパートを随時募集しています！私たちの部門や職種のご紹介をしていますので、以下のページもぜひご覧ください。
https://blog.adobe.com/jp/publish/2021/06/21/corp-adobe-life-talent-interview-11
:::