SHOHS
SHOHSOAuth 2.0 Developer Portal
route認証フロー vpn_keyCloud Console を開く
SHOHS IDENTITY PLATFORM

OAuth 2.0 Developer Portal

SHOHSアカウントでのログインをあなたのアプリに組み込むための、認証フロー・エンドポイント・スコープのリファレンスです。クライアントの発行や管理は Cloud Console から行います。

insights概要

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 の併用を推奨します。

info

すべてのやり取りは HTTPS 経由のみサポートしています。開発中であっても Redirect URI には HTTPS を使用してください(localhost の扱いは FAQ を参照)。

rocket_launchクイックスタート

最初のOAuthクライアントを作成し、最小限のフローを動かすまでの手順です。

  1. Cloud Console でプロジェクトを作成し、使う API(例: Runtera API)を「ライブラリ」から有効化します。
  2. 「OAuth 同意画面」でアプリ名・サポートメールを設定し、「データアクセス」で使うスコープを追加します。
  3. 「認証情報」から「認証情報を作成」を選び、名前と Redirect URI を登録します。
  4. 公開範囲を「内部」「外部」のいずれかで設定します(詳細はユーザータイプを参照)。
  5. 発行された Client ID と Client Secret を安全な場所に保存します(Secretは作成時の一度しか表示されません)。
  6. 下記の Authorization Code Flow に従って、ユーザーをログインへ導きます。
  7. 受け取った code をトークンエンドポイントに渡し、access_token を取得します。
  8. UserInfoエンドポイントでプロフィール情報を取得し、自アプリのアカウントと紐付けます。
tips_and_updates

まずはcURLでの動作確認から始めると、実装前にレスポンス形式を確認できます。

menu_book用語集

このドキュメント内で使われる用語の早見表です。

用語説明
Client IDアプリを一意に識別するID。公開しても問題ない値
Client Secretクライアントの正当性を証明する秘密の値。サーバー側のみで保持する
Authorization Code(認可コード)ユーザーが同意した後に発行される、一度限り使える短命なコード
Access TokenUserInfoなど保護されたAPIを呼び出すための資格情報
ID Tokenユーザーの認証結果を表すJWT。誰が・いつ認証したかを表す
Scopeアプリが要求する権限の範囲(openid / profile / email / name)
Redirect URI認可・認証の完了後にユーザーが戻ってくるURL
stateCSRF対策として認可リクエストに含めるランダムな値
nonceIDトークンのリプレイ攻撃対策として使う使い捨ての値
PKCEClient Secretを安全に扱えないクライアント向けの、認可コード横取り対策

routeAuthorization Code Flow

標準的な4ステップで、ユーザーの同意を得てからアクセストークンを取得します。

1
認可リクエスト
ユーザーを /oauth2/authorize.php へリダイレクト
2
ユーザー同意
SHOHSアカウントでログインし、権限を許可
3
認可コード発行
Redirect URI へ code 付きで戻る
4
トークン交換
/oauth2/token.php へ code を送り、access_token / id_token を取得

各ステップの詳細

  1. 認可リクエスト: ブラウザを 認可エンドポイント にリダイレクトします。state を必ず生成し、セッションに保存しておきます。
  2. ユーザー同意: auth.shohs.net 上でログインし、要求されたスコープへの同意画面が表示されます(既に同意済みの場合はスキップされることがあります)。
  3. 認可コード発行: 同意後、redirect_uri?code=XXXX&state=YYYY の形でブラウザがあなたのアプリに戻ります。ここで受け取った state が送信時と一致することを必ず検証してください。
  4. トークン交換: 受け取った code をサーバーサイドからトークンエンドポイントに送信し、access_token と id_token を取得します。認可コードは一度使うと失効するため、再利用はできません。

enhanced_encryptionPKCEの利用

PKCE(Proof Key for Code Exchange)は、Client Secretを安全に保管できないSPAやモバイルアプリ・デスクトップアプリで、認可コードの横取りを防ぐための仕組みです。Client Secretを使えるサーバーサイドアプリでも、追加の防御として併用できます。

手順

  1. ランダムな code_verifier(43〜128文字の英数字と一部記号)を生成し、セッションに保持します。
  2. code_verifier を SHA-256 でハッシュ化し、Base64URLエンコードした code_challenge を作成します。
  3. 認可リクエストに code_challenge と code_challenge_method=S256 を追加します(plain 方式は受け付けません)。
  4. トークンリクエスト時に、元の 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 のパラメータに付与
info

Client Secretを安全に保持できる通常のサーバーサイドアプリでは必須ではありませんが、多層防御として実装しておくことを推奨します。

fingerprintstate / nonce パラメータ

どちらもランダムな使い捨ての値ですが、目的が異なります。

パラメータ目的検証タイミング
stateCSRF対策。リクエストとコールバックが同一ブラウザセッションからのものであることを保証認可コード受け取り時、送信した値と一致するか確認
nonceIDトークンのリプレイ攻撃対策id_token をデコードし、nonce クレームが送信した値と一致するか確認
warning

state の検証を省略すると、CSRF攻撃によって攻撃者のアカウントが被害者のアプリに紐付けられる危険があります。必ず検証してください。

groupユーザータイプ(内部 / 外部)

クライアント作成時に、公開範囲をどちらかに設定します。

domain内部

@shohs アカウント以外のみが作成できるクライアントです。@shohs アカウントでは選択できません。

public外部

SHOHSアカウントを問わず、誰でも利用できるクライアントです。

keyスコープ

認可リクエストの 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 の範囲だけが共有されます。

login認可エンドポイント

GEThttps://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

vpn_keyトークンエンドポイント

認可コードを access_token および id_token と交換します。

POSThttps://auth.shohs.net/oauth2/token.php
パラメータ説明
client_idクライアントID
client_secretクライアントシークレット
code認可エンドポイントで受け取ったコード
grant_typeauthorization_code または refresh_token
refresh_tokengrant_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日間有効です。

directions_runRuntera API

SHOHS OAuth 2.0 のアクセストークンで、Runtera の練習記録・体調・目標・レース結果を読み書きできます。

BASEhttps://runtera.apis.shohs.net/v1

利用手順

  1. Cloud Console の「ライブラリ」で Runtera API を有効にし、「OAuth 同意画面 → データアクセス」で使うスコープを追加します(プロジェクト内の全クライアントに反映されます)。
  2. 認可リクエストの scope にそのスコープを指定します。利用者には同意画面で権限の一覧が表示され、不要なものは外せます。
  3. 受け取った access_token を Authorization: Bearer ... で送ります。

API が操作できるのは常にトークンの持ち主のデータだけです(リクエストで account_id は指定しません)。実際に使える権限は「利用者が許可したスコープ」と「Console でクライアントに許可されているスコープ」の両方に含まれるものです。Console でスコープを外すと、発行済みトークンにも即時に反映されます。

ブラウザ(JavaScript)から呼ぶ場合(CORS)は、クライアントの「JavaScript生成元」が未登録ならすべてのオリジンを許可し、登録がある場合は登録したオリジンのみ許可します。サーバー間の呼び出しは登録不要です。

keyRuntera 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読み取りレース記録の取得

list_altRuntera API エンドポイント

メソッドパススコープ
GET/tokeninfo有効なトークンのみ(持ち主・クライアント・有効スコープ)
GET/meruntera.profile.read
GET/records /records/{training_id}runtera.records.read (from to training_id limit offset)
POST/recordsruntera.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/racesruntera.races.read (from to limit offset)

成功時は {"success":true,"data":…}、失敗時は {"success":false,"error":"invalid_token","error_description":"…"} を返します。主なステータスは 400(パラメータ不正)/ 401(トークン無効)/ 403(スコープ不足・オリジン未登録・Runtera 未登録)/ 404 / 422(入力エラー。errors に項目別の理由)です。

terminalRuntera 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

auto_awesomeSHOHS AI API

SHOHS 経由で Google Gemini(テキスト生成・音声合成・Live API・エージェント・検索 / 地図グラウンディング)を利用できる API です。Google の API キーはサーバー側だけが持ち、アプリには渡りません。

BASEhttps://ai.apis.shohs.net/v1
info

OAuth ではなく API キーで認証します。 AI API は SHOHS ユーザーの個人情報を扱わないため、同意画面や access_token は使いません。Cloud Console で発行した API キー(shk_ で始まる文字列)を Authorization: Bearer に付けて呼び出します。

利用手順

  1. Cloud Console でプロジェクトを作成し、「ライブラリ」から SHOHS AI API を有効にします。
  2. 「認証情報 → API キーを作成」で、キー名・使う機能・利用制限を決めて発行します。キーは作成直後の1回だけ表示されます。
  3. リクエストに Authorization: Bearer shk_... を付けて呼び出します。
# 動作確認(キーの機能・上限を返します)
curl -H "Authorization: Bearer shk_YOUR_API_KEY" https://ai.apis.shohs.net/v1/tokeninfo

プロジェクトで AI API を無効にすると、そのプロジェクトのすべての API キーが即座に使えなくなります。

keyAPI キーの発行と制限

キーごとに、使える機能・呼び出せる場所・利用量の上限を設定できます。用途ごとにキーを分けると、漏えい時の影響を小さくできます。

項目説明
使える機能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 を返します
warning

API キーはブラウザやモバイルアプリのコードに埋め込まないでください。キーを知っている人は誰でもあなたの上限まで AI を使えます。公開する Web ページから使う場合は、許可オリジンと低めの上限を必ず設定するか、自分のサーバーを経由して呼び出してください。キーが漏れた疑いがあるときは、Console でそのキーを削除して再発行します。

キーは SHA-256 のハッシュとして保存され、SHOHS 側でも元の値を確認できません。紛失した場合は再発行してください。

memory利用できるモデル

最新の一覧と、各モデルが対応する機能は GET /v1/models で取得できます(?verify=1 を付けると、Google 側に実在するか確認します)。Google 側でモデルが終了・名称変更されることがあるため、モデル ID は固定せず一覧から選ぶことをお勧めします。

種類機能使い方
テキスト生成(Gemini Flash / Flash Lite 系)generatePOST /generate または POST /models/{model}:generateContent
音声合成(Gemini TTS)speechPOST /models/{model}:generateContent(responseModalities: ["AUDIO"])
Live API(リアルタイム音声・文字起こし・翻訳)livePOST /live/token で一時トークンを取得し、アプリから Google へ直接接続
エージェント(Antigravity / Deep Research / Computer Use)agentPOST /interactions(Computer Use は generateContent)
検索 / 地図グラウンディングgrounding.search / grounding.maps対応モデルで grounding または tools を指定

list_altAI API エンドポイント

メソッドパス必要な機能 / 説明
GET/tokeninfoキーの ID・使える機能・上限(機能の指定は不要)
GET/models /models/{model}モデル一覧 / 詳細(?verify=1 で実在確認)
POST/generategenerate(+ grounding.*)。model prompt のほか system temperature max_output_tokens grounding: {"search":true,"maps":true}
POST/models/{model}:generateContentGemini の generateContent をそのまま中継(画像・音声入力、tools、構造化出力など)。TTS もここ
POST/models/{model}:streamGenerateContent同上のストリーミング(SSE)。?alt=sse を付けると Gemini と同じ形式で逐次受信
POST/live/tokenlive。{"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 になります。

terminalAI 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'])

speedAI API の制限とエラー

ステータスerror説明
401invalid_tokenAPI キーが無い・形式が違う・削除済み、またはプロジェクトで AI API が無効
403insufficient_scopeこのキーに許可されていない機能を使おうとした
403origin_not_allowed / ip_not_allowed許可オリジン / 許可 IP の外から呼ばれた
400 / 422invalid_field / validation_failed指定できない項目、または入力の誤り(errors に項目別の理由)
404model_not_found一覧にないモデル。GET /models で確認
413payload_too_largeリクエストが大きすぎる(上限 20 MB)
429rate_limit_exceeded / daily_quota_exceeded1分 / 1日の上限超過。Retry-After を待って再試行
502 / 404upstream_errorGoogle 側のエラー(モデルの終了など)。error_description に Google のメッセージが入ります

生成には数秒〜数十秒かかることがあります。クライアントのタイムアウトは 120 秒以上にしてください。長時間かかる処理は、ストリーミングまたはエージェント(非同期)を使います。

shieldAI API を安全に使う

  • API キーはサーバー側の環境変数や設定ファイルで管理し、Git・ブラウザのコード・ログに含めないでください。
  • 用途ごとにキーを分け、必要な機能だけを許可し、1分 / 1日の上限を実際の利用量に近い値にしてください。
  • 固定のサーバーから呼ぶ場合は許可 IPを、ブラウザから呼ぶ場合は許可オリジンを設定してください。
  • 利用者の入力をそのまま tools や system に渡さないでください(プロンプトインジェクション対策)。
  • Live API の一時トークンは 1 回限り・短時間で失効します。ブラウザには一時トークンだけを渡し、API キーは渡さないでください。
  • 利用状況は GET /usage と Console で確認できます。身に覚えのない増加があれば、すぐにキーを削除してください。

badgeユーザー情報エンドポイント (UserInfo)

アクセストークンを使用して、ユーザーのプロフィール(名前、メール、画像など)を取得します。

GEThttps://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
}

verified_userIDトークンの検証

id_token は JWT(JSON Web Token)形式で発行され、「.」区切りの3パートで構成されます(ヘッダー / ペイロード / 署名)。UserInfoエンドポイントを呼ばずに、この中身だけでユーザーの認証結果を確認することもできます。

代表的なクレーム

クレーム説明
iss発行者。https://auth.shohs.net であることを確認
aud受信者。自分の client_id と一致するか確認
subユーザーの一意な識別子(account_id)
exp有効期限(UNIXタイムスタンプ)。過ぎていないか確認
iat発行日時(UNIXタイムスタンプ)
nonce認可リクエスト時に指定した値と一致するか確認(指定した場合)

最低限の検証手順

  1. ヘッダーの alg クレームを確認し、想定するアルゴリズムであることを確認する。
  2. iss・aud・exp を検証する。
  3. nonce を指定した場合は、その値が一致するか検証する。
  4. 署名を検証する(利用するJWTライブラリに依存します)。
warning

署名検証を省略して sub などのクレームだけを信用するのは危険です。改ざんされたトークンを受け入れてしまう可能性があります。必ずライブラリの検証機能を使ってください。

list_altエンドポイント一覧

実装時に参照する各エンドポイントURLとスコープのまとめです。

Authorize URLhttps://auth.shohs.net/oauth2/authorize.php
Token URLhttps://auth.shohs.net/oauth2/token.php
User Info URLhttps://auth.shohs.net/oauth2/userinfo.php
Scopeopenid profile email name
Attribute mapping (Avatar)picture

error_outlineエラーコード

エラー時は該当するHTTPステータスとともに、JSONで error フィールドが返ります。

ステータスerror説明
400invalid_request必須パラメータの不足、または形式不正
401invalid_clientClient ID / Client Secretが一致しない
400invalid_grant認可コードが無効・期限切れ・使用済み
400redirect_uri_mismatch登録済みのRedirect URIと一致しない
ステータスerror説明
401invalid_tokenアクセストークンが無効・期限切れ(UserInfo呼び出し時)
403access_deniedユーザーが同意画面で拒否した
429rate_limited短時間に多数のリクエストが送られた、または認証の失敗が続いて一時ロックされた。Retry-After 秒後に再試行(詳細はリトライとエラー処理を参照)
405 / 413method_not_allowed / request_too_large許可されていない HTTP メソッド、またはリクエストが大きすぎる
500server_error認可サーバー側の一時的な問題

terminalコード例: 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コード例: 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コード例: 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);
info

ホスティング環境によっては、サーバーから外部へのcurlアクセスがブロックされている場合があります。その場合はブラウザ側(JavaScript)で完結させる設計も検討してください。

codeコード例: 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()

lockトークンの取り扱いと保管

  • access_token と id_token はサーバーサイドのセッションストアなど、ユーザーから直接読めない場所に保管してください。
  • ブラウザに保存する必要がある場合は、HttpOnly・Secure属性付きのCookieを使い、JavaScriptから読み取れないようにしてください。
  • expires_in(秒)が経過したトークンは無効になります。有効期限が切れた場合は、あらためて認可フローをやり直してください(現在のレスポンスに refresh_token は含まれません)。
  • ログアウト時は、保持しているトークンをアプリ側で破棄してください。

restart_altリトライとエラー処理

  • 429(レート制限)や 500〜503 系のエラーが返った場合は、指数バックオフ(例: 1秒→2秒→4秒)を挟んでリトライしてください。具体的なレート制限のしきい値は現時点で公開されていないため、429を受け取った場合は間隔を空けて再試行する実装にしておくと安全です。
  • 400・401 系のエラー(パラメータ不正、認証失敗)はリトライしても解消しないため、ログに記録した上でユーザーに再認可を促してください。
  • ネットワークタイムアウトが発生した場合、トークンが実際には発行されている可能性があります。codeは使い捨てのため、同じcodeで再試行せず、認可フローからやり直してください。

quizトラブルシューティング / 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を実行してください。

shieldセキュリティ ベストプラクティス

  • 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 は登録値との完全一致のみ許可します。
warning

Client Secretが漏えいした疑いがある場合は、Cloud Console からそのクライアントを削除し、新しい認証情報を再発行してください。

history変更履歴

日付内容
2026-10-03SHOHS AI API(API キー認証)のドキュメントを追加。PKCE(S256)に対応し、トークンエンドポイントの失敗ロック・リフレッシュトークン再利用検知、同意画面の CSRF / クリックジャッキング対策を追加
2026-09-30Runtera API(OAuth 2.0 Bearer)を追加。refresh_token グラントに対応
2026-08-30Developer Portal 初版公開。Authorization Code Flow、UserInfoエンドポイント、コード例、FAQを追加