Web API 設計の要点
『Web API: The Good Parts』(水野貴明, O’Reilly Japan) を読んで理解した内容を、 自分の言葉と自分の文脈(API Gateway での設定)に落として書いたもの。本文の引用はしていない。 HTTP ヘッダやステータスコードの仕様そのものは RFC / W3C に基づく事実。
ApiGatewayでAPIを設定するに当たっての要点をまとめてみます。 例は本の内容を参考に独自のものにしています。
[https://www.amazon.co.jp/Web-API-Parts-%E6%B0%B4%E9%87%8E-%E8%B2%B4%E6%98%8E/dp/4873116864]
URL
- ドメインはapi.から始める
https://api.example.com/XXXXXXXX/XXXXXXXXXXXX/
- すべて小文字にする
https://developer.github.com/v3/git/commits/
https://api.twitter.com/1.1/statuses/retweets/:id.json
↓
AWS::ApiGateway::ResourceのPathPartは全て小文字にする
- 複数形の名詞を使用する
users
activities
※get等の動詞は含めない
↓
DynamoDBのテーブル名、AWS::ApiGateway::ResourceのLogicalIDを複数形の名詞
- 複数単語はパスを分ける
| サービス | URL | 修正後URL |
|---|---|---|
| /statuses/user_timeline | /statuses/user/timeline/ | |
| Youtube | /guideCategories | guide/categories |
| /me/books.quotes | me/books/quotes/ | |
| /v1/people-search | vi/people/search | |
| Bit.ly | /v3/user/popular_earned_by_clicks | v3/user/popular/clicks |
| Disqus | /api/3.0/applications/listUsage.json | /api/3.0/applications/list/usage.json |
- 取得数と取得位置
limitとoffsetにする
※SQLでもlimitとoffsetが使用されている
※取得数はcount,per_page,maxResultsを使っているのもあるが少数
※取得位置はstart,page,cursorを使っているのもあるが少数
- 全文検索
q or query
※googleはqを使用している
- パス or クエリ
一意なリソースを表すのに必要な情報の場合はパス
※id等
省略可能でデフォルトが存在するものはクエリ
※offsetやlimit
- ドメインの次のパス
v1 or 1.0
api.twitter.com/1.1
api.foursquare.com/v2
www.googleapis.com/youtube/v3
api.linkedin.com/v1
developer.github.com/v3
※以下は通常のAPIとはURLが異なるようです
app.rakuten.co.jp/services/api
webservice.recruit.co.jp/hotpepper
webservice.recruit.co.jp/r25
jws.jalan.net/APIAdvance
api.mixi-platform.com/2
- メソッド
X-HTTP-Method-Overrideヘッダーを使用する
| メソッド名 | 操作 |
|---|---|
| GET | リソースの取得 |
| POST | リソースの新規登録 |
| PUT | 既存リソースの更新 |
| DELETE | リソースの削除 |
| PATCH | リソースの一部変更 |
| HEAD | リソースのメタ情報の取得 |
- 個々のデータ取得
id or id.json
- 一覧
XXX/list or XXX/list.json or XXX/XXXies
- 更新
XXX/updates or XXX/id
レスポンス
- 取得するフィールドを選べるようにする
https://api.exmample.com/v1/users/12345?fields=name,age
or
https://api.exmample.com/v1/users/12345?ResponseGroup=Medium
[http://docs.aws.amazon.com/AWSECommerceService/latest/DG/CHAP_ResponseGroupsList.html:title]
- エンベロープ
HTTP 自体が既に封筒の役割を果たしているので、その中でもう一度包み直す意味はない。
NG
{
"header": {
"status": "success",
"errorCode": 0,
},
"response": {
... 実際のデータ ...
}
}
- jsonのトップレベル
配列にしない
※JSONインジェクションを防ぐ
- 件数や次の結果の存在の有無
{
"timelines":[
:
],
hasNext: true
}
{
"kind": "plus#activityFeed",
"title": "Plus Public Activities Feed",
"nextPageToken": "CKaEL",
"items": [
{
"kind": "plus#activity",
"id": "123456789",
...
},
]
}
なるべく少ない単語数で表現する
NG:userRegistrationDateTime
※/users というユーザー情報を取得する API なら、最初の user はな くても問題ない
複数の単語を連結する場合、その連結方法は API 全体を通して統一する
キャメル
※jsonなので
変な省略形は極力利用しない
NG: timezone→tz
NG: location→lctn
- 性別
gender: male|female
※maleを1等にはしない
- 数値 誤差が出ないように文字列として扱う
462781738297483264がと462781738297483260される。
これはJavaScriptが大きな整数を扱うと誤差が出てしまうから
"id": 266031293949698048,
"id_str": "266031293949698048"
- エラー
200番台はリクエストが成功したときにだけ返す。 パラメータ不正や権限不足でエラーなのに、ボディにエラー情報を入れて 200 を返す実装をたまに見るが、 これは誤りで、実際に問題を起こす。
- エラーの詳細をクライアントに返す
twitter
{
"errors":[
{
"message":"Bad Authentication data",
"code":215
}
]
}
GitHub
{
"message": "Not Found",
"documentation_url": "https://developer.github.com/v3"
}
-
参考
[https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html:title] -
APIのステータスコードのアンチパターン
200を返して、レスポンスボディで独自ステータスコードを返す
- 202(Accepted)の実例
承認を必要とする場合は202
ファイルのダウンロードをする際はRetry-After ヘッダとともに202
- PUTやPATCH
200と共に生成したデータを返す
※賛否両論あり
- DELETE
204(No Content)を返す
※賛否両論あり
- Expires or Cache-Control
更新日時が決まっているものはExpires
他はCache-Controlを使用する
- Dateヘッダを必ず返す(500番台以外)
Date: Wed, 20 Aug 2014 11:10:39 GMT
※必ずHTTP時間の形式でGMTで(日本でも)
- Last-ModifiedとETagを使って、クライアントはキャッシュがfreshか確認
Last-Modified: Tue, 01 Jul 2014 00:00:00 GMT
ETag: "ff39b31e285573ee373af0d492aca581"
- クライアントにキャッシュしてほしくない場合
Cache-Control: no-cache
※Expiresは使わない
- クライアントにキャッシュしてほしい場合
Cache-Control: public, max-age=3600
- 機密情報の場合でプロキシサーバー等にキャッシュしてほしくない場合
Cache-Control: no-store
- 同じURLでも、リクエストヘッダによって結果が異なるものはVaryヘッダーを指定する
Vary: Accept-Encoding,User-Agent,Accept-Language
Vary: Accept
※Acceptヘッダーに任せる
- 新鮮ではないキャッシュを持っているキャッシュサーバー
stale-while-revalidateによる非同期のキャッシュの更新を使う
- Content-Typeは正しく設定する
Content-Type: application/json; charset=utf-8
※iOSのネットワーククライアント等では、Content-Typeが不正な場合はエラーになる
※jsonをtext/htmlにするとXSSが可能になる
- 独自ヘッダーの例
独自ヘッダを定義するときは、
X-の接頭辞に続けてサービス名・アプリ名・組織名を置くのが慣例。 ただし RFC 6648 でこのX-慣習自体は非推奨とされたので、新規に決めるなら接頭辞なしも検討する。
X-GitHub-Media-Type: github.v3
- CORS
リクエストヘッダーのOriginを検証して、同じものを以下のようにして返す
Access-Control-Allow-Origin: http://www.example.com
- ブラウザで自動的に行われるプリフライトリクエスト
OPTIONS /v1/users/12345 HTTP/1.1
Host: api.example.com
Accept: application/json
Origin: http://www.example.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: X-RequestId
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Access-Control-Allow-Origin: http://www.example.com
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: X-RequestId
Access-Control-Max-Age: 864000
Content-Length: 0
Content-Type: text/plain
- CORSでのユーザー認証情報
Access-Control-Allow-Credentials: true
※認証情報を認識しておりますということ
※なかった場合、ブラウザは受け取ったレスポンスを拒否してしまいます
セキュリティ
- HTTPSなら安全か
man-in-the-middle attack
クライアントが証明書の警告を無視する場合は可能
[https://www.youtube.com/watch?v=lLC4OxplXuE:embed:cite]
- Content-Type ユーザーからの入力を受け入れてJSONに埋め込んで返すようなAPIでContent-Typeがtext/htmlだとXSSが可能になる。 ※X-Content-Type-Options: nosniffもしておかないとブラウザによってはXSSが可能になる。
{"data":"<script>alert('xss');</script>"}
- エスケープ
/(スラッシュ)
<: \u003C
>: \u003E
\: \u005C
\ や "、'などに関しても、\u005Cや\u0022、\u0027のように16進数のエスケープ
U+2028 と U+2029
+ を \u002B
- XSRF
あらかじめ XSRF トークンを渡しておき、それがパラメータとして送られてこなければ拒否する。 あわせて
X-Requested-Withの有無を見て、無ければ許可しない。
- JSONハイジャック用コード
<script type="application/javascript">
var data;
Array = function() {
data = this;
};
</script>
<script type="application/javascript">
Object.prototype.__defineSetter__('id', function(obj){alert(obj);});
</script>
<script>
window.onerror = function(e) {}
</script>
<script src="https://api.example.com/v1/users/me" language="vbscript"></script>
- JSONハイジャックの対策
XMLHttpRequest or ブラウザ以外からのアクセスのみ
→X-Requested-Withがない場合はアクセスを許可しない
レスポンスヘッダー
→Content-Type: application/json
→X-Content-Type-Options: nosniff
トップレベルに配列を使用しない
JSONの先頭にfor (;;);
→Javascriptとして実行されても次にいけなくしている
※facebook
- リクエストの再送信
同じアクセスが何度も行われたらエラーにする
1回の購入につき1回のポイントの付与だけを行うようにチェック
→購入トークンの発行とサーバ側での保持、検証
- ヘッダー
この本が挙げていたセキュリティヘッダの一覧は、API 設計に統合した。
書籍の時点の推奨のうち X-XSS-Protection と Public-Key-Pins は
現在では非推奨になっているので、そちらに現行の一次情報(OWASP Secure Headers)で
更新したものを置いてある。
下の実例は、当時実際に観測されたレスポンスとして残す。
X-XSS-Protection と Strict-Transport-Security が並んでいるのはそのときの状態であって、
いま真似する対象ではない。
- ヘッダーの例
HTTP/1.1 200 OK
Date: Mon, 16 Jun 2014 21:39:06 GMT
Server: nginx
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
Tracer-Time: 43
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 499
Strict-Transport-Security: max-age=864000
X-ex: fastly_cdn
Content-Length: 11074
Accept-Ranges: bytes
Via: 1.1 varnish
X-Served-By: cache-ty66-TYO
X-Cache: MISS
X-Cache-Hits: 0
Vary: Accept-Encoding,User-Agent,Accept-Language
HTTP/1.1 200 OK
Server: GitHub.com
Date: Mon, 16 Jun 2014 21:32:36 GMT
Content-Type: application/json; charset=utf-8
Status: 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
X-RateLimit-Reset: 1402957018
Cache-Control: public, max-age=60, s-maxage=60
Last-Modified: Mon, 16 Jun 2014 04:55:23 GMT
ETag: "cbd0cecf6295eba60adc4c06c7836b8d"
Vary: Accept
X-GitHub-Media-Type: github.v3
X-XSS-Protection: 1; mode=block
X-Frame-Options: deny
Content-Security-Policy: default-src 'none'
Content-Length: 1201
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-GitHub-OTP, X-RateLimit- Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval
Access-Control-Allow-Origin: *
X-GitHub-Request-Id: 719794F7:01FB:299F044:539F6273 Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff
Vary: Accept-Encoding
X-Served-By: 971af40390ac4398fcdd45c8dab0fbe7
- DOS対策
• リミット値の設定
エンドポイント毎: 15分 15回(ユーザー)/180回(アプリ)
APIの種類(core,search等)毎: 1時間5000回/60回
※アクセス制限の緩和も想定しておく
- 上限オーバーしたとき
HTTP/1.1 429 Too Many Requests
Retry-After: 3600
- レスポンスヘッダーにレートリミットを渡す
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
- レートリミットの実装
redisにする
ドキュメント
[https://github.com/subosito/iglo:title]
[https://developers.facebook.com/tools/explorer/]
[https://console.developers.google.com/:embed:cite]
関連
- API 設計 — 同じ主題を実務の判断としてまとめたもの。ヘッダーの例が一部重なる
- 早見表 — 判断を短く引ける形にしたもの
- アプリケーションセキュリティ — 「セキュリティ」の節の現在の対応
- 認証と認可 — OAuth とトークンの置き場所