Tenant Contract Setup

OPTiM Store のユーザー企業様が御社サービスを利用開始する際に、初回に OPTiM Store と御社サービスの間でテナント契約開始処理を行います。

その際、御社サービス側に Tenant Contract API Endpoint をご用意いただく必要があります。当該 Endpoint URL は、OPTiM Store へのアプリ登録時に設定します。

ここで交換された鍵は、後述の SCIM Client 登録や OpenID Connect Client / SAML Service Provider 登録を API 経由で自動化する際に必要になります。

SCIM Client 登録および OpenID Connect Client / SAML Service Provider 登録を手動で行う場合には、この API Endpoint は不要です。

事前設定項目

以降、本ドキュメントでは、OPTiM Store の URL を以下と仮定して説明を進めます。(実際には OPTiM Store の販社様ごとに異なる URL となります)

また、OPTiM Store では、場面に応じて以下の3つのサーバーが連動して動くようになっています。

OPTiM Store へのアプリ登録時に、御社サービス側の Tenant Contract API Endpoint を設定していただく必要があります。(HTTPS 必須)

以降、本ドキュメントでは、御社サービス側の Tenant Contract API Endpoint を以下の値と仮定して説明を進めます。

また OPTiM Store から送られてくる Tenant Contract Setup Request の署名検証に利用するため、契約時に送付される URL から OPTiM Store の Public Key を取得しておいてください。

以降、本ドキュメントでは、OPTiM Store の Public key URL を以下の値と仮定して説明を進めます。

この鍵は定期的に更新される可能性があるため、後述の署名付き JSON データに含まれる 鍵 ID (kid) をもとに、必要に応じて上記 URL から鍵を動的に取得できるような実装をお願いします。

なお、鍵更新は必ず kid の変更を伴うため、未知の kid を受け取るまでは御社サービス側で上記 URL から取得した鍵を無期限に cache していただいて構いません。

OPTiM Store -> 御社サービスへのテナント契約開始リクエスト

契約開始時に、OPTiM Store から御社サービスの Tenant Contract API Endpoint に以下のような JSON データを送ります。(実際には以下の JSON に JWS 仕様にしたがって署名したデータが送られます)

{
  "iss": "https://store-xyz.optim.co.jp",
  "aud": "https://your-service.example.com/contract/api",
  "company": {
    "id": "f46f9970c914af9a22491402aa9eedc4a9645f2d2a303a39867c304ba461c47f",
    "name": "株式会社 XXX"
  },
  "license": {
    "pool": 10
  },
  "contract_jwk": {
    "kid": "0-B5HVIdHUu8cPMKSn3jwlA0ctof_ajNUoblnEjYWrI",
    "kty": "RSA",
    "e": "AQAB",
    "n": "6JNHFtl3atob3u45Q23faw2mEAP4lOZO5PN5h-8Q_NfqRi2ADzHAdAM9BSEzSwqplAWKkr6Y9PLrWKvsgF6q6N-f3NHczaa2vfqt5CW4GiWBvPw2hZ9y2B2EzB76fIngTn43kE71p5u6jIWdflkmTEmKMVEmdC7KVRxoMQQkXiG-QU7Rw_Frr_OtGOnXXhxTrz1tyhm9FjmU1ImHZm2Rib5NEX4ghAAIJZswN8zN5tEyJiGg1aJ4BfYrS9AXEXQHaYJX9Kzbv-UqYGIiFHcP1MmK4ZqQ5Kp8x_k0IDA5DUc3ecDw0Axm_Arl1BP94YVPQob2D1Xgr6qYt-2Xp0uxyw",
    "use": "sig"
  },
  "iat": 1464053706,
  "exp": 1464054306
}

リクエスト JSON の各要素について

key description
iss リクエスト発行者 (issuer) の識別子です。OPTiM Store の URL となります。
aud リクエスト受信者 (audience) の識別子です。御社サービスの Tenant Contract API Endpoint URL となります。
company 実際に御社サービスを利用される OPTiM Store ユーザー企業様の情報です。特に必要が無い場合は無視していただいても結構です。
license 購入ライセンス数等の情報です。現時点ではライセンス数のみを含みます。
contract_jwk 各テナント契約ごとに OPTiM 側で生成される RSA 鍵ペアの公開鍵です。契約開始直後 Provisioning の自動設定を行う際に必要になります。
iat リクエスト生成日時 (issued_at) を示す Unix Timestamp です。
exp リクエストの有効期限 (expires_at) を示す Unix Timestamp です。

注) contract_jwk はテナント契約ごとに個別に生成される鍵であり、https://store-xyz-api.optim.co.jp/api/idp/contract/jwks に公開されている OPTiM Store の公開鍵は異なります。contract_jwkkid は JWK Thumbprint 値となっています。この contract_jwk およびその kid 値は後述の契約内容変更通知リクエストの検証のために利用することになるため、御社サービス側で当該テナント契約と紐付けて保存しておいてください。

署名付きリクエスト

実際の POST Body は、上記の JSON を前述の OPTiM Store の Public Key のペアとなる Private Key で署名されたものを contract_statement という key に wrap した JSON データになります。

署名に使用された鍵の ID (kid) は、JWS Header の kid に指定されています。

JWS Header 例

{
  "typ": "JWT",
  "alg": "RS256",
  "kid": "m5e8iciYCah6CCKyoK0klpYfr36guEdI8qxA10tAMho"
}

JWS Payload 例

{
  "iss": "https://store-xyz.optim.co.jp",
  "aud": "https://your-service.example.com/contract/api",
  "company": {
    "id": "f46f9970c914af9a22491402aa9eedc4a9645f2d2a303a39867c304ba461c47f",
    "name": "株式会社 XXX"
  },
  "license": {
    "pool": 10
  },
  "contract_jwk": {
    "kid": "0-B5HVIdHUu8cPMKSn3jwlA0ctof_ajNUoblnEjYWrI",
    "kty": "RSA",
    "e": "AQAB",
    "n": "6JNHFtl3atob3u45Q23faw2mEAP4lOZO5PN5h-8Q_NfqRi2ADzHAdAM9BSEzSwqplAWKkr6Y9PLrWKvsgF6q6N-f3NHczaa2vfqt5CW4GiWBvPw2hZ9y2B2EzB76fIngTn43kE71p5u6jIWdflkmTEmKMVEmdC7KVRxoMQQkXiG-QU7Rw_Frr_OtGOnXXhxTrz1tyhm9FjmU1ImHZm2Rib5NEX4ghAAIJZswN8zN5tEyJiGg1aJ4BfYrS9AXEXQHaYJX9Kzbv-UqYGIiFHcP1MmK4ZqQ5Kp8x_k0IDA5DUc3ecDw0Axm_Arl1BP94YVPQob2D1Xgr6qYt-2Xp0uxyw",
    "use": "sig"
  },
  "iat": 1464053706,
  "exp": 1464054306
}

署名付き JSON 例

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im01ZThpY2lZQ2FoNkNDS3lvSzBrbHBZZnIzNmd1RWRJOHF4QTEwdEFNaG8ifQ.eyJpc3MiOiJodHRwczovL3N0b3JlLm9wdGltLmNvLmpwIiwiYXVkIjoiaHR0cHM6Ly95b3VyLXNlcnZpY2UuZXhhbXBsZS5jb20vY29udHJhY3QvYXBpIiwiY29tcGFueSI6eyJpZCI6ImY0NmY5OTcwYzkxNGFmOWEyMjQ5MTQwMmFhOWVlZGM0YTk2NDVmMmQyYTMwM2EzOTg2N2MzMDRiYTQ2MWM0N2YiLCJuYW1lIjoiIFhYWCIsIndlYnNpdGUiOiJodHRwczovL3h4eC5leGFtcGxlLmNvbSIsImNvbnRhY3QiOiJzeXNhZG1pbkB4eHguZXhhbXBsZS5jb20ifSwibGljZW5jZSI6eyJwb29sIjoxMH0sImNvbnRyYWN0X2p3ayI6eyJraWQiOiIwLUI1SFZJZEhVdThjUE1LU24zandsQTBjdG9mX2FqTlVvYmxuRWpZV3JJIiwia3R5IjoiUlNBIiwiZSI6IkFRQUIiLCJuIjoiNkpOSEZ0bDNhdG9iM3U0NVEyM2ZhdzJtRUFQNGxPWk81UE41aC04UV9OZnFSaTJBRHpIQWRBTTlCU0V6U3dxcGxBV0trcjZZOVBMcldLdnNnRjZxNk4tZjNOSGN6YWEydmZxdDVDVzRHaVdCdlB3MmhaOXkyQjJFekI3NmZJbmdUbjQza0U3MXA1dTZqSVdkZmxrbVRFbUtNVkVtZEM3S1ZSeG9NUVFrWGlHLVFVN1J3X0Zycl9PdEdPblhYaHhUcnoxdHlobTlGam1VMUltSFptMlJpYjVORVg0Z2hBQUlKWnN3Tjh6TjV0RXlKaUdnMWFKNEJmWXJTOUFYRVhRSGFZSlg5S3pidi1VcVlHSWlGSGNQMU1tSzRacVE1S3A4eF9rMElEQTVEVWMzZWNEdzBBeG1fQXJsMUJQOTRZVlBRb2IyRDFYZ3I2cVl0LTJYcDB1eHl3IiwidXNlIjoic2lnIn0sImlhdCI6MTQ2NDA1MzcwNiwiZXhwIjoxNDY0MDU0MzA2fQ.O5FiomvNB6SiSGSlW1TAG4Q_Kesg7s7FfmraAb6G4CIgIq4UHCT8R7CT-ayS8GVT_GB4b40Ruc88Te-baZ2qC0vh3ceIzlNMep8mp4vLhzsxj7MgqGNyKiTjzvaTwLtRLD7Uc_-2-OVPgCha1545BlFxLbcPKRjxaNc5QmmBA6yi0kmCsidk1lfWEKx0vGZZih77g4Wn_k9LMQ4aB4OOkLvpaO0Afi0D-nv91xy2QI-pMDTphNpSqZkZBsNvvtyuEHHbo1qhrUJ81NDdALPUoMcws8TKq7pB5j_NUEY7sfXqgR0FQ5WvBult43Xx7RHvlNqOhpxw9bUQpWBcPstcHQ

Request Body 例

{
  "contract_statement": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im01ZThpY2lZQ2FoNkNDS3lvSzBrbHBZZnIzNmd1RWRJOHF4QTEwdEFNaG8ifQ.eyJpc3MiOiJodHRwczovL3N0b3JlLm9wdGltLmNvLmpwIiwiYXVkIjoiaHR0cHM6Ly95b3VyLXNlcnZpY2UuZXhhbXBsZS5jb20vY29udHJhY3QvYXBpIiwiY29tcGFueSI6eyJpZCI6ImY0NmY5OTcwYzkxNGFmOWEyMjQ5MTQwMmFhOWVlZGM0YTk2NDVmMmQyYTMwM2EzOTg2N2MzMDRiYTQ2MWM0N2YiLCJuYW1lIjoiIFhYWCIsIndlYnNpdGUiOiJodHRwczovL3h4eC5leGFtcGxlLmNvbSIsImNvbnRhY3QiOiJzeXNhZG1pbkB4eHguZXhhbXBsZS5jb20ifSwibGljZW5jZSI6eyJwb29sIjoxMH0sImNvbnRyYWN0X2p3ayI6eyJraWQiOiIwLUI1SFZJZEhVdThjUE1LU24zandsQTBjdG9mX2FqTlVvYmxuRWpZV3JJIiwia3R5IjoiUlNBIiwiZSI6IkFRQUIiLCJuIjoiNkpOSEZ0bDNhdG9iM3U0NVEyM2ZhdzJtRUFQNGxPWk81UE41aC04UV9OZnFSaTJBRHpIQWRBTTlCU0V6U3dxcGxBV0trcjZZOVBMcldLdnNnRjZxNk4tZjNOSGN6YWEydmZxdDVDVzRHaVdCdlB3MmhaOXkyQjJFekI3NmZJbmdUbjQza0U3MXA1dTZqSVdkZmxrbVRFbUtNVkVtZEM3S1ZSeG9NUVFrWGlHLVFVN1J3X0Zycl9PdEdPblhYaHhUcnoxdHlobTlGam1VMUltSFptMlJpYjVORVg0Z2hBQUlKWnN3Tjh6TjV0RXlKaUdnMWFKNEJmWXJTOUFYRVhRSGFZSlg5S3pidi1VcVlHSWlGSGNQMU1tSzRacVE1S3A4eF9rMElEQTVEVWMzZWNEdzBBeG1fQXJsMUJQOTRZVlBRb2IyRDFYZ3I2cVl0LTJYcDB1eHl3IiwidXNlIjoic2lnIn0sImlhdCI6MTQ2NDA1MzcwNiwiZXhwIjoxNDY0MDU0MzA2fQ.O5FiomvNB6SiSGSlW1TAG4Q_Kesg7s7FfmraAb6G4CIgIq4UHCT8R7CT-ayS8GVT_GB4b40Ruc88Te-baZ2qC0vh3ceIzlNMep8mp4vLhzsxj7MgqGNyKiTjzvaTwLtRLD7Uc_-2-OVPgCha1545BlFxLbcPKRjxaNc5QmmBA6yi0kmCsidk1lfWEKx0vGZZih77g4Wn_k9LMQ4aB4OOkLvpaO0Afi0D-nv91xy2QI-pMDTphNpSqZkZBsNvvtyuEHHbo1qhrUJ81NDdALPUoMcws8TKq7pB5j_NUEY7sfXqgR0FQ5WvBult43Xx7RHvlNqOhpxw9bUQpWBcPstcHQ"
}

OPTiM Store では、ユーザー企業様が御社サービスを利用開始する際に、上記 JSON データを御社サービスの Tenant Contract API Endpoint に HTTP POST で送信します。

リクエスト署名の検証

以下では Ruby の json-jwt gem を利用して、上記署名付き JSON リクエストの署名検証を行っています。

require 'json/jwt'

contract_statement = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6Im01ZThpY2lZQ2FoNkNDS3lvSzBrbHBZZnIzNmd1RWRJOHF4QTEwdEFNaG8ifQ.eyJpc3MiOiJodHRwczovL3N0b3JlLm9wdGltLmNvLmpwIiwiYXVkIjoiaHR0cHM6Ly95b3VyLXNlcnZpY2UuZXhhbXBsZS5jb20vY29udHJhY3QvYXBpIiwiY29tcGFueSI6eyJpZCI6ImY0NmY5OTcwYzkxNGFmOWEyMjQ5MTQwMmFhOWVlZGM0YTk2NDVmMmQyYTMwM2EzOTg2N2MzMDRiYTQ2MWM0N2YiLCJuYW1lIjoiIFhYWCIsIndlYnNpdGUiOiJodHRwczovL3h4eC5leGFtcGxlLmNvbSIsImNvbnRhY3QiOiJzeXNhZG1pbkB4eHguZXhhbXBsZS5jb20ifSwibGljZW5jZSI6eyJwb29sIjoxMH0sImNvbnRyYWN0X2p3ayI6eyJraWQiOiIwLUI1SFZJZEhVdThjUE1LU24zandsQTBjdG9mX2FqTlVvYmxuRWpZV3JJIiwia3R5IjoiUlNBIiwiZSI6IkFRQUIiLCJuIjoiNkpOSEZ0bDNhdG9iM3U0NVEyM2ZhdzJtRUFQNGxPWk81UE41aC04UV9OZnFSaTJBRHpIQWRBTTlCU0V6U3dxcGxBV0trcjZZOVBMcldLdnNnRjZxNk4tZjNOSGN6YWEydmZxdDVDVzRHaVdCdlB3MmhaOXkyQjJFekI3NmZJbmdUbjQza0U3MXA1dTZqSVdkZmxrbVRFbUtNVkVtZEM3S1ZSeG9NUVFrWGlHLVFVN1J3X0Zycl9PdEdPblhYaHhUcnoxdHlobTlGam1VMUltSFptMlJpYjVORVg0Z2hBQUlKWnN3Tjh6TjV0RXlKaUdnMWFKNEJmWXJTOUFYRVhRSGFZSlg5S3pidi1VcVlHSWlGSGNQMU1tSzRacVE1S3A4eF9rMElEQTVEVWMzZWNEdzBBeG1fQXJsMUJQOTRZVlBRb2IyRDFYZ3I2cVl0LTJYcDB1eHl3IiwidXNlIjoic2lnIn0sImlhdCI6MTQ2NDA1MzcwNiwiZXhwIjoxNDY0MDU0MzA2fQ.O5FiomvNB6SiSGSlW1TAG4Q_Kesg7s7FfmraAb6G4CIgIq4UHCT8R7CT-ayS8GVT_GB4b40Ruc88Te-baZ2qC0vh3ceIzlNMep8mp4vLhzsxj7MgqGNyKiTjzvaTwLtRLD7Uc_-2-OVPgCha1545BlFxLbcPKRjxaNc5QmmBA6yi0kmCsidk1lfWEKx0vGZZih77g4Wn_k9LMQ4aB4OOkLvpaO0Afi0D-nv91xy2QI-pMDTphNpSqZkZBsNvvtyuEHHbo1qhrUJ81NDdALPUoMcws8TKq7pB5j_NUEY7sfXqgR0FQ5WvBult43Xx7RHvlNqOhpxw9bUQpWBcPstcHQ"

# NOTE: "kid" を取得
unverified_jwt = JSON::JWT.decode(contract_statement, :skip_verification)
kid = unverified_jwt.header[:kid]

# NOTE: "kid" に紐づく OPTiM Store Public Key を取得 (cache から取得可能な場合は skip 可能)
jwk = JSON.parse(
  HTTPClient.get_content('https://store-xyz-api.optim.co.jp/api/idp/contract/jwks')
)['keys'].detect do |jwk|
  jwk['kid'] == kid
end
public_key = JSON::JWK.new(jwk)

# NOTE: 署名検証
verified_jwt = JSON::JWT.decode(contract_statement, public_key)

その他の JWT/JWS ライブラリを利用する場合は、各ライブラリのドキュメントを参照してください。

御社サービス -> OPTiM Store へのテナント契約成立レスポンス

上記リクエストを受け取り、署名およびリクエスト内容を検証した後、契約成立のレスポンスを返してください。

以下にレスポンス例を示します。

{
  "iss": "https://your-service.example.com/contract/api",
  "aud": "https://store-xyz.optim.co.jp",
  "contract_jwk": {
    "kid": "u67PW3Rc0To3t_4fPz3Cwb0fuxICXXhI8SwBnak4C6Q",
    "kty": "RSA",
    "e": "AQAB",
    "n": "yUB7KMp_ZsEbK734sMPjRSPfM7pV0Jf5KyjkkXk5Mw6Jja8z-ZTigqaoJVcV4tFX2CyTqDmVNgzb9oQJL62otjDWRwdFL99Rdor_YsxZt2eOSnBBY8PdhjYHTJtczIXsCHtYtx0clYcjMjvwNG_FIL4UJILmiHPxMMmWxPjEtTIo0pp8KdrrGZExJp4UD7JC7nPiNdvs31GHV0IoXpxqQAatfFBwQHFG3Fxsz7JS9p3mf_VBZ2CyPsdS58NqMPQVBbyxsdbK_OiPOk-4dmlJtxZFTMjXoBg4-hL9B1rRrUrN5ttm7QjJr3wjnKUy_OdKsqjxhPaj7XV04TuxBhxzOw"
  },
  "iat": 1464053706,
  "exp": 1464054306
}

レスポンス JSON の各要素について

key description
iss レスポンス発行者 (issuer) の識別子です。御社サービスの Tenant Contract API Endpoint URL となります。
aud レスポンス受信者 (audience) の識別子です。OPTiM Store の URL となります。
contract_jwk 各テナント契約ごとに御社サービス側で生成いただく RSA 鍵ペアの公開鍵です。契約開始直後 Federation の自動設定を行う際に必要になります。
iat レスポンス生成日時 (issued_at) を示す Unix Timestamp です。
exp レスポンスの有効期限 (expires_at) を示す Unix Timestamp です。

注) contract_jwkkid は、JWK Thumbprint 仕様にしたがって生成してください。

https://tools.ietf.org/html/rfc7638

レスポンスへの署名について

現状では御社サービス側の Contract Start API Endpoint を HTTPS 必須とすることで、契約成立レスポンスへの署名は不要としています。

テナント契約成立後の処理

テナント契約が成立すると、OPTiM / 御社サービス双方で当該契約に紐づく RSA 鍵ペアが生成され、相互に公開鍵を交換完了した状態になります。

テナント契約成立後は、自信の持つテナント契約毎の秘密鍵を使って署名したリクエストを送信しあい、相手の公開鍵でその署名を検証することができるようになります。

Provisioning / Federation の各システム連携に用いる OAuth / OpenID Connect Client の自動登録には、上記鍵ペアを利用します。

プロビジョニング初期設定処理については SCIM Client Setup をご覧ください。

ID 連携初期設定処理については OpenID Connect Client Setup をご覧ください。

テナント契約内容の変更および解除

契約ライセンス数の増減等のテナント契約内容の変更は Tenant Contract Update の仕様にしたがって OPTiM Store から御社サービスに通知されます。

また契約解除通知は Tenant Contract Destroy の仕様にしたがって OPTiM Store から御社サービスに通知されます。