例とトラブルシューティング デベロッパ

最終更新:2026年8月16日

例とトラブルシューティング

断片はプロトコルの対照用です。実行できる完全なスクリプトは「Python の最小実行例」にあります。定数は実際の値に置き換えてください。

ディスカバリ文書

curl -sS https://api-passport.swaymoon.com/.well-known/openid-configuration | jq .

authorization_endpointtoken_endpointuserinfo_endpointjwks_uriid_token_signing_alg_values_supported などのフィールドを確認してください。

認可 URL(ブラウザ遷移)

https://api-passport.swaymoon.com/oauth2/authorize
  ?response_type=code
  &client_id=swm_YOUR_CLIENT_ID
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
  &scope=openid%20name%20picture%20email
  &state=YOUR_STATE
  &code_challenge=YOUR_CHALLENGE
  &code_challenge_method=S256

URL を組み立てるとき、クエリパラメータは一度だけエンコードしてください。アドレスバーに redirect_uri=http%253A%252F%252F… が出るのは二重エンコードです。サーバーは字面どおり検証して失敗します。

トークン交換:機密クライアント(curl)

ISSUER=https://api-passport.swaymoon.com
CLIENT_ID='swm_YOUR_CLIENT_ID'
CLIENT_SECRET='YOUR_SECRET'
CODE='...'
VERIFIER='...'
REDIRECT='https://app.example.com/callback'

curl -sS -u "$CLIENT_ID:$CLIENT_SECRET" \
  -X POST "$ISSUER/oauth2/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=$REDIRECT" \
  --data-urlencode "code_verifier=$VERIFIER"

トークン交換:JWT アサーションクライアント(curl)

先にバックエンドで秘密鍵を使って client_assertion に署名します(秘密鍵をシェル履歴に書かないでください)。Python の例:

import time, uuid
from jwcrypto import jwk, jwt  # pip install jwcrypto

key = jwk.JWK.from_json(open("private.jwk.json").read())  # d を含む秘密鍵。ポータルへ提出しない
now = int(time.time())
token = jwt.JWT(
    header={"alg": "ES256", "kid": key["kid"], "typ": "JWT"},
    claims={
        "iss": "swm_YOUR_CLIENT_ID",
        "sub": "swm_YOUR_CLIENT_ID",
        "aud": "https://api-passport.swaymoon.com/oauth2/token",
        "jti": str(uuid.uuid4()),
        "iat": now,
        "exp": now + 300,
    },
)
token.make_signed_token(key)
assertion = token.serialize()

続けて:

ISSUER=https://api-passport.swaymoon.com
CLIENT_ID='swm_YOUR_CLIENT_ID'
CODE='...'
VERIFIER='...'
REDIRECT='https://app.example.com/callback'
ASSERTION='...'   # 前の手順で署名した JWT

curl -sS \
  -X POST "$ISSUER/oauth2/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=$REDIRECT" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "code_verifier=$VERIFIER" \
  --data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  --data-urlencode "client_assertion=$ASSERTION"

aud はディスカバリ文書の token_endpoint と一致させてください。更新時は grant_typerefresh_token にし、新しいアサーション(新しい jti / iat / exp)を署名します。

トークン交換:非機密クライアント(curl)

ISSUER=https://api-passport.swaymoon.com
CLIENT_ID='swm_YOUR_CLIENT_ID'
CODE='...'
VERIFIER='...'
REDIRECT='https://app.example.com/callback'

curl -sS \
  -X POST "$ISSUER/oauth2/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=$REDIRECT" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "code_verifier=$VERIFIER"

UserInfo(curl)

curl -sS https://api-passport.swaymoon.com/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"

トークンの更新(機密クライアント)

curl -sS -u "$CLIENT_ID:$CLIENT_SECRET" \
  -X POST "$ISSUER/oauth2/token" \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=$REFRESH_TOKEN"

よくある質問

現象考えられる原因対処
認可エラー / コールバックに飛べないredirect_uri が登録値と一致しない、または二重エンコード1 文字ずつ照合。アドレスバーに %253A / %252F が出てはいけない
invalid_request / PKCE 関連エラーcode_challenge がない、または code_challenge_methodS256 でない認可とトークン交換で code_verifiercode_challenge を対で使う
invalid_clientBasic 資格情報が違う、非機密クライアントに Basic / secret を使った、または JWT アサーションの検証に失敗した種類を確認。機密は client_id/client_secret。非機密はフォームの client_id。JWT は kid、公開鍵、audiss/subexp、ES256 を確認
invalid_grantcode が使用済みまたは期限切れ、または code_verifier が一致しない認可をやり直す。同じ code を再送しない
同意ページに想定の権限が出ないポータルにその権限がない、またはリクエストに対応する scope がないアプリの任意権限と scope パラメータを確認
コールバック error=invalid_scope認可リクエストの scope がクライアントの登録範囲を超えているポータルの「機能」で該当権限を選ぶか、リクエストの scope を狭める
UserInfo に email がないトークンに email がない(ユーザーが任意メールを外した、または未申請)トークン応答の scope を確認。email が付与されていれば常にあるはず
UserInfo のプロフィールが「空」に見える未認可のプロフィール細目は既定のプレースホルダ(ニックネーム Swaymoon ユーザー、アバター "")。フィールド欠落ではないトークンの scope で実データか判断。「トークンとユーザー情報」を参照
ユーザーがプライバシーメールを選んだemail@privaterelay.swaymoon.com の中継のことがある通常メールとして扱う。実メールは渡されない
sub が他アプリと違うpairwise の想定どおり現在の client_id またはチームの範囲でのみ sub を使う
ID Token の検証失敗ES256 を使っていない、またはキャッシュした JWKS が古い/oauth2/jwks を取り直し、algES256 であることを確認
フロントに client_secret が出ている純 SPA に機密クライアントを使った非機密クライアントを作るか、シークレットはバックエンドだけに置く
秘密鍵 JWK(d あり)がフロントや git にある秘密鍵を公開鍵として提出した、または鍵ファイルをコミットしたポータルは公開鍵だけ受け付けます。kid をローテーションし、漏れた秘密鍵を廃棄する
JWT アサーションの invalid_clientaudtoken_endpoint でない、kid が未登録、またはアサーションが RS256 / 期限切れディスカバリのトークン URL を使う。ヘッダの kid は登録済みであること。アルゴリズムは ES256

連携のヒント

  • エンドポイントはディスカバリ文書から取り、パスをハードコードしない。
  • 完全なローカルスクリプト:「Python の最小実行例」。
  • サインインと認可確認はブラウザで行う。機密および JWT アサーションクライアントのトークン交換と UserInfo はサーバーで行う。
  • 本番の Issuer とフロントドメインはそれぞれ api-passport.swaymoon.compassport.swaymoon.com

サポート

  • ユーザードキュメント:デベロッパモードをオフにし、「ガイド」と「法律」を見る。
  • デベロッパポータル:develop.swaymoon.com
  • 連絡先:hello@swaymoon.com(公開チャネルに平文の client_secret を送らない。問題を説明するときはマスクする)