OAuth 2.0 Developer Portal
SHOHSアカウントでのログインをあなたのアプリに組み込むための、認証フロー・エンドポイント・スコープのリファレンスです。クライアントの発行や管理は Cloud Console から行います。
概要
SHOHS Identity Platform は、標準的な OAuth 2.0 Authorization Code フロー(および OpenID Connect の一部)で動作する認証基盤です。ユーザーはSHOHSアカウントでログインし、あなたのアプリは同意を得たうえで発行されたアクセストークンを使ってユーザー情報にアクセスします。
登場人物
| 役割 | 説明 |
|---|---|
| Resource Owner(リソースオーナー) | SHOHSアカウントを持つユーザー本人 |
| Client(クライアント) | Cloud Consoleで登録した、あなたのアプリケーション |
| Authorization Server(認可サーバー) | auth.shohs.net。ログインと同意画面、トークン発行を担当 |
| Resource Server(リソースサーバー) | UserInfoエンドポイントなど、トークンで保護されたAPI |
対応しているグラントタイプ
公開されているグラントは authorization_code と refresh_token です。SPA・ネイティブアプリから利用する場合は後述の PKCE の併用を推奨します。
すべてのやり取りは HTTPS 経由のみサポートしています。開発中であっても Redirect URI には HTTPS を使用してください(localhost の扱いは FAQ を参照)。
クイックスタート
最初のOAuthクライアントを作成し、最小限のフローを動かすまでの手順です。
- Cloud Console でプロジェクトを作成し、使う API(例: Runtera API)を「ライブラリ」から有効化します。
- 「OAuth 同意画面」でアプリ名・サポートメールを設定し、「データアクセス」で使うスコープを追加します。
- 「認証情報」から「認証情報を作成」を選び、名前と Redirect URI を登録します。
- 公開範囲を「内部」「外部」のいずれかで設定します(詳細はユーザータイプを参照)。
- 発行された Client ID と Client Secret を安全な場所に保存します(Secretは作成時の一度しか表示されません)。
- 下記の Authorization Code Flow に従って、ユーザーをログインへ導きます。
- 受け取った
codeをトークンエンドポイントに渡し、access_tokenを取得します。 - UserInfoエンドポイントでプロフィール情報を取得し、自アプリのアカウントと紐付けます。
まずはcURLでの動作確認から始めると、実装前にレスポンス形式を確認できます。
用語集
このドキュメント内で使われる用語の早見表です。
| 用語 | 説明 |
|---|---|
| Client ID | アプリを一意に識別するID。公開しても問題ない値 |
| Client Secret | クライアントの正当性を証明する秘密の値。サーバー側のみで保持する |
| Authorization Code(認可コード) | ユーザーが同意した後に発行される、一度限り使える短命なコード |
| Access Token | UserInfoなど保護されたAPIを呼び出すための資格情報 |
| ID Token | ユーザーの認証結果を表すJWT。誰が・いつ認証したかを表す |
| Scope | アプリが要求する権限の範囲(openid / profile / email / name) |
| Redirect URI | 認可・認証の完了後にユーザーが戻ってくるURL |
| state | CSRF対策として認可リクエストに含めるランダムな値 |
| nonce | IDトークンのリプレイ攻撃対策として使う使い捨ての値 |
| PKCE | Client Secretを安全に扱えないクライアント向けの、認可コード横取り対策 |
Authorization Code Flow
標準的な4ステップで、ユーザーの同意を得てからアクセストークンを取得します。
各ステップの詳細
- 認可リクエスト: ブラウザを 認可エンドポイント にリダイレクトします。
stateを必ず生成し、セッションに保存しておきます。 - ユーザー同意: auth.shohs.net 上でログインし、要求されたスコープへの同意画面が表示されます(既に同意済みの場合はスキップされることがあります)。
- 認可コード発行: 同意後、
redirect_uri?code=XXXX&state=YYYYの形でブラウザがあなたのアプリに戻ります。ここで受け取ったstateが送信時と一致することを必ず検証してください。 - トークン交換: 受け取った
codeをサーバーサイドからトークンエンドポイントに送信し、access_tokenとid_tokenを取得します。認可コードは一度使うと失効するため、再利用はできません。
PKCEの利用
PKCE(Proof Key for Code Exchange)は、Client Secretを安全に保管できないSPAやモバイルアプリ・デスクトップアプリで、認可コードの横取りを防ぐための仕組みです。Client Secretを使えるサーバーサイドアプリでも、追加の防御として併用できます。
手順
- ランダムな
code_verifier(43〜128文字の英数字と一部記号)を生成し、セッションに保持します。 code_verifierを SHA-256 でハッシュ化し、Base64URLエンコードしたcode_challengeを作成します。- 認可リクエストに
code_challengeとcode_challenge_method=S256を追加します(plain方式は受け付けません)。 - トークンリクエスト時に、元の
code_verifierを一緒に送信します。認可サーバーはハッシュを再計算し、一致を検証します。一致しない場合、その認可コードは無効になります。
// code_verifier と code_challenge の生成例(ブラウザ) function base64url(buf){return btoa(String.fromCharCode(...new Uint8Array(buf))).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'')} const codeVerifier = base64url(crypto.getRandomValues(new Uint8Array(32))); const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier)); const codeChallenge = base64url(digest); // codeVerifier はセッションに保存し、codeChallenge を authorize.php のパラメータに付与
Client Secretを安全に保持できる通常のサーバーサイドアプリでは必須ではありませんが、多層防御として実装しておくことを推奨します。
state / nonce パラメータ
どちらもランダムな使い捨ての値ですが、目的が異なります。
| パラメータ | 目的 | 検証タイミング |
|---|---|---|
state | CSRF対策。リクエストとコールバックが同一ブラウザセッションからのものであることを保証 | 認可コード受け取り時、送信した値と一致するか確認 |
nonce | IDトークンのリプレイ攻撃対策 | id_token をデコードし、nonce クレームが送信した値と一致するか確認 |
state の検証を省略すると、CSRF攻撃によって攻撃者のアカウントが被害者のアプリに紐付けられる危険があります。必ず検証してください。
ユーザータイプ(内部 / 外部)
クライアント作成時に、公開範囲をどちらかに設定します。
@shohs アカウント以外のみが作成できるクライアントです。@shohs アカウントでは選択できません。
SHOHSアカウントを問わず、誰でも利用できるクライアントです。
スコープ
認可リクエストの scope パラメータで要求する権限を指定します。
| スコープ | 説明 |
|---|---|
openid | ユーザーの識別情報(sub / account_id)へのアクセス |
profile | ユーザーID(preferred_username)などプロフィール情報へのアクセス |
email | メールアドレス(email / email_verified)へのアクセス |
name | 表示名(name)へのアクセス |
runtera.* | Runtera のデータへのアクセス。Runtera API のスコープを参照(クライアントごとに Cloud Console で許可が必要) |
Runtera 以外のスコープだけを要求する従来のリクエストは、これまでどおり名前・メール・プロフィール画像を共有します。runtera.* を要求する場合は、要求した profile / email の範囲だけが共有されます。
認可エンドポイント
https://auth.shohs.net/oauth2/authorize.php| パラメータ | 必須 | 説明 |
|---|---|---|
client_id | 必須 | 発行されたクライアントID |
redirect_uri | 必須 | 承認後の戻り先URL |
response_type | 必須 | code を指定 |
scope | 任意 | スペース区切りのスコープ一覧 |
state | 任意 | CSRF対策用のランダムな文字列 |
# 例
GET /oauth2/authorize.php?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=openid+profile+email&state=xyz123
トークンエンドポイント
認可コードを access_token および id_token と交換します。
https://auth.shohs.net/oauth2/token.php| パラメータ | 説明 |
|---|---|
client_id | クライアントID |
client_secret | クライアントシークレット |
code | 認可エンドポイントで受け取ったコード |
grant_type | authorization_code または refresh_token |
refresh_token | grant_type=refresh_token のとき必須。使用するとローテーション(新しい値に置き換え)されます |
scope | 任意(refresh_token のみ)。元の許可範囲を絞り込む場合に指定 |
redirect_uri | 認可リクエストで使った値(送信した場合は一致を検証します) |
code_verifier | 認可リクエストに code_challenge を付けた場合は必須(PKCE) |
トークンエンドポイントは POST のみ受け付け、認証に失敗し続けた接続元は一定時間ロックされます(429)。使用済みのリフレッシュトークンが再び使われた場合は、盗用の可能性があるため、そのアカウント・クライアントのトークンをすべて失効させます。
# リクエスト
POST /oauth2/token.php
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=AUTH_CODE&client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&redirect_uri=YOUR_REDIRECT_URI
// レスポンス
{
"access_token": "shs_at_...",
"id_token": "eyJhbGciOi...",
"refresh_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "openid runtera.records.read"
}
scope には、利用者が同意画面で実際に許可したスコープが入ります。アクセストークンは1時間、リフレッシュトークンは30日間有効です。
Runtera API
SHOHS OAuth 2.0 のアクセストークンで、Runtera の練習記録・体調・目標・レース結果を読み書きできます。
https://runtera.apis.shohs.net/v1利用手順
- Cloud Console の「ライブラリ」で Runtera API を有効にし、「OAuth 同意画面 → データアクセス」で使うスコープを追加します(プロジェクト内の全クライアントに反映されます)。
- 認可リクエストの
scopeにそのスコープを指定します。利用者には同意画面で権限の一覧が表示され、不要なものは外せます。 - 受け取った
access_tokenをAuthorization: Bearer ...で送ります。
API が操作できるのは常にトークンの持ち主のデータだけです(リクエストで account_id は指定しません)。実際に使える権限は「利用者が許可したスコープ」と「Console でクライアントに許可されているスコープ」の両方に含まれるものです。Console でスコープを外すと、発行済みトークンにも即時に反映されます。
ブラウザ(JavaScript)から呼ぶ場合(CORS)は、クライアントの「JavaScript生成元」が未登録ならすべてのオリジンを許可し、登録がある場合は登録したオリジンのみ許可します。サーバー間の呼び出しは登録不要です。
Runtera API のスコープ
| スコープ | 種別 | 説明 |
|---|---|---|
runtera.read | 読み取り | 下記の *.read をまとめて許可 |
runtera.write | 書き込み | 下記の *.write をまとめて許可(読み取りは含まれません) |
runtera.profile.read | 読み取り | 氏名・ユーザー名・役割・自己ベスト |
runtera.records.read / .write | 読み書き | 練習記録の取得 / 追加・更新・削除 |
runtera.conditions.read / .write | 読み書き | 体調記録の取得 / 登録・更新・削除 |
runtera.goals.read / .write | 読み書き | 目標の取得 / 追加・更新・削除 |
runtera.races.read | 読み取り | レース記録の取得 |
Runtera API エンドポイント
| メソッド | パス | スコープ |
|---|---|---|
| GET | /tokeninfo | 有効なトークンのみ(持ち主・クライアント・有効スコープ) |
| GET | /me | runtera.profile.read |
| GET | /records /records/{training_id} | runtera.records.read (from to training_id limit offset) |
| POST | /records | runtera.records.write ({"records":[…]} または単一オブジェクト。最大50件) |
| PUT / DELETE | /records/{training_id} | runtera.records.write (PUT は同じ training_id の中身を置き換え) |
| GET | /body-conditions /body-conditions/{YYYY-MM-DD} | runtera.conditions.read |
| PUT / DELETE | /body-conditions/{YYYY-MM-DD} | runtera.conditions.write (PUT は送った項目だけ更新。なければ作成) |
| GET | /goals /goals/{id} | runtera.goals.read |
| POST / PATCH / DELETE | /goals /goals/{id} | runtera.goals.write |
| GET | /races | runtera.races.read (from to limit offset) |
成功時は {"success":true,"data":…}、失敗時は {"success":false,"error":"invalid_token","error_description":"…"} を返します。主なステータスは 400(パラメータ不正)/ 401(トークン無効)/ 403(スコープ不足・オリジン未登録・Runtera 未登録)/ 404 / 422(入力エラー。errors に項目別の理由)です。
Runtera API リクエスト例
# 練習記録を取得 curl -H "Authorization: Bearer ACCESS_TOKEN" \ "https://runtera.apis.shohs.net/v1/records?from=2026-09-01&to=2026-09-30&limit=50" # 練習記録を追加 curl -X POST -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"records":[{"record_date":"2026-09-30","training_type":"ジョグ","distance_km":10,"duration_seconds":3600,"rpe":4,"memo":"朝ラン"}]}' \ https://runtera.apis.shohs.net/v1/records # 体調を登録 curl -X PUT -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"weight":58.4,"sleep_start":"23:30","sleep_end":"06:30","fatigue_level":4}' \ https://runtera.apis.shohs.net/v1/body-conditions/2026-09-30 # 目標を追加 → 進捗を更新 curl -X POST -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"goal_type":"distance","title":"月間200km","target_value":200,"target_date":"2026-10-31"}' \ https://runtera.apis.shohs.net/v1/goals curl -X PATCH -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \ -d '{"current_value":120}' https://runtera.apis.shohs.net/v1/goals/1
SHOHS AI API
SHOHS 経由で Google Gemini(テキスト生成・音声合成・Live API・エージェント・検索 / 地図グラウンディング)を利用できる API です。Google の API キーはサーバー側だけが持ち、アプリには渡りません。
https://ai.apis.shohs.net/v1OAuth ではなく API キーで認証します。 AI API は SHOHS ユーザーの個人情報を扱わないため、同意画面や access_token は使いません。Cloud Console で発行した API キー(shk_ で始まる文字列)を Authorization: Bearer に付けて呼び出します。
利用手順
- Cloud Console でプロジェクトを作成し、「ライブラリ」から SHOHS AI API を有効にします。
- 「認証情報 → API キーを作成」で、キー名・使う機能・利用制限を決めて発行します。キーは作成直後の1回だけ表示されます。
- リクエストに
Authorization: Bearer shk_...を付けて呼び出します。
# 動作確認(キーの機能・上限を返します)
curl -H "Authorization: Bearer shk_YOUR_API_KEY" https://ai.apis.shohs.net/v1/tokeninfo
プロジェクトで AI API を無効にすると、そのプロジェクトのすべての API キーが即座に使えなくなります。
API キーの発行と制限
キーごとに、使える機能・呼び出せる場所・利用量の上限を設定できます。用途ごとにキーを分けると、漏えい時の影響を小さくできます。
| 項目 | 説明 |
|---|---|
| 使える機能 | generate(テキスト生成)/ speech(音声合成)/ live(Live API)/ agent(エージェント)/ grounding.search(Google 検索)/ grounding.maps(Google マップ)。許可していない機能は 403 になります |
| 許可オリジン | ブラウザ(JavaScript)から呼ぶ場合に、呼び出しを許す生成元。空欄ならブラウザからの呼び出しは拒否されます(サーバー間の呼び出しは影響なし) |
| 許可 IP | 呼び出しを許す IP アドレスまたは CIDR(例: 203.0.113.0/24)。空欄なら制限なし |
| 1分あたり / 1日あたりの上限 | リクエスト数の上限。超えると 429 を返します |
API キーはブラウザやモバイルアプリのコードに埋め込まないでください。キーを知っている人は誰でもあなたの上限まで AI を使えます。公開する Web ページから使う場合は、許可オリジンと低めの上限を必ず設定するか、自分のサーバーを経由して呼び出してください。キーが漏れた疑いがあるときは、Console でそのキーを削除して再発行します。
キーは SHA-256 のハッシュとして保存され、SHOHS 側でも元の値を確認できません。紛失した場合は再発行してください。
利用できるモデル
最新の一覧と、各モデルが対応する機能は GET /v1/models で取得できます(?verify=1 を付けると、Google 側に実在するか確認します)。Google 側でモデルが終了・名称変更されることがあるため、モデル ID は固定せず一覧から選ぶことをお勧めします。
| 種類 | 機能 | 使い方 |
|---|---|---|
| テキスト生成(Gemini Flash / Flash Lite 系) | generate | POST /generate または POST /models/{model}:generateContent |
| 音声合成(Gemini TTS) | speech | POST /models/{model}:generateContent(responseModalities: ["AUDIO"]) |
| Live API(リアルタイム音声・文字起こし・翻訳) | live | POST /live/token で一時トークンを取得し、アプリから Google へ直接接続 |
| エージェント(Antigravity / Deep Research / Computer Use) | agent | POST /interactions(Computer Use は generateContent) |
| 検索 / 地図グラウンディング | grounding.search / grounding.maps | 対応モデルで grounding または tools を指定 |
AI API エンドポイント
| メソッド | パス | 必要な機能 / 説明 |
|---|---|---|
| GET | /tokeninfo | キーの ID・使える機能・上限(機能の指定は不要) |
| GET | /models /models/{model} | モデル一覧 / 詳細(?verify=1 で実在確認) |
| POST | /generate | generate(+ grounding.*)。model prompt のほか system temperature max_output_tokens grounding: {"search":true,"maps":true} |
| POST | /models/{model}:generateContent | Gemini の generateContent をそのまま中継(画像・音声入力、tools、構造化出力など)。TTS もここ |
| POST | /models/{model}:streamGenerateContent | 同上のストリーミング(SSE)。?alt=sse を付けると Gemini と同じ形式で逐次受信 |
| POST | /live/token | live。{"model":"..."} を送ると、30分有効・1回限りの一時トークンと WebSocket URL を返します |
| POST / GET | /interactions /interactions/{id} | agent。agent input を送ると、サンドボックス(environment は省略時 "remote")で非同期に実行(background は省略時 true)。結果は GET でポーリング |
| GET | /usage | このキーの直近24時間の利用状況と上限 |
成功時は {"success":true,"data":…,"meta":{…}}、失敗時は {"success":false,"error":"invalid_token","error_description":"…"} を返します。generateContent 系は、Google の仕様どおりの JSON が data に入ります。
ツール(tools)は googleSearch googleMaps codeExecution urlContext functionDeclarations(および Computer Use モデルの computerUse)のみ指定できます。モデルが対応していないツールや、キーに許可されていない機能は 400 / 403 になります。
AI API リクエスト例
# テキスト生成 curl -X POST https://ai.apis.shohs.net/v1/generate \ -H "Authorization: Bearer shk_YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"model":"gemini-3.5-flash","prompt":"日本の首都を一文で説明して","temperature":0.4}' # Google 検索で裏付けを取って回答(grounding.search が必要) curl -X POST https://ai.apis.shohs.net/v1/generate \ -H "Authorization: Bearer shk_YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"model":"gemini-3.5-flash","prompt":"今日の東京の天気は?","grounding":{"search":true}}' # generateContent をそのまま使う(画像入力など) curl -X POST https://ai.apis.shohs.net/v1/models/gemini-3.5-flash:generateContent \ -H "Authorization: Bearer shk_YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"contents":[{"role":"user","parts":[{"text":"こんにちは"}]}]}' # ストリーミング(SSE) curl -N -X POST "https://ai.apis.shohs.net/v1/models/gemini-3.5-flash:streamGenerateContent?alt=sse" \ -H "Authorization: Bearer shk_YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"contents":[{"role":"user","parts":[{"text":"1から5まで数えて"}]}]}' # エージェントを実行 → 結果を取得 curl -X POST https://ai.apis.shohs.net/v1/interactions \ -H "Authorization: Bearer shk_YOUR_API_KEY" -H "Content-Type: application/json" \ -d '{"agent":"antigravity-preview-09-2026","input":"フィボナッチ数列を計算して表にして"}' curl -H "Authorization: Bearer shk_YOUR_API_KEY" https://ai.apis.shohs.net/v1/interactions/INTERACTION_ID
// PHP(サーバー側から呼ぶ。キーは環境変数や設定ファイルに置く)
$ch = curl_init('https://ai.apis.shohs.net/v1/generate');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('SHOHS_AI_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['model' => 'gemini-3.5-flash', 'prompt' => 'こんにちは']),
]);
$res = json_decode(curl_exec($ch), true);
echo $res['data']['text'];
// JavaScript(Node.js などサーバー側。ブラウザに直接キーを埋め込まないこと)
const res = await fetch('https://ai.apis.shohs.net/v1/generate', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + process.env.SHOHS_AI_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'gemini-3.5-flash', prompt: 'こんにちは' }),
});
console.log((await res.json()).data.text);
# Python
import os, requests
r = requests.post('https://ai.apis.shohs.net/v1/generate',
headers={'Authorization': 'Bearer ' + os.environ['SHOHS_AI_KEY']},
json={'model': 'gemini-3.5-flash', 'prompt': 'こんにちは'}, timeout=120)
print(r.json()['data']['text'])
AI API の制限とエラー
| ステータス | error | 説明 |
|---|---|---|
| 401 | invalid_token | API キーが無い・形式が違う・削除済み、またはプロジェクトで AI API が無効 |
| 403 | insufficient_scope | このキーに許可されていない機能を使おうとした |
| 403 | origin_not_allowed / ip_not_allowed | 許可オリジン / 許可 IP の外から呼ばれた |
| 400 / 422 | invalid_field / validation_failed | 指定できない項目、または入力の誤り(errors に項目別の理由) |
| 404 | model_not_found | 一覧にないモデル。GET /models で確認 |
| 413 | payload_too_large | リクエストが大きすぎる(上限 20 MB) |
| 429 | rate_limit_exceeded / daily_quota_exceeded | 1分 / 1日の上限超過。Retry-After を待って再試行 |
| 502 / 404 | upstream_error | Google 側のエラー(モデルの終了など)。error_description に Google のメッセージが入ります |
生成には数秒〜数十秒かかることがあります。クライアントのタイムアウトは 120 秒以上にしてください。長時間かかる処理は、ストリーミングまたはエージェント(非同期)を使います。
AI API を安全に使う
- API キーはサーバー側の環境変数や設定ファイルで管理し、Git・ブラウザのコード・ログに含めないでください。
- 用途ごとにキーを分け、必要な機能だけを許可し、1分 / 1日の上限を実際の利用量に近い値にしてください。
- 固定のサーバーから呼ぶ場合は許可 IPを、ブラウザから呼ぶ場合は許可オリジンを設定してください。
- 利用者の入力をそのまま
toolsやsystemに渡さないでください(プロンプトインジェクション対策)。 - Live API の一時トークンは 1 回限り・短時間で失効します。ブラウザには一時トークンだけを渡し、API キーは渡さないでください。
- 利用状況は
GET /usageと Console で確認できます。身に覚えのない増加があれば、すぐにキーを削除してください。
ユーザー情報エンドポイント (UserInfo)
アクセストークンを使用して、ユーザーのプロフィール(名前、メール、画像など)を取得します。
https://auth.shohs.net/oauth2/userinfo.php# リクエスト
GET /oauth2/userinfo.php
Authorization: Bearer YOUR_ACCESS_TOKEN
// レスポンス
{
"sub": "ACCOUNT_ID",
"name": "表示名 (姓名)",
"preferred_username": "ユーザーID",
"email": "メールアドレス",
"picture": "https://apis.shohs.net/user_avatar/?avatar_id=...",
"email_verified": true
}
IDトークンの検証
id_token は JWT(JSON Web Token)形式で発行され、「.」区切りの3パートで構成されます(ヘッダー / ペイロード / 署名)。UserInfoエンドポイントを呼ばずに、この中身だけでユーザーの認証結果を確認することもできます。
代表的なクレーム
| クレーム | 説明 |
|---|---|
iss | 発行者。https://auth.shohs.net であることを確認 |
aud | 受信者。自分の client_id と一致するか確認 |
sub | ユーザーの一意な識別子(account_id) |
exp | 有効期限(UNIXタイムスタンプ)。過ぎていないか確認 |
iat | 発行日時(UNIXタイムスタンプ) |
nonce | 認可リクエスト時に指定した値と一致するか確認(指定した場合) |
最低限の検証手順
- ヘッダーの
algクレームを確認し、想定するアルゴリズムであることを確認する。 iss・aud・expを検証する。nonceを指定した場合は、その値が一致するか検証する。- 署名を検証する(利用するJWTライブラリに依存します)。
署名検証を省略して sub などのクレームだけを信用するのは危険です。改ざんされたトークンを受け入れてしまう可能性があります。必ずライブラリの検証機能を使ってください。
エンドポイント一覧
実装時に参照する各エンドポイントURLとスコープのまとめです。
| Authorize URL | https://auth.shohs.net/oauth2/authorize.php |
|---|---|
| Token URL | https://auth.shohs.net/oauth2/token.php |
| User Info URL | https://auth.shohs.net/oauth2/userinfo.php |
| Scope | openid profile email name |
| Attribute mapping (Avatar) | picture |
エラーコード
エラー時は該当するHTTPステータスとともに、JSONで error フィールドが返ります。
| ステータス | error | 説明 |
|---|---|---|
| 400 | invalid_request | 必須パラメータの不足、または形式不正 |
| 401 | invalid_client | Client ID / Client Secretが一致しない |
| 400 | invalid_grant | 認可コードが無効・期限切れ・使用済み |
| 400 | redirect_uri_mismatch | 登録済みのRedirect URIと一致しない |
| ステータス | error | 説明 |
|---|---|---|
| 401 | invalid_token | アクセストークンが無効・期限切れ(UserInfo呼び出し時) |
| 403 | access_denied | ユーザーが同意画面で拒否した |
| 429 | rate_limited | 短時間に多数のリクエストが送られた、または認証の失敗が続いて一時ロックされた。Retry-After 秒後に再試行(詳細はリトライとエラー処理を参照) |
| 405 / 413 | method_not_allowed / request_too_large | 許可されていない HTTP メソッド、またはリクエストが大きすぎる |
| 500 | server_error | 認可サーバー側の一時的な問題 |
コード例: cURL
実装前の動作確認や、シェルスクリプトからの呼び出しに使えます。
トークン交換
curl -X POST https://auth.shohs.net/oauth2/token.php \
-d grant_type=authorization_code \
-d code="AUTH_CODE" \
-d client_id="YOUR_CLIENT_ID" \
-d client_secret="YOUR_CLIENT_SECRET" \
-d redirect_uri="YOUR_REDIRECT_URI"
UserInfoの取得
curl https://auth.shohs.net/oauth2/userinfo.php \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
コード例: JavaScript
Node.js(サーバーサイド)を想定した例です。fetch はNode 18以降で標準利用できます。
認可URLの生成
const params = new URLSearchParams({
client_id: 'YOUR_CLIENT_ID',
redirect_uri: 'YOUR_REDIRECT_URI',
response_type: 'code',
scope: 'openid profile email name',
state: state, // セッションに保存したランダム値
});
const authorizeUrl = `https://auth.shohs.net/oauth2/authorize.php?${params}`;
res.redirect(authorizeUrl);
コールバックでのトークン交換
const res = await fetch('https://auth.shohs.net/oauth2/token.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: req.query.code,
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
redirect_uri: 'YOUR_REDIRECT_URI',
}),
});
const tokens = await res.json(); // { access_token, id_token, ... }
UserInfoの取得
const profile = await fetch('https://auth.shohs.net/oauth2/userinfo.php', {
headers: { Authorization: `Bearer ${tokens.access_token}` },
}).then(r => r.json());
コード例: PHP
既存のSHOHS系アプリで使われているcurl拡張を使った例です。
認可URLの生成
$state = bin2hex(random_bytes(16));
$_SESSION['oauth_state'] = $state;
$params = http_build_query([
'client_id' => 'YOUR_CLIENT_ID',
'redirect_uri' => 'YOUR_REDIRECT_URI',
'response_type' => 'code',
'scope' => 'openid profile email name',
'state' => $state,
]);
header('Location: https://auth.shohs.net/oauth2/authorize.php?' . $params);
exit;
コールバックでのトークン交換
if (($_GET['state'] ?? '') !== ($_SESSION['oauth_state'] ?? '')) {
http_response_code(400);
exit('state mismatch');
}
$ch = curl_init('https://auth.shohs.net/oauth2/token.php');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => http_build_query([
'grant_type' => 'authorization_code',
'code' => $_GET['code'],
'client_id' => 'YOUR_CLIENT_ID',
'client_secret' => 'YOUR_CLIENT_SECRET',
'redirect_uri' => 'YOUR_REDIRECT_URI',
]),
]);
$tokens = json_decode(curl_exec($ch), true);
curl_close($ch);
UserInfoの取得
$ch = curl_init('https://auth.shohs.net/oauth2/userinfo.php');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $tokens['access_token']],
]);
$profile = json_decode(curl_exec($ch), true);
curl_close($ch);
ホスティング環境によっては、サーバーから外部へのcurlアクセスがブロックされている場合があります。その場合はブラウザ側(JavaScript)で完結させる設計も検討してください。
コード例: Python
requests ライブラリを使った例です(Flaskを想定)。
認可URLの生成
import secrets
from urllib.parse import urlencode
state = secrets.token_hex(16)
session['oauth_state'] = state
params = urlencode({
'client_id': 'YOUR_CLIENT_ID',
'redirect_uri': 'YOUR_REDIRECT_URI',
'response_type': 'code',
'scope': 'openid profile email name',
'state': state,
})
return redirect(f'https://auth.shohs.net/oauth2/authorize.php?{params}')
コールバックでのトークン交換
import requests
if request.args.get('state') != session.get('oauth_state'):
abort(400, 'state mismatch')
resp = requests.post('https://auth.shohs.net/oauth2/token.php', data={
'grant_type': 'authorization_code',
'code': request.args['code'],
'client_id': 'YOUR_CLIENT_ID',
'client_secret': 'YOUR_CLIENT_SECRET',
'redirect_uri': 'YOUR_REDIRECT_URI',
})
tokens = resp.json()
UserInfoの取得
profile = requests.get(
'https://auth.shohs.net/oauth2/userinfo.php',
headers={'Authorization': f"Bearer {tokens['access_token']}"},
).json()
トークンの取り扱いと保管
access_tokenとid_tokenはサーバーサイドのセッションストアなど、ユーザーから直接読めない場所に保管してください。- ブラウザに保存する必要がある場合は、
HttpOnly・Secure属性付きのCookieを使い、JavaScriptから読み取れないようにしてください。 expires_in(秒)が経過したトークンは無効になります。有効期限が切れた場合は、あらためて認可フローをやり直してください(現在のレスポンスにrefresh_tokenは含まれません)。- ログアウト時は、保持しているトークンをアプリ側で破棄してください。
リトライとエラー処理
429(レート制限)や500〜503系のエラーが返った場合は、指数バックオフ(例: 1秒→2秒→4秒)を挟んでリトライしてください。具体的なレート制限のしきい値は現時点で公開されていないため、429を受け取った場合は間隔を空けて再試行する実装にしておくと安全です。400・401系のエラー(パラメータ不正、認証失敗)はリトライしても解消しないため、ログに記録した上でユーザーに再認可を促してください。- ネットワークタイムアウトが発生した場合、トークンが実際には発行されている可能性があります。
codeは使い捨てのため、同じcodeで再試行せず、認可フローからやり直してください。
トラブルシューティング / FAQ
Q. 開発中に Redirect URI として localhost は使えますか?
本番のRedirect URIにはHTTPSが必須です。ローカル開発時の扱いについては、Cloud Consoleでクライアントを作成する際の設定に従ってください。
Q. 「redirect_uri_mismatch」エラーが出ます
Cloud Consoleに登録したRedirect URIと、認可リクエストに指定した redirect_uri が完全一致(末尾のスラッシュの有無なども含めて)している必要があります。
Q. 「invalid_grant」エラーが出ます
認可コードは一度きりしか使えず、有効期限も短く設定されています。同じcodeを2回使っていないか、発行から時間が経ちすぎていないかを確認してください。
Q. Client Secretを忘れてしまいました
Client Secretは作成時にしか表示されません。忘れてしまった場合は、Cloud Consoleから該当クライアントを削除し、新しいクライアントとして再発行してください。
Q. 内部と外部、どちらを選べばよいですか?
SHOHSグループ内で完結する社内向けツールであれば「内部」、外部のユーザーにも使ってもらうアプリであれば「外部」を選んでください。詳細はユーザータイプを参照してください。
Q. リフレッシュトークンは発行されますか?
現在のトークンレスポンスには refresh_token は含まれていません。expires_inで示された有効期限が切れた場合は、再度Authorization Code Flowを実行してください。
セキュリティ ベストプラクティス
- Client Secretはサーバー側でのみ保持し、ブラウザやモバイルアプリのコードに埋め込まないでください。
- 認可リクエストには必ず
stateを付与し、コールバック時に検証してください。 - 可能であれば PKCE(Proof Key for Code Exchange)の利用を検討してください。
- IDトークンを利用する場合は、署名・iss・aud・exp の検証を省略しないでください。
- Redirect URIは可能な限り厳密に一致させ、ワイルドカードでの登録は避けてください。
- トークンはログに出力しない、URLのクエリパラメータに含めない(第三者のアクセスログに残るため)など、漏えい経路を減らしてください。
- 複数のアプリでSHOHSアカウントと連携する場合、スコープは必要最小限に絞ってください。
- AI API の API キーも Client Secret と同様にサーバー側で管理し、ブラウザには渡さないでください(詳細)。
認可サーバー側の保護
auth.shohs.net の OAuth エンドポイントは、次の保護をサーバー自身で行っています。
- 同意画面の送信を、画面ごとに発行する使い捨てトークンと送信元で検証し、他のサイトからの勝手な「許可」を防ぎます(CSRF 対策)。
- 同意画面は他のサイトの
<iframe>に埋め込めません(クリックジャッキング対策)。 - パラメータの形式・長さを検証し、トークン・UserInfo・認可の各エンドポイントでリクエスト数を制限します。Client Secret や認可コードの総当たりは、失敗が続くと一時ロックされます。
- 認可コードは5分・1回限りで、リダイレクト URI は登録値との完全一致のみ許可します。
Client Secretが漏えいした疑いがある場合は、Cloud Console からそのクライアントを削除し、新しい認証情報を再発行してください。
変更履歴
| 日付 | 内容 |
|---|---|
| 2026-10-03 | SHOHS AI API(API キー認証)のドキュメントを追加。PKCE(S256)に対応し、トークンエンドポイントの失敗ロック・リフレッシュトークン再利用検知、同意画面の CSRF / クリックジャッキング対策を追加 |
| 2026-09-30 | Runtera API(OAuth 2.0 Bearer)を追加。refresh_token グラントに対応 |
| 2026-08-30 | Developer Portal 初版公開。Authorization Code Flow、UserInfoエンドポイント、コード例、FAQを追加 |