api
OAuth
apiconnect

[IBM API Connect] DataPower失効管理サービスをシステムIDで操作する

More than 1 year has passed since last update.

やりたいこと

IBM API Connectは、OAuth2.0のトークン管理を外部のRDBMS等に依存することなく、DataPower Gatewayのみで実現する構成オプションを提供しています。RDBMSが不要なのでAvailabilityが上げやすかったり、構成がシンプルになったりと様々なメリットがあるのですが、残念ながらマニュアルの記載が不親切なため取っ付き難い機能でもあります。

今回はDataPower失効管理サービスに関連して良く話題にあがる「システムIDでのトークン失効」を、以下のように実現してみたいと思います。

  • 通常のアプリとは別に、特権クライアントとしてPriviledged Clientを用意する
  • 特権クライアントからは、全resource_ownerが全clientに対して付与したあらゆる許可を失効させることができる
  • 当然一般クライアントであるHello Appからは特権機能は使えない

:point_down: は本投稿で扱うAPI定義の概要です。この図で、右上の赤枠部分が今回のテーマの中心です。2つのProvider APIが同一のゲートウェイで稼働している場合、それぞれが発行したトークンは、もう一方のProvider APIからも取り消しが可能なようです。この動作を利用すると、外部公開されたOAuth External APIで発行したaccess_tokenの権限を、非公開のOAuth Internal APIから取り消すことが可能になります。
図の左下部分(access_tokenの発行)は一般的なOAuthフローなので今回は軽く流します。一般的なOAuthフローについて知りたい方はこちらの記事(英語)がお薦めです。

image.png

コンポーネント間の処理の流れは以下のようになります。
image.png

環境・前提

IBM API Connect v5.0.8.0, IBM DataPower Gateway v7.6.0.1 の組み合わせ(オンプレミス環境)で動作を確認しています。(DataPower失効管理サービスはIBM API ConnectのBluemix版では利用できません)
またIBM API ConnectのOAuth実装、および一般的なOAuth2.0に関する情報については説明を省きます。

実現方針

先程の図をエンドポイントレベルまで分解したものがこちらになります。2つのProvider APIは、それぞれリクエストを認証するためのAuthentication URLと連係しています。access_token発行時にユーザーを認証する/userはLDAP等他の認証方式でも問題ありません。
image.png

同様に、コンポーネント間の処理の流れは以下のようになります。OAuth External API/userが認証したユーザーを主体としたaccess_tokenを発行しますが、OAuth Internal APIがこの権限を取り消すためには、Priviledged ClientによるOAuth External APIへの取り消し要求時に、/systemが同じ主体に対して認証したとする応答を返す必要があります。
image.png

各コンポーネントの紹介

Open API

Hello API

  • リソース・エンドポイント
  • セキュリティ: OAuth2.0, Client ID (Header)
    • 要求スコープは scope1
    • Token URL および Authorization URLOAuth External API を参照

今回のOAuthシナリオでリソース・エンドポイントとなる、Hello World! と応答を返す基本的なAPIです。簡単のためバックエンド呼び出しも無く、あらゆるリクエストに対してGateway上で固定の応答を返します。ただしHello APIは Security Definition として OAuth による保護ポリシーが設定されており、呼び出しにはスコープ scope1 に対して適切な許可が与えられた access_token を提示する必要があります。

Design

主な設定項目は以下の通りです。

hello-api.yaml(抜粋)
info:
  x-ibm-name: hello-api
  title: Hello API
basePath: /message
securityDefinitions:
  oauth:
    type: oauth2
    scopes:
      scope1: ''
    authorizationUrl: $(catalog-location)/ext/authorize
    tokenUrl: $(catalog-location)/ext/token
  clientIdHeader:
    type: apiKey
    in: header
    name: X-IBM-Client-Id
security:
  - oauth:
      - scope1
    clientIdHeader: []
paths:
  /hello:
    get:
Assembly

今回はset-variableポリシーで実装しています。

image

hello-api.yaml(抜粋)
  assembly:
    execute:
      - set-variable:
          title: set-message
          actions:
            - set: message.body
              value: '{"message":"Hello World!"}'

Client Application

Hello App

  • OAuth2.0 機密クライアント
  • OAuth APIsBusiness APIs 両API製品のDefault Planにサブスクライブ
  • Hello APIを呼び出すためのaccess_tokenの発行を受ける
  • Token Management APIsにはサブスクライブしないため、トークン失効処理を行うことはできない
  • 本検証ではcurlコマンドで代用

外部のクライアント・アプリケーションに相当するコンポーネントです。OAuth2.0のフローに則ってaccess_tokenの発行を受けます。

Privileged Client

  • OAuth2.0 機密クライアント
  • Token Management APIsAPI製品のDefault Planにサブスクライブ
  • 任意のresource_ownerが任意のclientに発行したaccess_tokenrefresh_tokenを失効させることができる特権を持つ

管理者向けのセキュリティ基盤等、社内の周辺システムに相当するコンポーネントです。本シナリオではPrivileged ClientOAuth Internal APIを使って、Hello App向けに発行されたaccess_tokenを失効(無効化)することが目標となります。

OAuth Provider API

OAuth External API

  • 外部向けのOAuth Provider API
  • authorization_endpoint (/authorize)token_endpoint (/token) を提供
  • introspection_endpoint (/introspect)は説明の都合上提供しているが、必須では無い
  • Enable Revocation のトグルを有効化
  • 認証URLとしてAuthentication Service (/user)を利用してresource_ownerを認証する
  • 認証URLからの応答にAPI-Authenticated-Credentialヘッダーが含まれた場合、このヘッダー値をaccess_tokenおよびrefresh_tokenresource_ownerとする
  • 本シナリオにおいてトークン発行機能を担う唯一のコンポーネント
Design

主な設定項目は以下の通りです。

oauth-external-api.yaml(抜粋)
info:
  x-ibm-name: oauth-external-api
  title: OAuth External API
basePath: /ext
paths:
  /oauth2/authorize:
    get:
      summary: endpoint for Authorization Code and Implicit grants
    post:
      summary: submit approval to authorization code or access token
  /oauth2/token:
    post:
      summary: Request Access Tokens
  /oauth2/introspect:
    post:
      summary: Introspect a given access_token supported
x-ibm-configuration:
  oauth2:
    scopes:
      scope1: Description 1
      scope2: Description 2
      scope3: Description 3
    identity-extraction:
      type: basic
    authentication:
      x-ibm-authentication-url:
        url: $(catalog-location)/authenticate/user
    authorization:
      type: authenticated
    access-token:
      ttl: 3600
    refresh-token:
      count: 2048
      ttl: 2682000
    revocation:
      type: gateway
  type: oauth

OAuth Internal API

  • 内部システム向けのOAuth Provider API
  • token_management_endpoint (/issued) を提供
  • introspection_endpoint (/introspect)は説明の都合上提供しているが、必須では無い
  • 認証URLとしてAuthentication Service (/system)を利用する
  • 認証URLからの応答にAPI-Authenticated-Credentialヘッダーが含まれた場合、このヘッダー値をresource_ownerとして振る舞う
    • つまりPriviledged Systemは認証URLからのAPI-Authenticated-Credential次第で任意のresource_ownerとして権限を行使可能
  • 本シナリオにおいてトークン削除機能を担う唯一のコンポーネントであり、本検証の主役
oauth-internal-api.yaml(抜粋)
info:
  x-ibm-name: oauth-internal-api
  title: OAuth Internal API
basePath: /int
paths:
  /oauth2/authorize:
    get:
      summary: endpoint for Authorization Code and Implicit grants
    post:
      summary: submit approval to authorization code or access token
  /oauth2/token:
    post:
      summary: Request Access Tokens
  /oauth2/issued:
    get:
      summary: Returns list of permission granted to the owner
    delete:
      summary: Revoke an application/client permission by the authorized owner
  /oauth2/introspect:
    post:
      summary: Introspect a given access_token supported
x-ibm-configuration:
  oauth2:
    scopes: {}
    identity-extraction:
      type: basic
    authentication:
      x-ibm-authentication-url:
        url: $(catalog-location)/authenticate/system
    authorization:
      type: authenticated
    access-token:
      ttl: 3600
    refresh-token:
      count: 2048
      ttl: 2682000
    revocation:
      type: gateway
  type: oauth

Authorization URL

IBM API Connectで定義された認証URLを実装したエンドポイント。通常はGateway外に別途構築されますが、本稿のサンプルでは簡単のためGateway上に作ります。

Authentication Service (/user)

  • Authorizationヘッダーの情報を元にユーザー認証を行い、認証成功時にはHTTPステータス・コード 200で応答する
  • 今回は固定のパスワード文字列user-passwordでの認証とする
  • 認証されたユーザーIDを元にcn=<user-id>,o=sampleの形式でサブジェクト文字列を生成し、応答のAPI-Authenticated-Credentialヘッダーに含める

Authentication Service (/system)

  • IBM API Connectで定義された認証URLを実装したエンドポイント
  • Authorizationヘッダーの情報を元に認証を行い、認証成功時にはHTTPステータス・コード 200で応答する
  • 今回はどのようなパスワードでも無条件で認証OKとする
  • 認証されたユーザーIDを元にcn=<user-id>,o=sampleの形式でサブジェクト文字列を生成し、応答のAPI-Authenticated-Credentialヘッダーに含める
  • 今回の検証の影の主役

Design

主な設定項目は以下の通りです。

hello-api.yaml(抜粋)
info:
  x-ibm-name: authentication-service
  title: Authentication Service
basePath: /authenticate
securityDefinitions: {}
paths:
  /user:
    get:
      parameters:
        - name: Authorization
          type: string
          required: true
          in: header
  /system:
    get:
    parameters:
      - name: Authorization
        type: string
        required: true
        in: header
Assembly

今回はoperation-switchgatewayscriptポリシーを組み合わせて実装しています。

image.png

extract-identity
var authHeaderStr = apim.getvariable('request.headers.authorization');
if (authHeaderStr) {
    var authHeaders = authHeaderStr.split(' ');
    if (authHeaders.length >= 2 && authHeaders[0] == 'Basic') {  
        // authHeaderが存在し、Basicスキームの場合のみ、base64デコードを実施
        var authCreds = new Buffer(authHeaders[1], 'base64').toString('ascii').split(':');
        var credential = {
            name:authCreds[0],
            secret:authCreds[1],
            validated:false
        }
    }
}
if (credential) {
    apim.setvariable('authorization-credential', credential);
} else {
    apim.error('ValidationFailure', 400, 'Bad Request', 'Unable to parse Authorization Header.');
}
validate-user
var credential = apim.getvariable('authorization-credential');

if (credential && credential.secret=='user-password') {
    credential.validated = true;
    apim.setvariable('authorization-credential', credential);
}
validate-system
var credential = apim.getvariable('authorization-credential');

if (true) {
    credential.validated = true;
    apim.setvariable('authorization-credential', credential);
}
set-response
var credential= apim.getvariable('authorization-credential');

if (credential.validated) {
    credential.subject = 'cn='+credential.name+',o=sample';
    credential.secret='*******';

    // 応答の書き出し処理
    apim.setvariable('message.headers.API-Authenticated-Credential', credential.subject);
    session.output.write(credential);
    apim.output('application/json');
} else {
    apim.error('ValidationFailure', 403, 'Forbidden', 'User credential validateion failed.');
}

動作確認

サブスクリプションの確認

事前に二つのクライアントを登録し、client_id,client_secretの発行とサブスクリプションを完了しておきます。

クライアント登録状況
image.png

サブスクリプション状況
image.png

access_token の発行

面倒なのでPasswordフローを使います :stuck_out_tongue:

POST_/ext/token
C:\Users\me>set catalog_location=https://<domain>/<provider-org>/<catalog>
C:\Users\me>set client_id=54d0ae98-78c9-4300-9a24-c1acf606de67
C:\Users\me>set client_secret=***********************************
C:\Users\me>set user_id=user01
C:\Users\me>set user_secret=user-password
C:\Users\me>set oauth_scope=scope1

C:\Users\me> curl -k -i -d "grant_type=password&client_id=%client_id%&client_secret=%client_secret%&scope=%oauth_scope%&username=%user_id%&password=%user_secret%" -H "Content-Type: application/x-www-form-urlencoded" -X POST "%catalog_location%/ext/oauth2/token"
HTTP/1.1 200 OK
X-Backside-Transport: FAIL FAIL
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json
Cache-Control: private, no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'
Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST
Access-Control-Allow-Credentials: false

{ "token_type":"bearer", "access_token":"AAIkNTRkMGFlOTgtNzhjOS00MzAwLTlhMjQtYzFhY2Y2MDZkZTY3gDh9eHOEulPLXISXeKmKIloQylIakqtodiZebcBegLumWkZIp4InjHxDgoY0RXokPAyi5B0Nk0YSAT1Zlls3YfWm3HEd9BadFJbrPTLoQ7RsuyBF2fpqP08KTg3UYPnv", "expires_in":3600, "consented_on":1506477368, "scope":"scope1", refresh_token":"AAJUpJXZBfoOpp3-Z9i0LD_yn_XZgnHxMtlna4bnt21B8ke-DDcdKkVKtMy2xCEdRnplTR2GLobsJ_hdIbsl7McLXqzFFTyLRnbjavecSTGUlj8ReE5rlZtKWiqaEGJoFCOaNSUJMw0m89wUr3swPmps", "refresh_token_expires_in":2682000 }

access_token の確認

Introspectionを使ってトークンの内容を確認すると、subject cn=user01,o=sampleに対して発行されていることが分かります。

POST_/ext/introspect
C:\Users\me>set catalog_location=https://<domain>/<provider-org>/<catalog>
C:\Users\me>set client_id=54d0ae98-78c9-4300-9a24-c1acf606de67
C:\Users\me>set client_secret=***********************************
C:\Users\me>set oauth_access_token=AAIkNTRkMGFlOTgtNzhjOS00MzAwLTlhMjQtYzFhY2Y2MDZkZTY3gDh9eHOEulPLXISXeKmKIloQylIakqtodiZebcBegLumWkZIp4InjHxDgoY0RXokPAyi5B0Nk0YSAT1Zlls3YfWm3HEd9BadFJbrPTLoQ7RsuyBF2fpqP08KTg3UYPnv
C:\Users\me>set token_type_hint=access_token

C:\Users\me>curl -k -i -d "token=%oauth_access_token%token_type_hint=%token_type_hint%" -H "Content-Type: application/x-www-form-urlencoded" -H "X-IBM-Client-Id: %client_id%" -H "X-IBM-Client-Secret: %client_secret%"  -X POST "%catalog_location%/ext/oauth2/introspect"
HTTP/1.1 200 OK
X-Backside-Transport: FAIL FAIL
Connection: Keep-Alive
Transfer-Encoding: chunked
Cache-Control: private, no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'
Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST
Access-Control-Allow-Credentials: false
Content-Type: application/json; charset=UTF-8

{ "active":true, "token_type":"bearer", "client_id":"54d0ae98-78c9-4300-9a24-c1acf606de67", "username":"cn=user01,o=sample", "sub":"cn=user01,o=sample", "exp":1506480968, "expstr":"2017-09-27T02:56:08Z", "iat":1506477368, "nbf":1506477368,"nbfstr":"2017-09-27T01:56:08Z", "scope":"scope1", "miscinfo":"[r:gateway]", "consented_on":1506477368, "consented_on_str":"2017-09-27T01:56:08Z", "grant_type":"password", "client_name":"Hello App" }

Hello API の呼び出し

access_tokenを使ってHello APIの呼び出しが成功することを確認します。

GET_/message/hello
C:\Users\me>set catalog_location=https://<domain>/<provider-org>/<catalog>
C:\Users\me>set client_id=54d0ae98-78c9-4300-9a24-c1acf606de67
C:\Users\me>set oauth_access_token=AAIkNTRkMGFlOTgtNzhjOS00MzAwLTlhMjQtYzFhY2Y2MDZkZTY3gDh9eHOEulPLXISXeKmKIloQylIakqtodiZebcBegLumWkZIp4InjHxDgoY0RXokPAyi5B0Nk0YSAT1Zlls3YfWm3HEd9BadFJbrPTLoQ7RsuyBF2fpqP08KTg3UYPnv

C:\Users\me>curl -k -i -H "Authorization: Bearer %oauth_access_token%" -H "X-IBM-Client-Id: %client_id%" -X GET "%catalog_location%/message/hello"
HTTP/1.1 200 OK
X-Backside-Transport: OK OK
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: unknown
Date: Wed, 27 Sep 2017 02:16:11 GMT
X-Global-Transaction-ID: 196c556559cb09eb0018afc1
User-Agent: IBM-APIConnect/5.0
Accept: */*
Via: 1.1 AQAAAIAXUgo-
X-Client-IP: 192.168.75.1
IBM-App-User: cn=user01,o=sample
Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Globa
l-Transaction-ID
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: false

{"message":"Hello World!"}

access_tokenの取り消し

Internal OAuth ProviderをPriviledged Clientのclient_id,client_secretで呼び出すことで、Hello Appに発行されたaccess_tokenを取り消します。この際、Hello Appやresource_owner (user01)のパスワードは必要ありません(ブランクでOK)。

DELETE_/int/issued
C:\Users\me>set catalog_location=https://<domain>/<provider-org>/<catalog>
C:\Users\me>set client_id=2779c8e6-105f-40c2-b41a-4ad44d6ef34e
C:\Users\me>set client_secret=******************************
C:\Users\me>set target_user_id=user01
C:\Users\me>set target_client_id=54d0ae98-78c9-4300-9a24-c1acf606de67

C:\Users\me>curl -k -i -u "%target_user_id%:" -H "X-IBM-Client-Id: %client_id%" -H "X-IBM-Client-Secret: %client_secret%" -X DELETE "%catalog_location%/int/oauth2/issued?client-id=%target_client_id%"
HTTP/1.1 200 OK
X-Backside-Transport: FAIL FAIL
Connection: Keep-Alive
Transfer-Encoding: chunked
Cache-Control: private, no-store, no-cache, must-revalidate
Pragma: no-cache
Content-Security-Policy: default-src 'self'; style-src 'unsafe-inline'
Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: DELETE
Access-Control-Allow-Credentials: false
Content-Type: application/json; charset=UTF-8

{ "status":"success" }

access_tokenの取り消し確認

再度access_tokenでHello APIを呼び出すと、失敗します。

GET_/message/hello
C:\Users\me>set catalog_location=https://<domain>/<provider-org>/<catalog>
C:\Users\me>set client_id=54d0ae98-78c9-4300-9a24-c1acf606de67
C:\Users\me>set oauth_access_token=AAIkNTRkMGFlOTgtNzhjOS00MzAwLTlhMjQtYzFhY2Y2MDZkZTY3gDh9eHOEulPLXISXeKmKIloQylIakqtodiZebcBegLumWkZIp4InjHxDgoY0RXokPAyi5B0Nk0YSAT1Zlls3YfWm3HEd9BadFJbrPTLoQ7RsuyBF2fpqP08KTg3UYPnv

C:\Users\me>curl -k -i -H "Authorization: Bearer %oauth_access_token%" -H "X-IBM-Client-Id: %client_id%" -X GET "%catalog_location%/message/hello"
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: false
Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID
WWW-Authenticate: Bearer error="invalid_token",error_description="permission is revoked or it cannot be reused"
X-Backside-Transport: FAIL FAIL
Connection: close

{ "httpCode":"401", "httpMessage":"Unauthorized", "moreInformation":"This server could not verify that you are authorized to access the URL" }

参考情報

IBM developerWorks Blog - Securing APIs using OAuth in API Connect – Video Tutorial

IBM developerWorks - IBM API Connect OAuth構成概要と構成例

IBM Knowledge Center - Authentication URL

IBM Knowledge Center - GatewayScript

IBM Knowledge Center - API Connect context variables

コード

今回作成したコードがこちらです。catalog-locationを適当な値に置き換えてご利用ください。

x-ibm-configuration:
  properties:
    catalog-location:
      value: "https://<domain>/<provider-org>/<catalog>"

API YAML

hello-api

hello-api_1.0.0.yaml
---
swagger: "2.0"
info:
  x-ibm-name: "hello-api"
  title: "Hello API"
  version: "1.0.0"
schemes:
- "https"
host: "$(catalog.host)"
basePath: "/message"
consumes:
- "application/json"
produces:
- "application/json"
securityDefinitions:
  oauth:
    type: "oauth2"
    description: ""
    flow: "accessCode"
    scopes:
      scope1: ""
    authorizationUrl: "$(catalog-location)/ext/authorize"
    tokenUrl: "$(catalog-location)/ext/token"
    x-tokenIntrospect:
      url: ""
  clientIdHeader:
    type: "apiKey"
    in: "header"
    name: "X-IBM-Client-Id"
security:
- oauth:
  - "scope1"
  clientIdHeader: []
x-ibm-configuration:
  testable: true
  enforced: true
  cors:
    enabled: true
  assembly:
    execute:
    - set-variable:
        title: "set-message"
        actions:
        - set: "message.body"
          value: "{\"message\":\"Hello World!\"}"
        version: "1.0.0"
  phase: "realized"
  properties:
    catalog-location:
      value: "https://<domain>/<provider-org>/<catalog>"
      description: ""
      encoded: false
paths:
  /hello:
    get:
      responses:
        200:
          description: "200 OK"
definitions: {}
tags: []

authentication-service

authentication-service_1.0.0.yaml
---
swagger: "2.0"
info:
  x-ibm-name: "authentication-service"
  title: "Authentication Service"
  version: "1.0.0"
schemes:
- "https"
host: "$(catalog.host)"
basePath: "/authenticate"
consumes:
- "application/json"
produces:
- "application/json"
securityDefinitions: {}
x-ibm-configuration:
  testable: true
  enforced: true
  cors:
    enabled: true
  assembly:
    execute:
    - gatewayscript:
        title: "extract-identity"
        version: "1.0.0"
        source: "var authHeaderStr = apim.getvariable('request.headers.authorization');\r\
          \nif (authHeaderStr) {\r\n\tvar authHeaders = authHeaderStr.split(' ');\r\
          \n\tif (authHeaders.length >= 2 && authHeaders[0] == 'Basic') {  \r\n  \
          \      // authHeaderが存在し、Basicスキームの場合のみ、base64デコードを実施\r\n        var authCreds\
          \ = new Buffer(authHeaders[1], 'base64').toString('ascii').split(':');\r\
          \n        var credential = {\r\n            name:authCreds[0],\r\n     \
          \       secret:authCreds[1],\r\n            validated:false\r\n        }\r\
          \n\t}\r\n}\r\nif (credential) {\r\n    apim.setvariable('authorization-credential',\
          \ credential);\r\n} else {\r\n    apim.error('ValidationFailure', 400, 'Bad\
          \ Request', 'Unable to parse Authorization Header.');\r\n}\r\n"
    - operation-switch:
        title: "operation-switch"
        case:
        - operations:
          - verb: "get"
            path: "/user"
          execute:
          - gatewayscript:
              title: "validate-user"
              version: "1.0.0"
              source: "var credential = apim.getvariable('authorization-credential');\r\
                \n\r\nif (credential && credential.secret=='user-password') {\r\n\
                \    credential.validated = true;\r\n    apim.setvariable('authorization-credential',\
                \ credential);\r\n}"
        - operations:
          - verb: "get"
            path: "/system"
          execute:
          - gatewayscript:
              title: "validate-system"
              version: "1.0.0"
              source: "var credential = apim.getvariable('authorization-credential');\r\
                \n\r\nif (true) {\r\n    credential.validated = true;\r\n    apim.setvariable('authorization-credential',\
                \ credential);\r\n}"
        otherwise: []
        version: "1.0.0"
    - gatewayscript:
        title: "set-response"
        version: "1.0.0"
        source: "var credential= apim.getvariable('authorization-credential');\r\n\
          \r\nif (credential.validated) {\r\n    credential.subject = 'cn='+credential.name+',o=sample';\r\
          \n    credential.secret='*******';\r\n\r\n    // 応答の書き出し処理\r\n    apim.setvariable('message.headers.API-Authenticated-Credential',\
          \ credential.subject);\r\n    session.output.write(credential);\r\n    apim.output('application/json');\r\
          \n} else {\r\n    apim.error('ValidationFailure', 403, 'Forbidden', 'User\
          \ credential validateion failed.');\r\n}"
    - activity-log:
        title: "activity-log"
        content: "payload"
        error-content: "payload"
        version: "1.0.0"
    catch: []
  phase: "realized"
paths:
  /user:
    get:
      responses:
        200:
          description: "200 OK"
      parameters:
      - name: "Authorization"
        type: "string"
        required: true
        in: "header"
  /system:
    get:
      responses:
        200:
          description: "200 OK"
    parameters:
    - name: "Authorization"
      type: "string"
      required: true
      in: "header"
definitions: {}
tags: []
security: []

oauth-external-api

oauth-external-api_1.0.0.yaml
---
swagger: "2.0"
info:
  x-ibm-name: "oauth-external-api"
  title: "OAuth External API"
  version: "1.0.0"
schemes:
- "https"
host: "$(catalog.host)"
basePath: "/ext"
securityDefinitions:
  clientID:
    description: "application's client_id"
    in: "query"
    name: "client_id"
    type: "apiKey"
  clientIdHeader:
    in: "header"
    type: "apiKey"
    name: "X-IBM-Client-Id"
  clientSecretHeader:
    in: "header"
    type: "apiKey"
    name: "X-IBM-Client-Secret"
security:
- {}
paths:
  /oauth2/authorize:
    get:
      produces:
      - "text/html"
      summary: "endpoint for Authorization Code and Implicit grants"
      description: "description"
      parameters:
      - name: "response_type"
        in: "query"
        description: "request an authorization code or or access token (implicit)"
        required: true
        type: "string"
        enum:
        - "code"
        - "token"
      - name: "scope"
        in: "query"
        description: "Scope being requested"
        type: "string"
        required: true
      - name: "redirect_uri"
        in: "query"
        type: "string"
        description: "URI where user is redirected to after authorization"
        required: false
      - name: "state"
        in: "query"
        type: "string"
        description: "This string will be echoed back to application when user is\
          \ redirected"
        required: false
      responses:
        200:
          description: "An HTML form for authentication or authorization of this request."
        302:
          description: "Redirect to the clients redirect_uri containing one of the\
            \ following\n- **authorization code** for Authorization code grant\n-\
            \ **access token** for Implicity grant\n- **error** in case of errors,\
            \ such as the user has denied the request\n"
      security:
      - clientID: []
    post:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "text/html"
      summary: "submit approval to authorization code or access token"
      description: "Submit resource owners approval (or rejection) for the OAuth2\
        \ Server to issue an\nauthorization code or access token to the application.\n"
      security: []
      parameters:
      - name: "client_id"
        in: "formData"
        description: "application requesting the access code or token"
        required: true
        type: "string"
      - name: "scope"
        in: "formData"
        description: "requested scope of this authorization"
        required: true
        type: "string"
      - name: "resource-owner"
        in: "formData"
        description: "resource owners user name"
        required: true
        type: "string"
      - name: "redirect_uri"
        in: "formData"
        description: "URI the application is requesting this code or token to be redirected\
          \ to"
        required: true
        type: "string"
      - name: "original-url"
        in: "formData"
        description: "URL of the original authorization request"
        required: true
        type: "string"
      - name: "dp-state"
        in: "formData"
        description: "state information provided in the authorization form"
        required: true
        type: "string"
      - name: "dp-data"
        in: "formData"
        description: "state information provided in the authorization form"
        required: true
        type: "string"
      responses:
        200:
          description: "A consent form for oauth processing."
  /oauth2/token:
    post:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "application/json"
      summary: "Request Access Tokens"
      description: "This endpoint allows requesting an access token following one\
        \ of the flows below:\n- Authorization Code (exchange code for access token)\n\
        - Client Credentials (2-legged, there isnt resource owner information)\n-\
        \ Resource Owner Password Credentials (2-legged, client provides resource\
        \ owner name and password)\n- Refresh Token (exchange refresh token for a\
        \ new access code)\n\nThe table below indicates the required parameters for\
        \ each specific grant_type options.\nEmpty cells indicate a parameter is ignored\
        \ for that specific grant type.\n\nClient authentication:\n- Confidential\
        \ clients should authenticate using HTTP Basic Authentication. Alternatively,\
        \ they may post\n  their client_id and client_secret information as a formData\
        \ parameter.\n- Public clients should send their client_id as formData parameter.\n\
        \n| grant_type           | code       | client_credentials | password    |\
        \ refresh_token |\n|----------------------|------------|--------------------|-------------|---------------|\n\
        | client_id            | required*  | required*          | required*   | required*\
        \     |\n| client_secret        | required*  | required*          | required*\
        \   | required*     |\n| code                 | required   |             \
        \       |             |               |\n| redirect_uri         | required\
        \   |                    |             |               |\n| username     \
        \        |            |                    | required    |               |\n\
        | password             |            |                    | required    | \
        \              |\n| scope                |            | optional         \
        \  | optional    |               |\n| refresh_token        |            |\
        \                    |             | required      |\n\nThe implicit grant\
        \ requests, see /oauth2/authorize.\n"
      security: []
      parameters:
      - name: "grant_type"
        in: "formData"
        description: "Type of grant"
        type: "string"
        required: true
        enum:
        - "authorization_code"
        - "password"
        - "client_credentials"
        - "refresh_token"
      - name: "client_id"
        in: "formData"
        description: "Application client ID, can be provided in formData or using\
          \ HTTP Basic Authentication"
        required: false
        type: "string"
      - name: "client_secret"
        in: "formData"
        description: "Application secret, must be provided in formData or using HTTP\
          \ Basic Authentication"
        required: false
        type: "string"
      - name: "code"
        in: "formData"
        description: "Authorization code provided by the /oauth2/authorize endpoint"
        required: false
        type: "string"
      - name: "redirect_uri"
        in: "formData"
        description: "required only if the redirect_uri parameter was included in\
          \ the authorization request /oauth2/authorize; their values MUST be identical."
        required: false
        type: "string"
      - name: "username"
        in: "formData"
        type: "string"
        description: "Resource owner username"
        required: false
      - name: "password"
        in: "formData"
        type: "string"
        description: "Resource owner password"
        required: false
      - name: "scope"
        in: "formData"
        type: "string"
        description: "Scope being requested"
        required: false
      - name: "refresh_token"
        in: "formData"
        type: "string"
        description: "The refresh token that the client wants to exchange for a new\
          \ access token (refresh_token grant_type)"
        required: false
      responses:
        200:
          description: "json document containing token, etc."
          schema:
            $ref: "#/definitions/access_token_response"
        400:
          description: "json document that may contain additional details about the\
            \ failure"
  /oauth2/introspect:
    post:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "application/json"
      summary: "Introspect a given access_token supported"
      description: "This endpoint introspects a given access_token\n"
      security:
      - clientIdHeader: []
        clientSecretHeader: []
      parameters:
      - name: "token"
        in: "formData"
        description: "String value of the access_token to be introspected"
        required: true
        type: "string"
      - name: "token_type_hint"
        in: "formData"
        description: "This must contain 'access_token' to indicate the token type"
        required: true
        type: "string"
      responses:
        200:
          description: "json document containing access_token information, etc."
          schema:
            $ref: "#/definitions/introspect_response"
        401:
          description: "failure"
x-ibm-configuration:
  testable: true
  enforced: true
  phase: "realized"
  oauth2:
    client-type: "confidential"
    scopes:
      scope1: "Description 1"
      scope2: "Description 2"
      scope3: "Description 3"
    grants:
    - "application"
    - "password"
    - "accessCode"
    - "implicit"
    identity-extraction:
      type: "basic"
    authentication:
      x-ibm-authentication-url:
        url: "$(catalog-location)/authenticate/user"
        tls-profile: ""
    authorization:
      type: "authenticated"
    access-token:
      ttl: 3600
    refresh-token:
      count: 2048
      ttl: 2682000
    metadata:
      metadata-url:
        url: ""
        tls-profile: ""
    revocation:
      type: "gateway"
  cors:
    enabled: true
  type: "oauth"
  properties:
    catalog-location:
      value: "https://<domain>/<provider-org>/<catalog>"
      description: ""
      encoded: false
definitions:
  access_token_response:
    type: "object"
    additionalProperties: false
    required:
    - "token_type"
    - "access_token"
    - "expires_in"
    properties:
      token_type:
        enum:
        - "bearer"
      access_token:
        type: "string"
      expires_in:
        type: "integer"
      scope:
        type: "string"
      refresh_token:
        type: "string"
  introspect_response:
    type: "object"
    additionalProperties: false
    required:
    - "active"
    - "client_id"
    - "client_name"
    - "username"
    - "sub"
    - "exp"
    - "expstr"
    - "iat"
    - "nbf"
    - "nbfstr"
    - "scope"
    properties:
      active:
        type: "boolean"
      client_id:
        type: "string"
      client_name:
        type: "string"
      username:
        type: "string"
      sub:
        type: "string"
      exp:
        type: "string"
      expstr:
        type: "string"
      iat:
        type: "string"
      nbf:
        type: "string"
      nbfstr:
        type: "string"
      scope:
        type: "string"
      miscinfo:
        type: "string"
      consented_on:
        type: "string"
      consented_on_str:
        type: "string"
      grant_type:
        type: "string"

oauth-internal-api

oauth-internal-api_1.0.0.yaml
---
swagger: "2.0"
info:
  x-ibm-name: "oauth-internal-api"
  title: "OAuth Internal API"
  version: "1.0.0"
schemes:
- "https"
host: "$(catalog.host)"
basePath: "/int"
securityDefinitions:
  clientID:
    description: "application's client_id"
    in: "query"
    name: "client_id"
    type: "apiKey"
  clientIdHeader:
    in: "header"
    type: "apiKey"
    name: "X-IBM-Client-Id"
  clientSecretHeader:
    in: "header"
    type: "apiKey"
    name: "X-IBM-Client-Secret"
security:
- clientID: []
paths:
  /oauth2/authorize:
    get:
      produces:
      - "text/html"
      summary: "endpoint for Authorization Code and Implicit grants"
      description: "description"
      parameters:
      - name: "response_type"
        in: "query"
        description: "request an authorization code or or access token (implicit)"
        required: true
        type: "string"
        enum:
        - "code"
        - "token"
      - name: "scope"
        in: "query"
        description: "Scope being requested"
        type: "string"
        required: true
      - name: "redirect_uri"
        in: "query"
        type: "string"
        description: "URI where user is redirected to after authorization"
        required: false
      - name: "state"
        in: "query"
        type: "string"
        description: "This string will be echoed back to application when user is\
          \ redirected"
        required: false
      responses:
        200:
          description: "An HTML form for authentication or authorization of this request."
        302:
          description: "Redirect to the clients redirect_uri containing one of the\
            \ following\n- **authorization code** for Authorization code grant\n-\
            \ **access token** for Implicity grant\n- **error** in case of errors,\
            \ such as the user has denied the request\n"
      security:
      - clientID: []
    post:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "text/html"
      summary: "submit approval to authorization code or access token"
      description: "Submit resource owners approval (or rejection) for the OAuth2\
        \ Server to issue an\nauthorization code or access token to the application.\n"
      security: []
      parameters:
      - name: "client_id"
        in: "formData"
        description: "application requesting the access code or token"
        required: true
        type: "string"
      - name: "scope"
        in: "formData"
        description: "requested scope of this authorization"
        required: true
        type: "string"
      - name: "resource-owner"
        in: "formData"
        description: "resource owners user name"
        required: true
        type: "string"
      - name: "redirect_uri"
        in: "formData"
        description: "URI the application is requesting this code or token to be redirected\
          \ to"
        required: true
        type: "string"
      - name: "original-url"
        in: "formData"
        description: "URL of the original authorization request"
        required: true
        type: "string"
      - name: "dp-state"
        in: "formData"
        description: "state information provided in the authorization form"
        required: true
        type: "string"
      - name: "dp-data"
        in: "formData"
        description: "state information provided in the authorization form"
        required: true
        type: "string"
      responses:
        200:
          description: "A consent form for oauth processing."
  /oauth2/token:
    post:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "application/json"
      summary: "Request Access Tokens"
      description: "This endpoint allows requesting an access token following one\
        \ of the flows below:\n- Authorization Code (exchange code for access token)\n\
        - Client Credentials (2-legged, there isnt resource owner information)\n-\
        \ Resource Owner Password Credentials (2-legged, client provides resource\
        \ owner name and password)\n- Refresh Token (exchange refresh token for a\
        \ new access code)\n\nThe table below indicates the required parameters for\
        \ each specific grant_type options.\nEmpty cells indicate a parameter is ignored\
        \ for that specific grant type.\n\nClient authentication:\n- Confidential\
        \ clients should authenticate using HTTP Basic Authentication. Alternatively,\
        \ they may post\n  their client_id and client_secret information as a formData\
        \ parameter.\n- Public clients should send their client_id as formData parameter.\n\
        \n| grant_type           | code       | client_credentials | password    |\
        \ refresh_token |\n|----------------------|------------|--------------------|-------------|---------------|\n\
        | client_id            | required*  | required*          | required*   | required*\
        \     |\n| client_secret        | required*  | required*          | required*\
        \   | required*     |\n| code                 | required   |             \
        \       |             |               |\n| redirect_uri         | required\
        \   |                    |             |               |\n| username     \
        \        |            |                    | required    |               |\n\
        | password             |            |                    | required    | \
        \              |\n| scope                |            | optional         \
        \  | optional    |               |\n| refresh_token        |            |\
        \                    |             | required      |\n\nThe implicit grant\
        \ requests, see /oauth2/authorize.\n"
      security: []
      parameters:
      - name: "grant_type"
        in: "formData"
        description: "Type of grant"
        type: "string"
        required: true
        enum:
        - "authorization_code"
        - "password"
        - "client_credentials"
        - "refresh_token"
      - name: "client_id"
        in: "formData"
        description: "Application client ID, can be provided in formData or using\
          \ HTTP Basic Authentication"
        required: false
        type: "string"
      - name: "client_secret"
        in: "formData"
        description: "Application secret, must be provided in formData or using HTTP\
          \ Basic Authentication"
        required: false
        type: "string"
      - name: "code"
        in: "formData"
        description: "Authorization code provided by the /oauth2/authorize endpoint"
        required: false
        type: "string"
      - name: "redirect_uri"
        in: "formData"
        description: "required only if the redirect_uri parameter was included in\
          \ the authorization request /oauth2/authorize; their values MUST be identical."
        required: false
        type: "string"
      - name: "username"
        in: "formData"
        type: "string"
        description: "Resource owner username"
        required: false
      - name: "password"
        in: "formData"
        type: "string"
        description: "Resource owner password"
        required: false
      - name: "scope"
        in: "formData"
        type: "string"
        description: "Scope being requested"
        required: false
      - name: "refresh_token"
        in: "formData"
        type: "string"
        description: "The refresh token that the client wants to exchange for a new\
          \ access token (refresh_token grant_type)"
        required: false
      responses:
        200:
          description: "json document containing token, etc."
          schema:
            $ref: "#/definitions/access_token_response"
        400:
          description: "json document that may contain additional details about the\
            \ failure"
  /oauth2/issued:
    get:
      produces:
      - "application/json"
      summary: "Returns list of permission granted to the owner"
      description: "This endpoint allows the return of all the issued permission for\
        \ a given authenticated owner per owner authentication \ndefined in the x-ibm-configuration\
        \ section\n"
      security:
      - clientIdHeader: []
        clientSecretHeader: []
      responses:
        200:
          description: "json document containing issued information, etc."
          schema:
            $ref: "#/definitions/issued_responses"
        401:
          description: "failure in retreiving issued list"
    delete:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "application/json"
      summary: "Revoke an application/client permission by the authorized owner"
      description: "Revoke an application/client permission by the authorized owner"
      security:
      - clientIdHeader: []
        clientSecretHeader: []
      parameters:
      - name: "client-id"
        in: "query"
        description: "client-id is the OAuth client_id or application id to be revoked"
        required: true
        type: "string"
      responses:
        200:
          description: "OK"
  /oauth2/introspect:
    post:
      consumes:
      - "application/x-www-form-urlencoded"
      produces:
      - "application/json"
      summary: "Introspect a given access_token supported"
      description: "This endpoint introspects a given access_token\n"
      security:
      - clientIdHeader: []
        clientSecretHeader: []
      parameters:
      - name: "token"
        in: "formData"
        description: "String value of the access_token to be introspected"
        required: true
        type: "string"
      - name: "token_type_hint"
        in: "formData"
        description: "This must contain 'access_token' to indicate the token type"
        required: true
        type: "string"
      responses:
        200:
          description: "json document containing access_token information, etc."
          schema:
            $ref: "#/definitions/introspect_response"
        401:
          description: "failure"
x-ibm-configuration:
  testable: true
  enforced: true
  phase: "realized"
  oauth2:
    client-type: "confidential"
    scopes: {}
    grants:
    - "implicit"
    - "application"
    - "password"
    - "accessCode"
    identity-extraction:
      type: "basic"
    authentication:
      x-ibm-authentication-url:
        url: "$(catalog-location)/authenticate/system"
        tls-profile: ""
    authorization:
      type: "authenticated"
    access-token:
      ttl: 3600
    refresh-token:
      count: 2048
      ttl: 2682000
    revocation:
      type: "gateway"
    metadata:
      metadata-url:
        url: ""
        tls-profile: ""
  cors:
    enabled: true
  type: "oauth"
  properties:
    catalog-location:
      value: "https://<domain>/<provider-org>/<catalog>"
      description: ""
      encoded: false
definitions:
  access_token_response:
    type: "object"
    additionalProperties: false
    required:
    - "token_type"
    - "access_token"
    - "expires_in"
    properties:
      token_type:
        enum:
        - "bearer"
      access_token:
        type: "string"
      expires_in:
        type: "integer"
      scope:
        type: "string"
      refresh_token:
        type: "string"
  issued_response:
    type: "object"
    additionalProperties: false
    required:
    - "clientId"
    - "owner"
    - "scope"
    - "issuedAt"
    - "expiredAt"
    - "refreshTokenIssued"
    properties:
      clientId:
        type: "string"
      clientName:
        type: "string"
      owner:
        type: "string"
      scope:
        type: "string"
      issuedAt:
        type: "string"
      expiredAt:
        type: "string"
      refreshTokenIssued:
        type: "boolean"
      miscInfo:
        type: "string"
      consentedOn:
        type: "string"
      appId:
        type: "string"
      org:
        type: "string"
      orgId:
        type: "string"
      provider:
        type: "string"
      providerId:
        type: "string"
      catalog:
        type: "string"
      catalogId:
        type: "string"
  issued_responses:
    type: "array"
    items:
      $ref: "#/definitions/issued_response"
  introspect_response:
    type: "object"
    additionalProperties: false
    required:
    - "active"
    - "client_id"
    - "client_name"
    - "username"
    - "sub"
    - "exp"
    - "expstr"
    - "iat"
    - "nbf"
    - "nbfstr"
    - "scope"
    properties:
      active:
        type: "boolean"
      client_id:
        type: "string"
      client_name:
        type: "string"
      username:
        type: "string"
      sub:
        type: "string"
      exp:
        type: "string"
      expstr:
        type: "string"
      iat:
        type: "string"
      nbf:
        type: "string"
      nbfstr:
        type: "string"
      scope:
        type: "string"
      miscinfo:
        type: "string"
      consented_on:
        type: "string"
      consented_on_str:
        type: "string"
      grant_type:
        type: "string"

Product YAML

business-apis

business-apis_1.0.0.yaml
---
product: "1.0.0"
info:
  name: "business-apis"
  title: "Business APIs"
  version: "1.0.0"
visibility:
  view:
    enabled: true
    type: "public"
    tags: []
    orgs: []
  subscribe:
    enabled: true
    type: "authenticated"
    tags: []
    orgs: []
apis:
  hello-api-gateway:
    $ref: "hello-api_1.0.0.yaml"
plans:
  Default:
    title: "Default Plan"

backend-apis

backend-apis_1.0.0.yaml
---
product: "1.0.0"
info:
  name: "backend-apis"
  title: "Backend APIs"
  version: "1.0.0"
visibility:
  view:
    enabled: true
    type: "public"
    tags: []
    orgs: []
  subscribe:
    enabled: true
    type: "authenticated"
    tags: []
    orgs: []
apis:
  authentication-service:
    $ref: "authentication-service_1.0.0.yaml"
plans:
  default:
    title: "Default Plan"
    description: "Default Plan"
    approval: false
    rate-limit:
      hard-limit: false
      value: "100/hour"

oauth-apis

oauth-apis_1.0.0.yaml
---
product: "1.0.0"
info:
  name: "oauth-apis"
  title: "OAuth APIs"
  version: "1.0.0"
visibility:
  view:
    enabled: true
    type: "public"
    tags: []
    orgs: []
  subscribe:
    enabled: true
    type: "authenticated"
    tags: []
    orgs: []
apis:
  oauth-apis:
    $ref: "oauth-external-api_1.0.0.yaml"
plans:
  default:
    title: "Default Plan"
    description: "Default Plan"
    approval: false
    rate-limit:
      hard-limit: false
      value: "100/hour"

token-management-apis

token-management-apis_1.0.0.yaml
---
product: "1.0.0"
info:
  name: "token-management-apis"
  title: "Token Management APIs"
  version: "1.0.0"
visibility:
  view:
    enabled: true
    type: "public"
    tags: []
    orgs: []
  subscribe:
    enabled: true
    type: "authenticated"
    tags: []
    orgs: []
apis:
  oauth-internal-api:
    $ref: "oauth-internal-api_1.0.0.yaml"
plans:
  default:
    title: "Default Plan"
    description: "Default Plan"
    approval: false
    rate-limit:
      hard-limit: false
      value: "100/hour"