In-App SDK から KLON への移行
このガイドは、In-App SDK を利用していたサービスを KLON(KLON IdP / KLON Registry)ベースのフローに移行するための手順を説明します。
移行後はポケットサインアプリ上でもブラウザ上でも同じ仕組みで動作するようになります。
移行の全体像
Link v1(旧 In-App SDK)
Link v2
主な変更点
対応関係
| 従来(In-App SDK) | KLON |
|---|---|
| In-App SDK 経由の認証・権限要求 | OIDC 認可フロー(IdP) |
| In-App SDK 経由のリソース読み書き | Registry API(UserService / ClientService) |
| 独自 JWT(署名付き) | 標準 OIDC ID Token / Access Token |
getSubscriptionIdWithSignature の JWT | ID Token の sub クレーム |
大きな違い
1. 認証・リソースアクセスがバックエンド主導の OIDC フローに変わった
In-App SDK ではフロントエンドが SDK メソッド(getSubscriptionIdWithSignature -> requestPermission -> readResourceWithSignature)を逐次呼び出していました。KLON ではバックエンドで標準的な OIDC 認可フローを実行し、フロントエンドはリダイレクトのみになります。
IdP: 認証認可、トークン発行、同意画面Registry: ユーザーリソースの読み書き(ConnectRPC API)
2. SDK のリソース操作が Registry API に置き換わった
| 観点 | In-App SDK | KLON |
|---|---|---|
| ユーザーリソースの読み取り | readResourceWithSignature / readResourceRaw | RegistryUserService.ReadResourceValues API |
| ユーザーリソースの書き込み | writeResource | RegistryUserService.WriteResourceValue API |
| サービスからのリソース操作 | (SDK 経由のみ) | RegistryClientService API |
| リソース指定 | リソース ID(UUID) | id_or_alias(UUID / エイリアス両対応) |
| API 形態 | SDK メソッド呼び出し | ConnectRPC |
詳細はリソースモデルを参照してください。
3. リソースモデルが拡張された
KLON Registry は従来のリソース概念を引き継ぎつつ、次を拡張しています。
- エイリアス
string/json/binaryの値フォーマット- 算出リソース
- 検証プロセス情報
特に json と binary を明示的に分けた点は、移行時に設計見直しが必要になりやすいです。
4. 権限要求が認可リクエストに統合された
In-App SDK では requestPermission で個別に権限を要求していましたが、KLON では認可リクエスト時に authorization_details と grant_management_action で一括指定します。
- 初回要求:
authorization_details - 追加権限:
grant_management_action=merge - 既存置換:
grant_management_action=replace
SDK 関数の移行先マッピング
In-App SDK の各関数が KLON でどう変わるかの一覧です。
KLON API で代替する関数
| In-App SDK 関数 | KLON での代替 |
|---|---|
createSDKInstance | OIDC フロー + Registry API に移行。ポケットサインアプリ限定機能を使う場合のみ In-App SDK 側で使用 |
requestSubscription | scope に openid を含む認可リクエスト |
getSubscriptionIdWithSignature | ID Token の sub クレーム(= subscription_id) |
requestPermission | authorization_details で認可リクエスト時に指定 |
readResourceRaw / readResourceWithSignature | RegistryUserService API ReadResourceValues / RegistryClientService API ReadResourceValues |
readResourcesRaw / readResourcesWithSignature | 同上(複数リソース版) |
writeResource | Registry.UserService.WriteResourceValue / ClientService.WriteResourceValue |
checkCertificateRevocation | IdPUserService API CheckCertificateRevocation / IdPClientService API CheckCertificateRevocation |
getCurrentGeolocation | Web 標準 navigator.geolocation.getCurrentPosition |
share | Web 標準 navigator.share |
downloadFile | Web 標準 Blob + <a download> |
openImagePicker / openCamera | Web 標準 <input type="file"> |
ポケットサインアプリ限定の関数
ブラウザでは代替手段がなく、ポケットサインアプリ内でのみ動作する関数です。In-App SDK 経由で引き続き利用できます。
| In-App SDK 関数 | 用途 |
|---|---|
closeWindow | ウィンドウを閉じる |
openAppSettings | アプリ設定画面を開く |
requestLocalAuthentication / getLocalAuthenticationAvailability / openLocalAuthenticationSettings | 端末生体認証 |
requestAppUpdate | アプリのアップデートを要求 |
openService | 別のミニアプリを開く |
openServiceSettings / openResourceEditor | アプリ内の設定画面を開く |
readWithQrScanner | ネイティブ QR スキャナー |
前提
- KLON のサービス登録が完了していること
- KLON の OIDC クライアント ID / クライアントシークレットが発行されていること
- リダイレクト URI が設定されていること
- KLON SDK がインストールされていること
1. SDK の初期化
Link v1(旧 In-App SDK)
createSDKInstance にバックエンドの種別を指定して初期化していました:
import {
createSDKInstance,
createAppBackend,
createApiBackend,
} from "@pocketsign/in-app-sdk";
const instance = await createSDKInstance({
serviceId,
backend: createAppBackend(),
});
Link v2
KLON ベースでは、認証・リソースアクセスに In-App SDK は使用しません(OIDC フロー + Registry API に移行)。
2. 認証・認可
Link v1(旧 In-App SDK)
従来は In-App SDK を通じて、サブスクリプション -> 権限要求 -> リソース読み取りを逐次実行していました:
import {
getSubscriptionIdWithSignature,
MergedSourceResources,
readResourceWithSignature,
requestPermission,
} from "@pocketsign/in-app-sdk";
// 1. サブスクリプション ID を取得(署名付き JWT)
const subscriptionIdRes = await getSubscriptionIdWithSignature(sdk);
// 2. 権限を要求
const resources = [
MergedSourceResources.fullName,
MergedSourceResources.fullAddress,
];
const requestPermissionRes = await requestPermission(
sdk,
resources.map((resourceId) => ({
resourceId,
verbs: [{ verb: "read", term: "always" }],
})),
);
// 3. リソースを 1 つずつ署名付きで読み取り
const fullNameRes = await readResourceWithSignature(sdk, {
resourceId: MergedSourceResources.fullName,
});
const fullAddressRes = await readResourceWithSignature(sdk, {
resourceId: MergedSourceResources.fullAddress,
});
受け取った JWT をポケットサインサーバーの公開鍵(JWKS)で署名検証し、クレームを抽出していました:
import { createRemoteJWKSet, jwtVerify } from "jose";
// ポケットサインサーバーの公開鍵(JWKS)
const JWKS = createRemoteJWKSet(
new URL("https://id.pocketsign.co.jp/.well-known/jwks.json"),
);
// 署名検証 + aud が serviceId と一致するかの検証を同時に行う
const { payload } = await jwtVerify(jwtString, JWKS, {
audience: serviceId,
});
const value = payload[key] as string;
Link v2
KLON では 1 回の OIDC 認可リクエストでサブスクリプション + 権限要求が完結します。
2.1 OIDC クライアント(バックエンド)
import (
klon "github.com/pocketsign/klon-sdk-go"
)
oidcClient := klon.NewClient(klon.ClientConfig{
Issuer: "https://id.example.com",
ClientID: "your-client-id",
ClientSecret: "your-client-secret", // Confidential Client の場合
RedirectURI: "https://your-service.example.com/callback",
})
2.2 認可リクエスト(バックエンド)
// 認可リクエストを構築(サブスク + 権限要求が 1 回で完結)
boolTrue := true
maxAge := 3600
authURL, session, err := oidcClient.CreateAuthorizationURL(ctx, klon.AuthorizeOptions{
Scopes: []string{klon.ScopeOpenID, klon.ScopeOfflineAccess},
AuthorizationDetails: []klon.AuthorizationDetailInput{
{
Identifiers: []string{klon.ResourceMergedFullName},
Actions: []klon.ResourceAction{klon.ResourceActionRead},
Required: &boolTrue, // 必須リソース
},
{
Identifiers: []string{klon.ResourceMergedBirthDate},
Actions: []klon.ResourceAction{klon.ResourceActionRead},
},
{
Identifiers: []string{klon.ResourceEmailAddress},
Actions: []klon.ResourceAction{klon.ResourceActionRead},
},
},
AcrValues: []string{klon.AcrHigh},
Prompt: []string{klon.PromptConsent},
MaxAge: &maxAge,
GrantManagementAction: klon.GrantManagementReplace,
UsePAR: true, // Pushed Authorization Request (RFC 9126)
})
// session をセッションストレージに保存(コールバック時の検証に使用)
// JSON シリアライズ可能: json.Marshal(session) / json.Unmarshal(..., &session)
sessionStore.Save(session)
http.Redirect(w, r, authURL.String(), http.StatusFound)
UsePAR: trueを推奨。認可パラメータを事前に IdP に送信し、URL にパラメータを含めない。
authorization_details の詳細は authorization_details と同意モデルを参照してください。
2.3 コールバック処理(バックエンド)
// /callback ハンドラー
state := r.URL.Query().Get("state")
session, ok := sessionStore.Load(state)
if !ok {
http.Error(w, "invalid state", http.StatusBadRequest)
return
}
code := r.URL.Query().Get("code")
tokenSet, err := oidcClient.ExchangeCode(ctx, code, state, session)
// tokenSet.AccessToken — ユーザーアクセストークン(Registry API 呼び出し用)
// tokenSet.IDToken — ID Token(sub = Subscription ID)
// tokenSet.RefreshToken — リフレッシュトークン(offline_access スコープ時)
// tokenSet.AuthorizationDetails — 付与された権限の詳細
ExchangeCodeはstateパラメータの一致検証を内部で行う。
2.4 トークンリフレッシュ(バックエンド)
newTokenSet, err := oidcClient.RefreshToken(ctx, tokenSet.RefreshToken)
3. 追加権限の要求
初回ログイン後に、追加でリソースへのアクセス権限を要求するケースです。
Link v1(旧 In-App SDK)
従来は requestPermission を任意のタイミングで再度呼び出すことで、追加の権限を要求できました:
import { requestPermission, MergedSourceResources } from "@pocketsign/in-app-sdk";
// 後から追加で住所の読み取り権限を要求
const result = await requestPermission(sdk, [
{
resourceId: MergedSourceResources.fullAddress,
verbs: [{ verb: "read", term: "always" }],
},
]);
Link v2
KLON では、grant_management_action=merge を指定した認可リクエストを再度発行します。既存のグラントに新しい権限だけが追加され、差分がある場合のみ同意画面が表示されます。
authURL, session, err := oidcClient.CreateAuthorizationURL(ctx, klon.AuthorizeOptions{
Scopes: []string{klon.ScopeOpenID, klon.ScopeOfflineAccess},
AuthorizationDetails: []klon.AuthorizationDetailInput{
{
// 追加で要求するリソースだけを指定
Identifiers: []string{klon.ResourceMergedFullAddress},
Actions: []klon.ResourceAction{klon.ResourceActionRead},
},
},
GrantManagementAction: klon.GrantManagementMerge, // 既存グラントに追加
UsePAR: true,
})
コールバック処理は初回と同じです。トークン交換後に返される tokenSet には、追加権限を含む新しいアクセストークンとリフレッシュトークンが含まれます。
詳細は後から追加権限を要求するを参照してください。
4. リソース読み取り
Link v1(旧 In-App SDK)
従来は SDK でリソースを署名付き JWT として読み取り、バックエンドで署名検証していました:
import { createRemoteJWKSet, jwtVerify } from "jose";
// SDK で署名付きリソースを取得
const fullNameRes = await readResourceWithSignature(sdk, {
resourceId: MergedSourceResources.fullName,
});
// fullNameRes.value は JWT 文字列
// JWKS で JWT 署名を検証し、クレームを抽出
const JWKS = createRemoteJWKSet(
new URL("https://id.pocketsign.co.jp/.well-known/jwks.json"),
);
// 署名検証 + aud が serviceId と一致するかの検証を同時に行う
const { payload } = await jwtVerify(jwtString, JWKS, {
audience: serviceId,
});
const value = payload.value as string;
Link v2
OIDC で取得したアクセストークンで Registry UserService を呼び出します。
// ConnectRPC クライアントを作成(ユーザーアクセストークンを付与)
userServiceClient := linkv2connect.NewRegistryUserServiceClient(
&http.Client{Transport: &bearerTransport{token: tokenSet.AccessToken}},
registryURL,
)
// 複数リソースを一括取得(最大 50 個)
resp, err := userServiceClient.ReadResourceValues(ctx,
connect.NewRequest(&linkv2.RegistryUserServiceReadResourceValuesRequest{
IdOrAliases: []string{
klon.ResourceMergedFullName, // "klon/merged_full_name"
klon.ResourceMergedBirthDate, // "klon/merged_birth_date"
klon.ResourceEmailAddress, // "klon/email_address"
},
}),
)
// 各リソースの結果を処理
for _, rv := range resp.Msg.Results {
if rv.Error != linkv2.ReadResourceValueError_READ_RESOURCE_VALUE_ERROR_UNSPECIFIED {
// READ_RESOURCE_VALUE_ERROR_NO_VALUE
// READ_RESOURCE_VALUE_ERROR_UNKNOWN_RESOURCE
// READ_RESOURCE_VALUE_ERROR_NO_PERMISSION
// READ_RESOURCE_VALUE_ERROR_SUBSCRIPTION_NOT_FOUND
// READ_RESOURCE_VALUE_ERROR_PERMISSION_SUSPENDED
// READ_RESOURCE_VALUE_ERROR_PERMISSION_EXPIRED
continue
}
// rv.Value.GetJson(), rv.Value.GetString_(), rv.Value.GetBinary()
}
もしくは、サービスアクセストークンで Registry ClientService を呼び出します。
subscription_id を指定して特定ユーザーのリソースを取得します。
// ConnectRPC クライアントを作成(サービスアクセストークンを付与)
clientServiceClient := linkv2connect.NewRegistryClientServiceClient(
&http.Client{Transport: &bearerTransport{token: serviceAccessToken}},
registryURL,
)
// 複数ユーザー x 複数リソースを一括取得(最大 50 個)
resp, err := clientServiceClient.ReadResourceValues(ctx,
connect.NewRequest(&linkv2.RegistryClientServiceReadResourceValuesRequest{
UserResources: []*linkv2.RegistryClientServiceReadResourceValuesRequest_UserResource{
{IdOrAlias: klon.ResourceMergedFullName, SubscriptionId: subscriptionID},
{IdOrAlias: klon.ResourceMergedBirthDate, SubscriptionId: subscriptionID},
},
}),
)
5. リソース書き込み
Link v1(旧 In-App SDK)
import { writeResource } from "@pocketsign/in-app-sdk";
await writeResource(sdk, {
resourceId: FAVORITE_FOOD_RESOURCE_ID,
resource: { value: JSON.stringify("寿司") },
});
Link v2
// Registry UserService.WriteResourceValue
resp, err := userServiceClient.WriteResourceValue(ctx,
connect.NewRequest(&linkv2.RegistryUserServiceWriteResourceValueRequest{
IdOrAlias: "your-custom-resource-alias",
// 3 つのフォーマットから 1 つを選択:
Value: &linkv2.RegistryUserServiceWriteResourceValueRequest_Json{
Json: structpb.NewStringValue("寿司"),
},
// Value: &linkv2.RegistryUserServiceWriteResourceValueRequest_String_{String_: "寿司"},
// Value: &linkv2.RegistryUserServiceWriteResourceValueRequest_Binary{Binary: &linkv2.ResourceValueBinary{Bytes: bytes, MimeType: "image/png"}},
}),
)
サービスアクセストークンを使って ClientService で書き込む場合は、subscription_id を指定する必要があります。
// Registry ClientService.WriteResourceValue
resp, err := clientServiceClient.WriteResourceValue(ctx,
connect.NewRequest(&linkv2.RegistryClientServiceWriteResourceValueRequest{
IdOrAlias: "your-custom-resource-alias",
SubscriptionId: subscriptionID,
Value: &linkv2.RegistryClientServiceWriteResourceValueRequest_Json{
Json: structpb.NewStringValue("寿司"),
},
}),
)
6. 電子証明書の失効確認
Link v1(旧 In-App SDK)
import { checkCertificateRevocation } from "@pocketsign/in-app-sdk";
const result = await checkCertificateRevocation(sdk, {
type: "jpki_card_digital_signature",
});
Link v2
// IdP UserService.CheckCertificateRevocation
resp, err := idpUserServiceClient.CheckCertificateRevocation(ctx,
connect.NewRequest(&linkv2.IdPUserServiceCheckCertificateRevocationRequest{
CertificateType: linkv2.CertificateType_CERTIFICATE_TYPE_JPKI_CARD_DIGITAL_SIGNATURE,
}),
)
// resp.Msg.Status: CERTIFICATE_STATUS_GOOD | CERTIFICATE_STATUS_REVOKED
// resp.Msg.RevocationReason: CERTIFICATE_REVOCATION_REASON_KEY_COMPROMISE | CERTIFICATE_REVOCATION_REASON_CA_COMPROMISE | ...
// resp.Msg.RevokedAt: *timestamppb.Timestamp
サービスアクセストークンで ClientService を呼び出す場合は、subscription_id を指定する必要があります。
// IdP ClientService.CheckCertificateRevocation
resp, err := idpClientServiceClient.CheckCertificateRevocation(ctx,
connect.NewRequest(&linkv2.IdPClientServiceCheckCertificateRevocationRequest{
SubscriptionId: subscriptionID,
CertificateType: linkv2.CertificateType_CERTIFICATE_TYPE_JPKI_CARD_DIGITAL_SIGNATURE,
}),
)
証明書タイプ一覧:
| 従来の SDK 値 | KLON CertificateType |
|---|---|
jpki_card_digital_signature | CERTIFICATE_TYPE_JPKI_CARD_DIGITAL_SIGNATURE |
jpki_card_user_authentication | CERTIFICATE_TYPE_JPKI_CARD_USER_AUTHENTICATION |
jpki_mobile_digital_signature | CERTIFICATE_TYPE_JPKI_MOBILE_DIGITAL_SIGNATURE |
| (なし) | CERTIFICATE_TYPE_JPKI_MOBILE_USER_AUTHENTICATION |
7. 位置情報
Link v1(旧 In-App SDK)
import { getCurrentGeolocation } from "@pocketsign/in-app-sdk";
const result = await getCurrentGeolocation(sdk, {
enableHighAccuracy: true,
maximumAge: 0,
});
Link v2
// Web 標準 API を直接使用する
const position = await new Promise<GeolocationPosition>((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, {
enableHighAccuracy: true,
maximumAge: 0,
});
});
8. Link v2 未対応ポケットサインアプリへの対応
Link v2 のサービスが未対応のバージョンのポケットサインアプリで開かれた場合にアップデートを要求します。requestAppUpdate は SDK インスタンスなしで呼び出せます。
import {
isKLONSupported,
requestAppUpdate,
} from "@pocketsign/in-app-sdk";
if (!isKLONSupported()) {
requestAppUpdate();
return;
}
// KLON ベースのフローへ
9. ポケットサインアプリ / ブラウザの判別
ポケットサインアプリ固有の機能(closeWindow 等)を使う場合に環境判別が必要です。
import { isInApp } from "@pocketsign/in-app-sdk";
if (isInApp()) {
// ポケットサインアプリ内で動作中
// closeWindow 等のネイティブ機能が使える
} else {
// ブラウザで動作中
// ネイティブ機能は使えない
}
リファレンス
ACR Values
認証の強度を制御するために acrValues を指定します。
| ACR Value | 定数(TS) | 定数(Go) | 説明 |
|---|---|---|---|
urn:klon:acr:low | AcrValues.LOW | klon.AcrLow | メール / 外部 IdP |
urn:klon:acr:high | AcrValues.HIGH | klon.AcrHigh | JPKI 利用者証明用電子証明書 |
urn:klon:acr:very_high | AcrValues.VERY_HIGH | klon.AcrVeryHigh | JPKI 署名用電子証明書 |
Scopes
| Scope | 定数(TS) | 定数(Go) | 説明 |
|---|---|---|---|
openid | Scopes.OPENID | klon.ScopeOpenID | OpenID Connect(必須) |
profile | Scopes.PROFILE | klon.ScopeProfile | 氏名 / 生年月日 / 性別 |
email | Scopes.EMAIL | klon.ScopeEmail | メールアドレス |
phone | Scopes.PHONE | klon.ScopePhone | 電話番号 |
address | Scopes.ADDRESS | klon.ScopeAddress | 住所 |
personal_info | Scopes.PERSONAL_INFO | klon.ScopePersonalInfo | 基本4情報 |
offline_access | Scopes.OFFLINE_ACCESS | klon.ScopeOfflineAccess | リフレッシュトークン |
Scopes の詳細はリソース権限要求を参照してください。
リソースエイリアスの対応表
利用可能なリソース定義の完全な一覧はリソース定義一覧を参照してください。
In-App SDK の MergedSourceResources.* 定数が参照する UUID は、KLON Registry では klon/legacy_merged_* エイリアスのリソースに対応しています。
移行先としては、署名用電子証明書と券面事項入力補助APの更新日時を比較して新しい方を返すリソース klon/merged_* を推奨します。どちらにも値がない場合は手入力の値にフォールバックします。
マージ済み(最新値を採用)
| 従来のリソース | KLON エイリアス | TS 定数 | Go 定数 |
|---|---|---|---|
MergedSourceResources.firstName | klon/merged_first_name | Resources.MERGED_FIRST_NAME | klon.ResourceMergedFirstName |
MergedSourceResources.lastName | klon/merged_last_name | Resources.MERGED_LAST_NAME | klon.ResourceMergedLastName |
| (なし) | klon/merged_maiden_name | Resources.MERGED_MAIDEN_NAME | klon.ResourceMergedMaidenName |
MergedSourceResources.fullName | klon/merged_full_name | Resources.MERGED_FULL_NAME | klon.ResourceMergedFullName |
| (なし) | klon/merged_popular_name | Resources.MERGED_POPULAR_NAME | klon.ResourceMergedPopularName |
MergedSourceResources.birthDate | klon/merged_birth_date | Resources.MERGED_BIRTH_DATE | klon.ResourceMergedBirthDate |
MergedSourceResources.gender | klon/merged_gender | Resources.MERGED_GENDER | klon.ResourceMergedGender |
MergedSourceResources.fullAddress | klon/merged_full_address | Resources.MERGED_FULL_ADDRESS | klon.ResourceMergedFullAddress |
MergedSourceResources.prefectureAddress | klon/merged_prefecture_address | Resources.MERGED_PREFECTURE_ADDRESS | klon.ResourceMergedPrefectureAddress |
MergedSourceResources.cityAddress | klon/merged_city_address | Resources.MERGED_CITY_ADDRESS | klon.ResourceMergedCityAddress |
| (なし) | klon/merged_moving_abroad_date | Resources.MERGED_MOVING_ABROAD_DATE | klon.ResourceMergedMovingAbroad |
MergedSourceResources.birthYear | klon/merged_birth_year | Resources.MERGED_BIRTH_YEAR | klon.ResourceMergedBirthYear |
MergedSourceResources.birthDay | klon/merged_birth_day | Resources.MERGED_BIRTH_DAY | klon.ResourceMergedBirthDay |
その他
| 従来のリソース | KLON エイリアス | TS 定数 | Go 定数 |
|---|---|---|---|
EmailResourceId | klon/email_address | Resources.EMAIL_ADDRESS | klon.ResourceEmailAddress |
PhoneNumberResourceId | klon/phone_number | Resources.PHONE_NUMBER | klon.ResourcePhoneNumber |
FaceImageResourceId | klon/face_image | Resources.FACE_IMAGE | klon.ResourceFaceImage |
フリガナ(klon/merged_first_name_ruby 等)、通称(klon/merged_popular_name 等)、旧姓(klon/merged_maiden_name 等)、海外転出(klon/merged_moving_abroad_date 等)、署名用(klon/signing_*)、券面事項入力補助(klon/ticket_*)、手入力(klon/manual_*)も利用可能です。
完全な一覧は KLON SDK の Resources(TypeScript)/ Resource* 定数(Go)を参照してください。
プッシュ通知
プッシュ通知(PushNotificationResourceId / Service API CreateNotification)は、引き続き従来の仕組みを利用してください。
イベントウェブフック
イベントウェブフックは KLON でも利用できます。詳細は Webhook イベントを参照してください。