会員データを連携する
会員連携を使うと、公開ページのログイン中の会員IDと属性をKotaelへ送信できます。改ざん防止が必要な連携では、サーバーで生成した署名付きJWTを使用してください。
更新日: 2026年8月10日1. 署名なしと署名付きを選ぶ
- 署名なし:ログイン中の会員IDと、毎回の完全な属性セットをブラウザから送信します。ブラウザからIDを書き換えられるため、本人性は保証できません。
- 署名付き:サーバーでHS256署名した会員JWTを生成し、ブラウザから送信します。改ざん防止が必要な会員向け機能にはこちらを使用します。
Adminの会員データを開き、会員情報連携の設置方法から使用する方式を選んでください。
2. 署名キーを準備する
- Adminの会員データを開き、会員情報連携を展開します。
- 署名付きタブを選び、新しいキーを発行を選びます。
- 表示されたキーIDとシークレットをサーバーの環境変数へ保存します。
サンプルではキーIDをKOTAEL_KEY_ID、シークレットをKOTAEL_SIGNING_SECRETとして参照します。シークレットは発行直後に一度だけ表示されます。
3. サーバーで会員JWTを生成する
使用中のサーバー言語を選び、ログイン済み会員の情報からJWTを生成します。headerにはalg: HS256、typ: JWT、発行したkidを入れます。payloadには会員IDのsubと有効期限のexpが必須です。
import { createHmac } from "node:crypto";
const keyId = process.env.KOTAEL_KEY_ID;
const signingSecret = process.env.KOTAEL_SIGNING_SECRET;
function base64UrlJson(value) {
return Buffer.from(JSON.stringify(value)).toString("base64url");
}
function createKotaelMemberJwt(member) {
if (!keyId || !signingSecret) {
throw new Error("Kotael signing credentials are not configured");
}
const header = base64UrlJson({ alg: "HS256", typ: "JWT", kid: keyId });
const payload = base64UrlJson({
sub: member.id,
exp: Math.floor(Date.now() / 1000) + 3600,
email: member.email,
name: member.name,
family_name: member.familyName,
given_name: member.givenName,
attrs: member.attrs,
});
const signingInput = `${header}.${payload}`;
const signature = createHmac("sha256", signingSecret)
.update(signingInput)
.digest("base64url");
return `${signingInput}.${signature}`;
}
const memberJwt = createKotaelMemberJwt({
id: "cust_10293",
email: "taro@example.com",
name: "山田太郎",
familyName: "山田",
givenName: "太郎",
attrs: { member_rank: "gold", orders_count: 12 },
});
<?php
$keyId = getenv("KOTAEL_KEY_ID");
$signingSecret = getenv("KOTAEL_SIGNING_SECRET");
if (!$keyId || !$signingSecret) {
throw new RuntimeException("Kotael signing credentials are not configured");
}
function base64UrlEncode(string $value): string
{
return rtrim(strtr(base64_encode($value), "+/", "-_"), "=");
}
function base64UrlJson(array $value): string
{
return base64UrlEncode(json_encode(
$value,
JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
));
}
function createKotaelMemberJwt(array $member, string $keyId, string $secret): string
{
$header = base64UrlJson(["alg" => "HS256", "typ" => "JWT", "kid" => $keyId]);
$payload = base64UrlJson([
"sub" => $member["id"],
"exp" => time() + 3600,
"email" => $member["email"],
"name" => $member["name"],
"family_name" => $member["family_name"],
"given_name" => $member["given_name"],
"attrs" => $member["attrs"],
]);
$signingInput = $header . "." . $payload;
$signature = base64UrlEncode(hash_hmac("sha256", $signingInput, $secret, true));
return $signingInput . "." . $signature;
}
$memberJwt = createKotaelMemberJwt([
"id" => "cust_10293",
"email" => "taro@example.com",
"name" => "山田太郎",
"family_name" => "山田",
"given_name" => "太郎",
"attrs" => ["member_rank" => "gold", "orders_count" => 12],
], $keyId, $signingSecret);
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
public final class KotaelMemberJwt {
private static final ObjectMapper JSON = new ObjectMapper();
private static final Base64.Encoder BASE64_URL =
Base64.getUrlEncoder().withoutPadding();
private static String base64UrlJson(Object value) throws Exception {
return BASE64_URL.encodeToString(JSON.writeValueAsBytes(value));
}
public static String create(
Map<String, Object> member,
String keyId,
String signingSecret
) throws Exception {
String header = base64UrlJson(
Map.of("alg", "HS256", "typ", "JWT", "kid", keyId));
Map<String, Object> payload = new LinkedHashMap<>();
payload.put("sub", member.get("id"));
payload.put("exp", Instant.now().getEpochSecond() + 3600);
payload.put("email", member.get("email"));
payload.put("name", member.get("name"));
payload.put("family_name", member.get("family_name"));
payload.put("given_name", member.get("given_name"));
payload.put("attrs", member.get("attrs"));
String signingInput = header + "." + base64UrlJson(payload);
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(new SecretKeySpec(
signingSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = BASE64_URL.encodeToString(
hmac.doFinal(signingInput.getBytes(StandardCharsets.UTF_8)));
return signingInput + "." + signature;
}
public static void main(String[] args) throws Exception {
String keyId = System.getenv("KOTAEL_KEY_ID");
String signingSecret = System.getenv("KOTAEL_SIGNING_SECRET");
if (keyId == null || signingSecret == null) {
throw new IllegalStateException("Kotael signing credentials are not configured");
}
String memberJwt = create(
Map.of(
"id", "cust_10293",
"email", "taro@example.com",
"name", "山田太郎",
"family_name", "山田",
"given_name", "太郎",
"attrs", Map.of("member_rank", "gold", "orders_count", 12)),
keyId,
signingSecret);
}
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"time"
)
func base64URLJSON(value any) (string, error) {
data, err := json.Marshal(value)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(data), nil
}
func createKotaelMemberJWT(
member map[string]any,
keyID string,
signingSecret string,
) (string, error) {
header, err := base64URLJSON(map[string]any{
"alg": "HS256", "typ": "JWT", "kid": keyID,
})
if err != nil {
return "", err
}
payload, err := base64URLJSON(map[string]any{
"sub": member["id"],
"exp": time.Now().Unix() + 3600,
"email": member["email"],
"name": member["name"],
"family_name": member["family_name"],
"given_name": member["given_name"],
"attrs": member["attrs"],
})
if err != nil {
return "", err
}
signingInput := header + "." + payload
signer := hmac.New(sha256.New, []byte(signingSecret))
_, _ = signer.Write([]byte(signingInput))
signature := base64.RawURLEncoding.EncodeToString(signer.Sum(nil))
return signingInput + "." + signature, nil
}
func main() {
keyID := os.Getenv("KOTAEL_KEY_ID")
signingSecret := os.Getenv("KOTAEL_SIGNING_SECRET")
if keyID == "" || signingSecret == "" {
log.Fatal("Kotael signing credentials are not configured")
}
memberJWT, err := createKotaelMemberJWT(
map[string]any{
"id": "cust_10293",
"email": "taro@example.com",
"name": "山田太郎",
"family_name": "山田",
"given_name": "太郎",
"attrs": map[string]any{"member_rank": "gold", "orders_count": 12},
},
keyID,
signingSecret,
)
if err != nil {
panic(err)
}
fmt.Println(memberJWT)
}
require "base64"
require "json"
require "openssl"
def base64url_json(value)
Base64.urlsafe_encode64(JSON.generate(value), padding: false)
end
def create_kotael_member_jwt(member, key_id, signing_secret)
header = base64url_json(alg: "HS256", typ: "JWT", kid: key_id)
payload = base64url_json(
sub: member[:id],
exp: Time.now.to_i + 3600,
email: member[:email],
name: member[:name],
family_name: member[:family_name],
given_name: member[:given_name],
attrs: member[:attrs]
)
signing_input = "#{header}.#{payload}"
digest = OpenSSL::HMAC.digest("SHA256", signing_secret, signing_input)
signature = Base64.urlsafe_encode64(digest, padding: false)
"#{signing_input}.#{signature}"
end
member_jwt = create_kotael_member_jwt(
{
id: "cust_10293",
email: "taro@example.com",
name: "山田太郎",
family_name: "山田",
given_name: "太郎",
attrs: { member_rank: "gold", orders_count: 12 }
},
ENV.fetch("KOTAEL_KEY_ID"),
ENV.fetch("KOTAEL_SIGNING_SECRET")
)
Javaの例はJSON生成にJacksonを使用します。各例のmemberJwtを、そのページを閲覧しているログイン中の会員にだけ渡してください。
4. ブラウザから署名付きJWTを送信する
サーバーで生成したJWTをページへ安全に埋め込み、Kotaelの埋め込みタグを読み込むページで次のコードを実行します。
<script>
kotael("user_jwt", "SERVER_SIGNED_JWT");
</script>会員がログアウトしたときはkotael("logout")を実行します。同じページで署名なしと署名付きを両方送った場合は署名付きが優先され、JWTの検証に失敗しても署名なしへフォールバックしません。
5. 毎回、完全な属性セットを送る
会員連携では、ID、名前、メールアドレスと任意の属性を送信できます。属性は差分追加ではなく、ページ表示時点の完全なセットを毎回送ってください。
前回送った属性を今回は省略すると、その属性が不要になった状態として扱われます。属性は最大50キー、JWT全体は4KB以下にし、値には文字列、有限数値、真偽値、またはnullを使用します。
6. 一覧・詳細・検証エラーを確認する
会員データ一覧には、外部会員ID、名前、メールアドレス、連携方式、最終確認日時が表示されます。行を開くと属性、登録日時、更新日時、内部IDを確認できます。
署名付きタブにJWT検証エラーが表示された場合は、キーID、キーの有効状態、署名、期限、claimsの形式を確認します。送信側の修正後に新しい会員データが記録されることを確認してください。