この記事は Zenn に投稿したものの再掲です。
https://zenn.dev/reona777/articles/salesforce-soap-login-retirement
教育系の事業所で現場の運営をしています。エンジニアではありません。2026年の春から Claude Code で社内ツールを作り始めて、5か月で6本を本番稼働させました。
前の記事では、Salesforce のパスワードを1回変えたら連携が8箇所いっせいに止まった話を書きました。
今回はその続きのような話です。そのパスワード認証そのものが、2027年6月に使えなくなります。
先に結論を書きます。移行の中身は小さいです。ログイン処理を差し替えるだけで、SOQL もレコード更新も一行も変わりません。ところが、そのログイン処理が10本のスクリプトに独立してコピペされていました。 小さい修正が10箇所ある、という形です。そして手を動かす前に、自分にその作業をする権限が無いことが分かりました。
何が廃止されるのか
Summer '27(2027年6月ごろ)のリリースで、SOAP API の login() 呼び出しが廃止されます。ユーザー名とパスワードとセキュリティトークンを送ってセッションIDをもらう、あの方式です。
調べて分かった大事な点が3つあります。
- 対象は SOAP API バージョン 31.0〜64.0。 事実上ほぼ全部です。うちが使っているのは 59.0 と 62.0 なので、当然どちらも入ります
- SOAP API そのものは廃止されません。ログインだけです。 ここは最初に誤解しました
- 新しいAPIバージョンに上げても逃げられません。 「古いバージョンを使っているのが悪い」という話ではなく、認証方式を変えろという告知です
代替は External Client App の OAuth 2.0 です。サーバー間の無人ジョブなら client credentials フロー、ユーザーの文脈が要るものは web server フロー。うちの自動化は全部が無人ジョブなので、前者に当たります。
出典はヘルプの記事(id=005132110)とリリースノート(rn_api_upcoming_retirement_258rn)です。
まず、何本が該当するのか数えた
告知を読んで最初にやったのは、設計でも実装でもなく棚卸しでした。前回、認証情報の配布先を一覧で持っていなかったせいで8箇所を追いかける羽目になったので、今回は先に数えました。
- 無人で動いていて、止まると業務が止まるもの: 7本(GAS 4本、GitHub Actions で毎日動く Python 1本、Vercel 上の Next.js 1本、共用PCで動く印刷用スクリプト1本)
- 手動で実行するツール: 3本(面談用の資料生成、成績の書き出し、日程の一括変更)
- 公開しているサンプル: 1本
- 使い捨てのスクリプト: 十数本(壊れても実害なし)
数える基準は「Salesforce にログインしているか」だけです。この一覧は30分かからずに作れました。そして、これを持っていない状態では移行の見積もりが一生できませんでした。
構造の作り直しは要らないと分かった
次に、10本の中身を見て「どこまで直すのか」を確定させました。結論はログインの部分だけです。
10本が例外なく同じ形をしていたからです。
session = sfLogin_() ← ここだけが SOAP
GET /services/data/v62.0/query Bearer <id> ← 無傷
POST /services/data/v62.0/composite Bearer <id> ← 無傷
SOAP で取ったセッションIDを、そのまま REST の Bearer トークンとして使っている形です。SOQL もレコード更新も composite も REST 側なので、認証方式が変わっても影響を受けません。OAuth で取るアクセストークンも、同じ場所に入るだけです。
Python 側も同じでした。simple_salesforce はコンストラクタの引数を consumer_key / consumer_secret に変えれば OAuth に対応します(手元に入っているバージョンで、分岐が存在することを確認しました)。
移行と聞くと身構えますが、触る面積を先に確定させると、やることは驚くほど小さくなります。 ここまでで、これは「認証方式の載せ替え」ではなく「関数1つの差し替え」だと分かりました。
本当の問題は、その関数が10本にコピペされていたこと
ところが、その差し替えるべき関数が10本すべてに独立して書かれていました。 共通化されていません。
しかも、揃っていませんでした。
v59.0 と書いてあるもの
v62.0 と書いてあるもの
59.0 と書いてあるもの(v が無い)
作った時期が違うだけで、意味のある差ではありません。前回はキー名が SF_TOKEN と SF_SECURITY_TOKEN で揃っていない話を書きましたが、今回はAPIバージョンで同じことをやっていました。
構造も同じです。1本ずつ作っているあいだ、重複は問題として現れません。全部を同時に変える日が来て、初めて一括で請求されます。 前回は認証情報の配布先が8箇所、今回はログイン処理が10本。レイヤーが違うだけで、まったく同じ形でした。
なので、移行の前に共通化しました。「1箇所の修正」にしてから移行する、という順序です。
両方式を同居させるコードにした
書いたのは、SOAP と OAuth を1つの入口の中で切り替えるものです。判定は設定値が入っているかどうかにしました。
- スクリプトのプロパティに
SF_CLIENT_IDとSF_CLIENT_SECRETが両方あれば OAuth - 無ければ従来の SOAP ログイン
- 片方だけのときは SOAP のまま(設定の途中で壊さないため)
function sfAuthUsesOAuth_() {
var props = PropertiesService.getScriptProperties();
return !!(props.getProperty('SF_CLIENT_ID') && props.getProperty('SF_CLIENT_SECRET'));
}
function sfAuthLogin_(opts) {
return sfAuthUsesOAuth_()
? sfAuthOAuth_(opts.instanceUrl) // client credentials
: sfAuthSoap_(opts.soapUrl); // 2027年6月までのつなぎ
}
返り値は従来と同じ { sessionId, instanceUrl } にしました。呼び出し側は一行も変わりません。
この形にした理由は3つあります。
- 資格情報が手に入る前に、コードだけ先に本番へ配れる。 後述しますが、資格情報がいつ手に入るかは自分では決められませんでした
- 切り替えはプロパティを足すだけ、切り戻しは消すだけ。 デプロイが要りません
- 1本ずつ切り替えられる。 10本を同時に切り替えて、まとめて壊す事態を避けられます
OAuthが失敗しても、SOAPに落とさないことにした
ここが今回いちばん悩んだところで、この記事で唯一「意見」の部分です。
自然な発想はフォールバックです。OAuth を試して、駄目なら従来の SOAP で繋ぐ。止まらないので安全に見えます。
やめました。 落とすとこうなるからです。
OAuth の設定を間違える → でも SOAP に落ちて動き続ける → 誰も気づかない → 2027年6月に SOAP が廃止される → 当日、無人ジョブ7本がいっせいに死ぬ
フォールバックは「今日壊れない」代わりに「気づく機会を消す」仕掛けでもあります。しかも今回は期限が決まっているという条件が付きます。猶予期間のあいだに間違いへ気づけなければ、猶予そのものが無意味になります。
なので、SF_CLIENT_ID が入っている時点で OAuth に賭けて、駄目なら例外を投げて止めることにしました。設定した以上、間違っていればその場で気づきたいからです。
前に「無言で失敗する自動化」の記事を書きましたが、今回はその予防でもあります。
そのぶん、今どちらの方式で繋がっているかを確かめるための、手動実行の関数を用意しました。実際にログインして、SOQL を1本投げて、方式とインスタンスと HTTP ステータスをログに出すだけのものです。切り替えの前後で必ずこれを叩きます。
// 今どちらの方式で繋がっているかを、実際に繋いで確かめる
function sfAuthTestLogin() {
var props = PropertiesService.getScriptProperties();
var opts = {
soapUrl: props.getProperty('SF_SOAP_URL'), // .../services/Soap/u/62.0
instanceUrl: props.getProperty('SF_INSTANCE_URL')
};
var method = sfAuthUsesOAuth_() ? 'OAuth (client credentials)' : 'SOAP login(廃止予定)';
var session = sfAuthLogin_(opts);
var res = UrlFetchApp.fetch(
session.instanceUrl + '/services/data/v62.0/query?q=' +
encodeURIComponent('SELECT Id FROM Contact LIMIT 1'),
{ headers: { Authorization: 'Bearer ' + session.sessionId }, muteHttpExceptions: true });
Logger.log('方式: ' + method); // ここが OAuth に変わったことを目で見る
Logger.log('インスタンス: ' + session.instanceUrl);
Logger.log('SOQL疎通: HTTP ' + res.getResponseCode());
if (res.getResponseCode() !== 200) {
throw new Error('SOQLが通りません: ' + res.getContentText().slice(0, 300));
}
return method;
}
(記事に載せるにあたり、接続先のURLはプロパティから読む形に書き換えています。手元では定数で持っています)
ログに出すのを「成功しました」ではなく方式そのものにしたのは、切り替えたつもりで切り替わっていない状態を検出したいからです。 どちらの方式でも成功はするので、成否だけ見ていると区別がつきません。
テストは16本書きました。名前を並べると、何を心配していたかがそのまま出ます。
資格情報が無ければSOAPを選ぶ
資格情報が両方あればOAuthを選ぶ
片方だけではOAuthにしない(設定途中で壊さない)
OAuthが失敗したらSOAPへ落とさず例外を投げる
OAuthの応答がJSONでなくても分かる形で落ちる
200でもaccess_tokenが無ければ失敗にする
プロパティが無ければ何を設定すべきか言う
XMLの特殊文字を含むパスワードでも壊れない
最後のものだけ毛色が違いますが、これは既存の SOAP 側のコードを共通化するときに気づいた穴です。パスワードを XML の中に埋めて送っているので、& や < が入っていると壊れます。移行のついでに、移行と関係ないバグを1つ潰したことになります。
手を動かす前に、権限が無いことが分かった
コードができたので、次は External Client App を作って Consumer Key と Secret を取る番です。ここで止まりました。
接続に使っているアカウントに、External Client App を作る権限がありませんでした。
推測ではなく、PermissionSetAssignment を読んで確定させました。自分のユーザーIDは、REST のルート(/services/data/)が返す identity URL の末尾から取れます。
SELECT PermissionSet.Label, PermissionSet.IsOwnedByProfile,
PermissionSet.PermissionsApiEnabled,
PermissionSet.PermissionsCustomizeApplication
FROM PermissionSetAssignment
WHERE AssigneeId = '<自分のId>'
結果、付いていたのは ApiEnabled だけでした。ExternalClientAppAdmin も ExternalClientAppDeveloper も CustomizeApplication も ModifyAllData もありません。組織の中でそれらを持っているのは、システム管理者プロファイルの10アカウントだけでした。
「たぶん権限が無い」で止めずにクエリで確定させたのは、正解でした。 依頼を出すときに「たぶん権限が足りないので作れません」と言うのと、「ExternalClientAppDeveloper が付いていないので作れません」と言うのとでは、返ってくる速度が違います。
依頼の出し方は2通りあります。
- こちらに External Client App Developer の権限を付けてもらう
- 向こうで接続アプリを作って、Consumer Key と Secret をもらう
2のほうが先方の手間が少ないので通りやすいと判断しました。1は権限設計の変更で、2は1回の作業です。自分の都合ではなく、頼む相手のコストで選びました。
権限が無くても試せる道があった
依頼を出すと、返事が来るまで手が止まります。そのあいだに検証だけでも進められないかを探しました。
見つかったのは、Salesforce CLI に同梱されている接続アプリ PlatformCLI です。組織に最初から存在するので、アプリを作る権限が要りません。
実際に確かめたのは次の3点です。
- 認可エンドポイントに
client_id=PlatformCLIを投げると HTTP 302 でログイン画面に飛びました。 アプリが有効で、web server フローを受け付けている証拠です -
デバイスフローは無効でした(
device flow is not enabled for the appが返ります)。なのでブラウザを使う web server フロー一択です - ブラウザで1回認可して refresh token を取れば、以後は
grant_type=refresh_tokenでアクセストークンを取り直せます。SOAP の login を一切通らないので、廃止と無関係になります
ただしこれは本命ではありません。 トレードオフが2つあります。
- refresh token は失効しうる。 切れたら無人ジョブが止まり、ブラウザでの再認証が要ります。client credentials は無人ジョブ用の正式な方式で、この問題がありません
- CLI用のアプリを自前のスクリプトから使うのは、正式な想定ではありません。 将来塞がれる可能性があります
そして正直に書くと、ここはまだ通し切っていません。 ブラウザでの認可が要るので、実際に refresh token を取って無人で回り続けるところまでは確認できていません。refresh token の失効ポリシーも、管理者権限が無いので読めませんでした。
なので方針は両方を並行させることにしました。本部への依頼を本命として出し、返事を待たずに CLI 経由で1本通してコードを検証する。 依頼が通らなければ CLI 経由が本番の答えになり、その場合は「refresh token が切れたら Slack に飛ぶ」を足して運用でカバーします。
期限まで10か月あるので待てる、と考えないことにしました。 待って良いのは、待っているあいだに何も失われない場合だけです。今回は「依頼が通らなかったと分かるのが遅れる」ぶんだけ、確実に失われます。
切り替えの順序
まだ1本も切り替えていませんが、順序だけ決めてあります。影響の小さいものから1本ずつです。
手動実行のツールから始めて、次に止まっても半日待てるもの、最後に毎日動いている無人ジョブ。全部まとめては切り替えません。 1本目で想定外が出たときに、被害が「手で実行し直す」で済む場所から順に踏むためです。
コードのほうは設定値の有無で方式が変わるので、切り戻しはデプロイなしでできます。順序を決められるのは、1本ずつ切り替えられる形にしたからです。 ここは先に効いてきました。
ついでに見つかった別件
棚卸しの副産物として、使い捨てのスクリプト4本に、セキュリティトークンがソースへ直接書いてあるのを見つけました。公開しているリポジトリではないので事故ってはいませんが、移行のときに一緒に片付けます。
「Salesforce にログインしている場所を全部数える」という作業は、認証情報が置いてある場所を全部数える作業と同じでした。片方をやると、もう片方が出てきます。
AIに頼めなかった部分
このシリーズは毎回同じ結論に着いています。今回もそうでした。
移行そのものは、AI がよく知っています。廃止の内容も、client credentials フローの実装も、simple_salesforce の引数も、聞けば正確に出てきます。実際、共通化した認証コードもテスト16本も、ほとんどは Claude Code に書かせました。
出てこなかったものが2つあります。
1つは、自分の環境で何本が該当するか。 これはコードを読めば数えられますが、リポジトリが複数に分かれていて、GAS と Python と TypeScript にまたがっていて、共用PCの中にもあるという配置そのものは、コードの中に書いてありません。前回「AI が持っていないのは配布先の地図だ」と書きましたが、今回も同じ地図が要りました。
もう1つは、自分に何の権限が無いか。 これはコードを全部読んでも絶対に出てきません。組織に聞くしかありません。 そして権限が無いという事実は、移行の設計そのものを変えました。コードと資格情報を分離して、資格情報が無い状態でも先にコードを配れる形にした理由がこれです。
移行の作業量を決めていたのは、認証方式の難しさではありませんでした。自分の環境が何本あって、自分に何ができるかでした。 どちらも、数えるまで自分も知りませんでした。
最後に
2027年6月はまだ先です。ただ、該当する自動化が何本あるかを数えるのは今日できて、30分で終わります。
そして数えると、たぶん移行以外のものが出てきます。うちの場合は、コピペされた10本のログイン処理と、バラバラのAPIバージョンと、直接書かれたトークンでした。期限のある廃止告知は、棚卸しをする口実として都合がいいと思っています。
ほかに作ったものは GitHub に置いています。
書いている人
エンジニアではありません。勤務先の業務ツールを Claude Code で作って運用していて、作ったものと、壊れたときに直した話を書いています。
Claude Code の実務運用については X(@KouritsuONI)でも書いています。スプレッドシート・GAS・LINE・Salesforce まわりの業務自動化について、ご相談は X のDMからどうぞ。
この記事のシリーズ
Claude Code の実務運用について、順に9本書いています。
- 非エンジニアがClaude Codeで社内ツールを6本 本番稼働させるまでにやったこと
- AIに作らせたツールが本番で壊れた5つの原因と、直し方
- Claude CodeにCONTEXT.mdとSPEC.mdとADRを書かせると、途中で破綻しなくなる
- Claude Codeのスラッシュコマンドには、手順ではなく踏んだ罠を書く
- Claude Codeのメモリに53本ためて分かった、書く価値のある事実とない事実
- 実行は成功、でも誰にも届いていない。無言で失敗する自動化に気づく仕掛け
- Salesforceの項目は「ある」と「使える」が別だった。外から自動化して踏んだ6つ
- Salesforceのパスワードを変えたら、連携が8箇所いっせいに止まった
- Salesforceのログインが2027年6月に廃止される。自動化10本を調べたら、直す場所は1つではなかった(この記事)