inuinu blog(開発用)

BOT @wagagun の開発ノウハウや、IT向け?の雑記ブログです。

素のJavascript(Vanilla)だけでフロントエンド開発(12:API編)

このページでは、ReactやVueではなく、Vanillaと呼ばれる素のJavascriptのみで、フロントエンド開発が可能であるか?を解説します。今回はフロントエンドではなくAPI側の注意点です。

今回は、前回…

inuinu-tech.hatenablog.jp

の続きになります。

今回は、フロントエンド技術者向けではなく、バックエンド技術者向けの内容です。

これ以降は、ある程度、APIが開発できる方向けに書いています。

いきなりここにたどり着いたのでしたら、お手すきの際で構いませんので、下記の記事からも読んでみていただければ幸いです。

inuinu-tech.hatenablog.jp

今回の課題のゴールは?

素のJavascriptシリーズのAPIについて、おさらいしつつ、私の経験を交え、API開発の注意点等を考えていきます。

APIについて

素のJavascriptシリーズに登場するAPI

今回のシリーズで想定されるAPIは以下のとおり。

  • 各画面共通のもの
    • SELECTのOPTION値セット
    • メニューなどの共通部分(含む第2セッション値)の取得
    • 明細行の複写
  • 各画面ごとに異なるもの(入力系)
    • 画面の初期表示(変更画面用)、明細含む
    • エラーチェック&更新
  • 各画面ごとに異なるもの(照会系)
    • 一覧画面の取得
    • 明細(詳細)画面の取得
  • その他(今回のシリーズには直接登場しないが、必ず作るであろうもの)
    • ログイン、ユーザー登録
    • コード検索、名称検索などの入力アシスタント(モーダル等?)
    • 何らかの計算等のロジック(合計欄など)

今回のシリーズのAPIは「〜.json」となっていますが、これは単なるモックです。実態は異なるドメインで、バックエンドのフレームワークに準じたURLにて取得すると思いますので、共通化含め、考えてみてください。

フロントエンドから直接実行を避けるべきAPI

フロントエンドから実行するAPIについては、同時進行で開発していく自前のバックエンドAPI以外は実行しないようにします。
つまり、以下のようなAPIは、フロントエンドからの直接的な実行を避けるようにします。

  • API内で実行されるであろう、プロシージャや、バッチ処理の類
  • DB更新のみの処理
  • 社内の他の基幹システム連携といったAPI
  • 他のクラウドサービスのAPI

ありがちなのは、異なるシステムのDBにアクセスするようなもの。
例えばECパッケージから、自社の流通基幹システムに連携する場合、基幹システム側でAPIを用意し、そちらをバックエンドAPI側から実行するという手順がとられるケースがあると思います。
これが、このAPIがフロントエンド側に漏れ伝わり、直接実行されないようにしましょう。

他のクラウドサービスのAPIは、例えばSlackで通知を行うといったものだと思いますが、これらはトークンを渡して実行しますので、フロントエンドからそれをしてしまうことは、セキュリティ上、非常にリスキーです。
こちらも、バックエンド側のAPIから、さらにAPIを呼ぶようにしましょう。

フロントエンドとバックエンドを同じ人が開発すると、方向性を見失うことがあるかと思います。
上記のようなルールを考えながら開発していきましょう。

APIの共通仕様(セキュリティ以外)

参考サイト

まずはこちらに目を通しましょう。
何が何でもRESTfulにしないとカッコ悪いみたいな発想は捨てましょう。

inuinu-tech.hatenablog.jp

inuinu-tech.hatenablog.jp

大前提は、フロントエンドとバックエンド双方の合意

どちらかのシステム的に出来ないもの、無理難題なものを押し付ける必要はありません。

例えば、RESTfulじゃないからダサいみたいなものはあくまでも表面的なものであって、ある特定の言語が、オフィスコンピュータ等の基幹システムのような特殊な環境との相性が良いので、バックエンドではそちらを使用する…といったケースもあるのです。

無理にRESTfulにしなくて良い

無理にRESTfulにしなくて良いと思います。

  • 動詞はGET/POST/PATCH(PUT)/DELETE…はバックエンド側に制約があればこだわらない
    • POST/GETのみの方がテストやWebサーバーの設定が楽
    • URLに動詞を含んでも別にいい
  • pathパラメータが無理ならやらない
  • .phpとか.cfmとか、拡張子が見えてても特に問題ない
    • ハッキングはまた別のお話

拡張子嫌いの人はソースが覗けるかもしれない…だと思うのだけど、そういう人のシステムほど、500エラーになった際にデバッグモードが全開になっていたり、出来合いのAPIを使ってしまったり…

nullやtrue/falseが苦手なシステムもある

JSONはnullやtrue/falseが使えますが、API側が使用できない(異なるものに置き換えてしまう)ケースもあります。

その場合、true/falseは避けて、"1"や"0"あるいは1と0とします。
さらに、nullではなく、""や0やとします。

例えば、"data" : nullに特別な意味、例えばレコードのNot Foundなどの意味をもたせたいのならば、"record_count" : 0といったJSONキーと値を別途持ち、nullではなく、空の配列、"data" : []とした方が、不具合も少なく、後々わかりやすいです。

レスポンスコードは凝らない(むしろ避ける)

経験値的にこれで落ち着くことが多いと思います。

  • 正常終了はすべて200
    • 201/202などバリエーションはフロントエンドエンジニアが混乱するだけ
      (ソースを見ると20xでチェックしているなんてことも…)
    • エラーか正常かはerrorキーを見る(重要度をもたせる)、メッセージを返す…等の仕様に統一する
  • 不具合などの異常終了は500
  • セキュリティーの問題は403
  • 404はURLミスのみ(レコードのNot Foundには使用しない)

名前は伏せますが、有名なAPIサービスでも、もともとのレコードなしは404だが、一旦削除してなくなったレコードは404じゃない…と不具合(海外のサービスは大抵の場合はそれを「仕様」という)ことありました。

さらに、更新時のレコード不在は404だが、削除時のレコード不在は404じゃない…みたいなのもありました。

可能であれば、200以外のレスポンスでもJSONで渡せると良い

無理であれば仕方ないのですが、200以外のレスポンスコードでもJSONを返すようなフレームワークだとやりやすいかもしれません。
(例えばSinatraはそれが容易。)

POSTはJSONではなく、フォームデータでもらう

添付ファイル対応の画面はフォームデータなのに、他はBODYにJSONといった、統一性のないAPIを割と見かけます。

突然、添付ファイル欄が欲しいなんていうアジャイルなリクエストもあるので、フォームデータに統一のほうが吉でしょうね。

可変行はフォーム(INPUTやTEXTAREA)の中にJSONでも良い

POST時にJSONが使えなくなるのも嫌という場合もある。
それなら、INPUTやTEXTAREAの中にJSONが入っていてもよい。

フロントエンドとバックエンド、どちらかの処理がめんどくさければ、そうしても良い。
すべては、フロントエンドとバックエンドの双方の認識合わせかなと。

APIの共通仕様(主にセキュリティ関連)

HTTPSであること

こちらは本番導入までにちゃんとしてくださいということでしょうね。

ログイン時にCookieにセッション値をセットする

こちらの記事では、

inuinu-tech.hatenablog.jp

Cookieのセットをフロントエンド側で行っていますが、大抵の場合API側からもCookieを操作できますので、ログイン時の設定はAPI側でやっちゃいましょう。
その方が処理が隠蔽できます。

さらに、サーバーからでないと変更できないオプション(HttpOnly)にすると、Javascriptからはアクセスができなくなります。

セッション値はユーザーテーブルで管理して構いませんが、くれぐれもパスワードのハッシュ値Cookieにセットしないように。

セッション値は、その時々で異なり、さらにユニークなハッシュ値にしましょう。
ログオフ時に、双方のセッション値をクリアします。

Cookieの値を確認し、有効値でなければ、強制終了する

API実行の都度の共通処理を初期処理として実行します。

その際に、Cookieの値を確認し、有効なセッション値ではない場合は、処理を中断し、画面遷移先を返すようにします。

多くの場合は、ログオン画面になると思います。

その際、何らかのログを残しておきましょう。

CSRF対策を行う

次章の「CSRF対策について」をご参照ください。

フレームワークでサポートされていて、自動的にチェックがかかるような場合は、それに従ってください。

所定のドメインからの実行でない場合は、強制終了する(イントラネットの場合)

前出のAPI初期処理において、特定のドメインからでしかアクセスしないようなシステムの場合は、URLのドメインをチェックすると、より堅牢になるかと。

リモートワーク時に使用できなくなるかもしれないので、状況を考えながらでしょうか…

その際の画面遷移先はログオンではなく、403となるでしょうか。

その際は、エラーの経緯等、細かい説明を書くのも、相手に情報を与えるだけですので、シンプルな403画面に遷移すると良いと思います。

セッションが有効でも、管轄外のデータを改ざんしようとした場合は強制終了する

ここまででノーエラーの場合は、APIの処理を続行して良いとは思いますが、その前に、そのデータが自分の管轄内なのか?のチェックをかける必要があります。

例えば、ECサイトの注文履歴で、入力された注文番号がそのユーザーのものなのか?といったチェックは最低限必要です。

この場合は、「この注文は他人のものです」ではなく「この注文は(履歴に)ありません」といったそっけない返しで良いと思います。

ワークフローの場合は、自分が申請していないデータを改ざんできなくしたり、自分が承認できない申請データを覗けないようにします。

失念しやすいのは、ユーザーテーブルが有効であっても、その従業員は実は退職していたりといったパターンですね。
(退職というステータスがユーザーテーブルにない。)
ただ、退職しても源泉徴収票だけは取得したいというニーズもあるかと思いますので、そこは、システム開発者の腕の見せ所でしょうか。

結果の画面遷移先はAPIで渡す

前項でもありましたが、ログインの失効や、セキュリティインシデントは特別なURL(403等?)に飛ばす等、意外とバリエーションがあると思いますので、更新完了後の次画面等も含め、画面遷移先は常にAPIで渡す方がベターかもしれません

500エラー(不具合発生)の際も手の内がわからないように

API内での500エラー(=API側での不具合発生)については、普通にJSONで重大なエラーが発生した旨を返す。

Sinatraでは、ルーター定義で500でもJSONを返せたはず。
そうでないフレームワークでも、最悪、try〜catchでJSONを返せるはず。
(try〜catchではフレームワークが自動的にログを吐き出さなくなるので、手動でなんとかしましょう。)

フロントエンド側でどういう振る舞いをするのかは、遷移先(500エラー)を指定し、特に詳細のないシンプルな画面に遷移するといいでしょう。

あとはログを見て調査できるようにしましょう。
もし、意図的に不具合を起こすような行為であれば、対策を施しておきましょう。

CSRF対策について

セキュリティ関連でもCSRF対策はちょっと特殊な仕様なので、実装したい場合のヒントを提示しておきます。

こちらは主に、エラーチェック&更新や、ログインといった入力系で使用します。

共通項目の取得時(メニューセットなど)でhiddenに第2セッション値を忍ばせる

問題はCSRF対応用のサービスがAPI側に存在するか?ですが、ユーザーテーブル内にセッション値と異なるハッシュ値を持ち、それで代用しても良いと思います(パスワードのハッシュ値はダメ)。

最低限、セッション内で同一値でもいいですが、都度、ユーザーテーブルを書き換えたほうが良いかもしれませんね。

エラーチェックAPIに、第2セッション値を受け取る

POST&フォームデータなら、(hidden値がFORMタグ内にあれば)自然と受け取れるはずです。
ユーザーテーブルの第2セッション値と同一ではない場合は、API側はログに残し、403画面のURLを渡すと良いでしょう。<

API側の注意点

API開発時の注意点等を記しておきます。

フレームワークで自動作成されるAPIは使用しない(可能であれば除去する)

サーバーサイドのフレームワークによっては、DB作成時にAPIまで作成してしまうものがあるかと思いますが、開発時間の短縮になる反面、不要なカラムまで取得できてしまうため、セキュリティ上よろしくないと思います。

ものによっては登録更新削除まで出来てしまうため、とても危険です。
出来てしまうような場合は、そういったAPIを除去する等、対策を考えてください。
可能な限り、APIは自前で用意しましょう。

すべてのレコードが取得できるようなAPIは作成しない

前項はカラムでしたが、こちらは行についてです。

一覧画面用のAPIで、すべてのレコードを検索できるといった仕様は避けましょう。
具体的には…

  • 「検索条件を何も指定しない場合はすべてを表示する」といった仕様は避ける
    (初期表示=条件なしは0件にするという仕様でも良い)
  • そのログイン対象者の範囲外(管轄外)のデータは取得できないようにする
    (ECで他者の購入履歴が取得できるといった不具合がないように…)
  • 締め処理済みやキャンセル済みなど、活きていないデータは(デフォルトでは)検索できなくする 検索数に限度を設ける
    (限度を超えた場合は0件で戻すといった仕様でも良い)
  • 「*」や「all」といった隠しコマンド的なものも避ける

今回のAPI例では「SELECTのOPTION値セット」がありますが、対象コードを空欄にしたら、すべてのコード値が取得できるといった仕様は、危険ですので避けましょう。
(複数のコード値を取得したい場合は、カンマ区切りなどで表現しましょう。)
OPTION値の取得とは関係のないコード値がある場合は、可能であれば、コード値テーブルのカラムに、このAPIでは取得できない的なフラグを用意し、APIでは除外しておくと、良いかもしれません。

エラーチェックは極力APIのほうで行う

以下の理由で、エラーチェックのAtoZはAPI側で行います。

  • 処理隠蔽の視点から(Javascriptを覗いてビジネスロジックを解析されるのはまずい)
  • ビジネスロジックを一々フロントエンドエンジニアに伝えるのが、ぶっちゃけツラい
  • 仕様変更によるダブルメンテナンスを避けたい
  • フロントエンドは気まぐれでとっかえひっかえならば、そこに都度都度ロジックを入れ込むのはキツい

INPUTタグのtypeをdateにしたり、timeにする程度でしたら、フロント側でもやって良いと思います。
でも、そこに渡す際のしきたり(yyyy/mm/ddではなくyyyy-mm-ddとか)もあるので、やってもやんなくても良いかもとは思います。

エラーチェックについては、下記の記事の「参考:API(バックエンド側)は何をしている?」も参考にしてみてください。

inuinu-tech.hatenablog.jp

GET/POSTパラメータの異常値に注意

こちらもAPIの初期処理で行います。

区分値や、特定の文字種しか入らないようなパラメータは、異常値が入った場合、デフォルト値に変更します。

例えば、1:昇順、2:降順といったGETパラメータがある場合、それ以外の値が設定された場合は、デフォルト値(1または2、画面によって異なる)に強制的に変更します。
数値以外はセットできないGETパラメータに数字以外の文字がセットされた場合は0にします。
といった決め事に対応した部品(function)を作成し、活用すると良いでしょう。

その他の文字列パラメータはXSS攻撃対策を施します。

XSS攻撃はAPIには直接関係ないかもしれませんが。DBに更新し、そのDB値を二次使用する際に、問題が生じる危険性はあります。
もし、意図的に不具合を起こすような行為であれば、対策を施しておきましょう。

ソート順などは初期処理時に対策を施しますが、入力フォームの値はエラーチェック時に対策を施せばよいでしょう。

こちらの記事はフロントエンド用ですが、参考にしてみてください。

inuinu-tech.hatenablog.jp

SQLインジェクションに注意

SQLインジェクションとは、(フォーム内にHTMLタグを忍ばせるXSS攻撃同様)、SQL内に別のSQL文を意図的に忍ばせ、データを改ざんしたり、それが出来なくてもシステム的に障害を発生することが目的です。

プレースホルダが使用できれば、極力そちらを使用しましょう。

入力時の明細行番号には欠番がある

今回のシリーズの例題プログラムの入力タグのnameには欠番が生じます。

詳細は、こちらを参照してください。

inuinu-tech.hatenablog.jp

もし、どうしても欠番が嫌だったら、フォームデータの中でJSON化してもらえばいいかなと思いますが、ただ、エラー時のINPUTタグを差別化(装飾)したいといたニーズがあると、nameの値はユニーク化することになるかと思いますので、name値を別途もらうなどの工夫が必要になりそうですね。<

最後に

このシリーズ(素のJavascript〜)は、とりあえず今回までとなります。

もし、次回があるとしたら、捕捉やFAQ的なものになるかもしれません。

それでは、また。