初めに
最近、業務でAPIに触れる機会が増えてきました。そこで、WebAPIについての知識が足りないなーと思い始めたので、定番の書籍(Web API The Good Parts)を読んで、その情報を自分なりに解釈してまとめてみました。さらに知りたいと思われた方はこちらの書籍をご覧ください。
対象
- Web APIやHTTPなどについてはある程度理解している。
- どういう風にAPIを設計すれば良いのかわからない。
全てを詳細に説明しているというよりはインデックス的な感覚でみてもらう方が良いと思います。
Web APIとは
こちらについては様々な記事があるので、そちらをご覧いただければと思います。
基本的な設計
では、まずWebAPIの基本的な設計からです。
以下のような特徴を持っているWebAPIが良いとされているようです。
覚えやすく、どんな機能を持つURIなのかがひと目でわかる
-
短く入力しやすい
OK: http://api.example.com/search
NG: http://api.example.com/service/api/search -
人間が読んで理解できる
NG: http://api.example.com/sv/u
NG: http://api.example.com/shouhin -
大文字小文字が混在していない
基本は全て小文字を使用する。ただし、get_user_nameやget-user-nameとすれば良いわけではなく、この場合は、そもそもこの名前自体に問題がある。もし繋げたい場合は極論、好みだがやチームの方針だが、ハイフンにしておくと良い。
NG: http://api.example.com/getUserName
NG: http://api.example.com/Users/1 -
改造しやすい、Hackable
次のように書けば、123の:id
を変更すれば良いのでは?と推測できる。
OK: http://api.example.com/123 -
サーバ側のアーキテクチャが反映されていない
例えばphp
で書かれていることは利用者にとっては必要のない情報なので、書く必要はない。
NG: http://api.example.com/get_user.php?user=10 -
ルールが統一されている
次のようにfriend
とfriends
が混在しているため良くない。今回の場合は、friends
で統一すると良い。
NG: http://api.example.com/friends?id=100
NG: http://api.example.com/friend/100/message -
スペースやエンコードを必要とする文字が使われていない
NG: http://api.example.com/v1/%E%83%A6%83%82%CV/100 -
複数形の名詞が使われている
food/friend/updateなどではなく、集合を表しているものなのでfoods/friends/updatesなどにすること。また、単複同型などにも気をつけましょう。 -
適切な英単語が使われている
これは日本人ならではですが、英単語の選定が間違っていることが見受けられます。
例えば、情報を検索するAPIfind
とsearch
のどちらを使うべきかと言われれば当然search
な訳ですが、find
を使ったりするなどです。
HTTPメソッドとエンドポイント
HTTPメソッドの説明はは以下の表のとおりです。
勘違いしやすいのが、POSTとPUTですね。厳密に言えば、POSTメソッドは新しい情報を登録するために利用するのが本来の目的で、更新とは意味合いが違います。
しかし、HTTP4.0ではFormにおいて、GET/POSTしか使えないため、POSTで更新するのが一般的になってしまったようです。
一方で、PUTは更新したいリソースのURI自体を指して、その内容をまるっと上書きします。したがって、一部だけ変えたい更新したい場合は、PATCHを使用します。
メソッド名 | 説明 |
---|---|
GET | リソースの取得 |
POST | リソースの新規登録 |
PUT | 既存リソースの更新 |
DELETE | リソースの削除 |
PATCH | リソースの一部変更 |
HEAD | リソースのメタ情報の取得 |
これらを踏まえて、例えば、SNSのユーザー情報の基本設計を作成するならば、以下のようになります。
- ユーザー登録
- 自分の情報の取得
- 自分の情報の更新
- ユーザー情報の取得
- ユーザーの検索
目的 | エンドポイント | メソッド |
---|---|---|
ユーザー一覧取得 | http://api.example.com/v1/users | GET |
ユーザーの新規登録 | http://api.example.com/v1/users | POST |
特定のユーザーの情報取得 | http://api.example.com/v1/users/:id | GET |
ユーザーの情報の更新 | http://api.example.com/v1/users/:id | PUT/PATCH |
ユーザーの情報の削除 | http://api.example.com/v1/users/:id | DELETE |
もしGET/POSTに対応していない場合は?
とは言ってもGET/POST以外にもPUTやDELETEなどを使用したいケースはあります。
しかし、ライブラリやサーバなどによっては使えないケースも考えられます。その場合にどうしたら良いのかというと、以下の2つの選択肢があります。
- X-HTTP-Method-Override
- _method
取得数と取得位置のクエリパラメータ
GET: http://api.example.com/users
といエンドポイントでユーザーの情報を取得することが可能になります。しかし、これでは全てのユーザーを取得することとなり、明らかに適切ではありません。
そこで、ユーザーデータのどのデータを取得するのか。例えば、「idが10〜100のユーザー」や「ユーザー名がyuji」などのように指定してあげる必要があります。そのために使用するのが クエリパラメータ です。
クエリパラメータの例
以下はクエリパラメータの例です。
例えば、Flicker
で1ページ50アイテム存在している場合で101アイテム目から取得するのであれば、
per_page=50&page=3
となります。
サービス | 取得数 | 取得位置(相対) | 取得位置(絶対) |
---|---|---|---|
count | cursor | max_id | |
Flicker | per_page | page | max_upload_date |
YouTube | maxResults | pageToken | max_upload_state |
- | - | max_id | |
GitHub | per_page | page | - |
絞り込みのためのパラメータ
以下のようにさまざまな項目で検索することができます。
OK: http://api.example.com/v1/people-search?first-name=Taro
OK: http://api.example.com/v1/people-search?last-name=Fujiwara
OK: http://api.example.com/v1/people-search?schoo-name=Todaiji%High%School
また、検索するフィールドがほぼ1つに決まる場合は、q
というパラメータが使用される場合もある。
q
はquery
の略で、以下のように書きます。
[1] http://api.example.com/v1/users?name=Taro
[2] http://api.example.com/v1/users?q=Taro
[1]の場合は、絞り込み対象が名前に限定される一方で、[2]の場合は全文検索のためユーザー情報の中で文書情報が含まれるフィールド全てでTaro
が検索される。
ログインとOAuth 2.0
OAuthについては以下の記事が分かりやすいので参考にどうぞ。
https://zenn.dev/mryhryki/articles/2020-12-28-oauth2-flow
OAuthは、サードパーティアプリケーションでTwitterやFaceBookなどのユーザー情報を利用す流ような認可する仕組みです。
OAuthにおいて登場人物は3つです。
- リソースの所有者(例:Twitterユーザー)
- リソースを保持するサービス(例:Twitter)
- リソースにアクセスしたいサードパーティアプリ(任意のアプリ)
以下がざっくりとした認可の流れです。
- ユーザーがアプリでTwitterアカウントを使用して情報を取得しようとする
- アプリがTwitterに許可を取ってきてねーとユーザーに伝える
- ユーザーは、Twitterにアクセスして、Twitterからトークンを受け取る
- ユーザーはその受け取ったトークンをアプリに渡す
- アプリはTwitterにリソースをくださいと伝え、同時にトークンをTwitterに渡す
- Twitterはそのトークンが正しいか確認して、問題なければリソースを渡す
Grant Type
OAuth 2.0にはリソースにアクセスするために許可を得るためのやり取りの手順が4つ定められていて、それらをGrant Typeと呼ぶ。
Grant Type | 用途 | 説明 |
---|---|---|
Authorization Code | サーバサイドで多くの処理を行うウェブアプリケーション向け | FaceBookやTwitterが提供しているような、第三者があなたのサービスにユーザーが保存している情報へのアクセスの許可を得るために利用する。 |
Implicit | スマートフォンアプリやJavaScriptを用いたクライアントサイドで処理の多くを行うアプリケーション向け | 上に同じ |
Resource Owner Password Credentials | サーバサイドを利用しないアプリケーション向け | 以降説明 |
Client Credentials | ユーザー単位での認可を行わないアプリケーション向け | 第三者が特定のユーザーの許可を必要としない情報にアクセスしたい場合に利用する。 |
OAuthの Resource Owner Password Credentials認証
キー | 内容 |
---|---|
grant_type | passwordという文字列。 Resource Owner Password Credentialsであることを表す |
username | ログインするユーザー名 |
password | ログインするパスワード |
scope | アクセスのスコープを指定する(省略可能) |
最後のscopeはどんな権限にアクセスをさせるか、例えばe-mail情報や友達一覧情報などを指定するものです。
まとめるとクライアントのリクエストは以下のようになります。
POST /v1/oauth2/token HTTP/1.1
HOST: api.example.com
Authorization: Basic Y2xoZW60XAS342ADggsgsAG=
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=takeru&password=abcde&scope=api
ここでのAuthorization
ヘッダはクライアント認証(Client Authentication)と呼ばれ、アクセスしようとしているサービスやクライアントアプリケーションが何であるかを特定するための情報を意味する。
アプリケーションを登録すると、ClientIDとClientSecretを発行されるが、その値をユーザー名とパスワードとみなして、Basic認証の形式でBase64変換したものがAuthorizationには入っている。要は、これが認証情報となる。
Basic
という部分は認証の方式で、他にもDigest
やBearer
がある。この後ろにtoken
を書いている。
また、Authorizationではなく、client_id
とclient_secret
という名前でリクエストボディに入れることでも可能。
Headersの具体的値
Referrer-Policy:strict-origin-when-cross-origin
client:1HAqOVI2Bs50afsdK3g
access-token:ts5D6ASDGDHrga4tg3r
uid:メールアドレス
expiry: 145413312409
token-type: Bearer
authorization: BearertdG9rZW4iOiJ0bDZENlhjb2xzcmpzd0ZJME1fS0lnIiwidG9rZW4tdHlwZSI6IkJlYXJlciIsImNsaWVudCI6IjFIQXFPVkkyQnM1MGpLd
Content-Type: application/json, charset=utf-8
Cache-Control: max-age=0, pricate, must-revalidate
access-token: ユーザーの認証に使用されるアクセストークンを表します。アクセストークンは、ログインしたユーザーが認証済みであることを証明するために使用されます。通常、ユーザーがログインした後にサーバーから受け取るトークンです。APIリクエストを送信する際に、このヘッダーに有効なアクセストークンを含める必要があります。
client: アプリケーションのクライアントIDを表します。クライアントIDは、アプリケーションが認証サーバーとやり取りする際に識別されるための一意の識別子です。クライアントIDは通常、アプリケーションの登録時に発行されます。
uid: ユーザーの識別子を表します。通常、ユーザーのメールアドレスやユーザー名など、一意の識別子が使用されます。このヘッダーは、ユーザーを識別するために使用されます。
これらのヘッダーは、APIの認証および認可のために使用されます。クライアントは、リクエストごとにこれらのヘッダーを送信し、サーバーはそれらの値を使用してリクエストの妥当性を検証し、適切なアクセス許可を付与します。
個人的メモ
Authorizationとgrant_typeに違いがいまいちわからなかったので、調べてみると
Authorizationは単純に認証に利用されるパラメータで、grant_typeパラメータは、クライアントがどの種類の認可フローを使用してアクセストークンを要求しているかを示すためのパラメータらしい...
grant_typeパラメータの値には、以下のいくつかのオプションがあって、
authorization_code:
認可コードグラントタイプを使用してアクセストークンを要求する場合に使用
password:
パスワードグラントタイプを使用してアクセストークンを要求する場合に使用
client_credentials:
クライアントクレデンシャルグラントタイプを使用してアクセストークンを要求する場合に使用
refresh_token:
リフレッシュトークンを使用して新しいアクセストークンを要求する場合に使用
これは今後調査して理解できたらわかりやすく追記できたらなーと思っています。
そして、上のリクエストで返ってくるJSONは以下のような形
{
"access_token": "ba7g7asd7gsdgasdgs9g8gsdgsd",
"token_type": "bearer",
"expires_in": 272342,
"refresh_token": "asga34FsgaAa32GgVD323rADGAs"
}
まず、クライアント固有のaccess_token
が返されているところが重要です。これは今後のAPIリクエストに使用するもので、アクセスの際にはこのトークンを送って認証を行う。したがって、ClientIDやClientSecretを送る必要がない。
次に、token_typeの"bearer"はRFC6750で定義されているOAuth 2.0用のトークンの形式です。これはリクエストヘッダー、リクエストボディ、URIのクエリパラメータとして入れる方法がある。
-
リクエストヘッダの場合
GET /v1/users HTTP/1.1
Host: api.example.com
Authorization: Bearer by8a8sgb94sdj43kjk343ds8g739s3dgg -
リクエストボディの場合
GET /v1/users HTTP/1.1
Host: api.example.com
Content-Type: application/x-www-form-urlencoded
access_token=by8a8sgb94sdj43kjk343ds8g739s3dgg -
クエリパラメータの場合
GET /v1/users?access_token=by8a8sgb94sdj43kjk343ds8g739s3dgg HTTP/1.1
Host: server.example.com
RFCとは?
RFCとは、インターネット技術の標準化などを行うIETF(Internet Engineering Task Force)が発行している、技術仕様などについての文書群。TCP/IP関連のプロトコル(通信規約)の標準仕様などが記されたもので、インターネット上で公開されており誰でも入手・閲覧することができる。(https://e-words.jp/w/RFC.html より引用)
認証と認可の違いについては以下の記事が参考になりました。
「1スクリーン1APIコール、1セーブ1APIコール」
これまでの書き方だと、1ページに様々な情報が載っている場合、多くのAPIコールがなされることとなる。そうすると非効率でユーザービリティーも悪くなる可能性がある。
そこで、出てきた考え方がMichele Titolo & Paul Wrightの「「1スクリーン1APIコール、1セーブ1APIコール」」という考え方らしい。
例えば、ホーム画面に「新着情報」「おすすめ商品」「ユーザー情報」などがある場合、それぞれのAPIを作成するのではなく、HOME APIを作成するという考え方。
参考
RFC 7231で定義されているレスポンスステータスコード
バリデーションエラー:400 Bad Request
業務エラー:400 Bad Request
認証エラー:401 Unauthorized
認可エラー:403 Forbidden
システムエラー:500 Internal Server Error
GET
正常系:200 OK
検索系APIで結果0件:200 OK
キー検索系APIで対象リソースが存在しないエラー:404 Not Found
POST
正常系(同期):201 Created
正常系(非同期):202 Accepted
一意制約違反エラー:409 Conflict
親リソースが存在しないエラー:404 Not Found
PUT
正常系(同期):200 OK
正常系(非同期):202 Accepted
対象リソースが存在しないエラー:404 Not Found
DELETE
正常系:204 No Content
対象リソースが存在しないエラー:404 Not Found