Twitterでいつも参考にさせていただいている、よわよわエンジニアさんが「クライアントライブラリを脳死でAxiosを使う駆け出し勢があまりにも多過ぎる」件について発信をしていました。
見事に自分もその中の一人でしたので、これを機にFetchAPIに触れてみました。
参考↓
https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch
https://blog.capilano-fw.com/?p=6646
開発環境
Laravel8(APIモード) × Vue2(SPA)
現在axiosで記述しているコードをfetch APIで書き直す形式で進めていきます。
何はともあれコードを書いていきます。
GETメソッド
Controller(Laravel)
public function index()
{
$users = USER::all();
return response(['users' => $users, 'message' => 'Retrieved successfully'], 200);
}
axios(元のコード)
axios
.get("http://localhost:8000/api/user")
.then((response) => {
this.users = response.data.users;
console.log(this.users);
})
.catch((response) => console.log(response));
fetch APIで書き直し
fetch('http://localhost:8000/api/user')
.then(response => {
console.log(response);
return response.json();
})
.then(data => {
this.users = data.users;
console.log(data.users);
})
.catch(error => {
console.log(error);
});
console.logの結果を確認
.then
を2回使用しているのは、1回目の.then
のresponse.json
がPromiseだから。
promiseチェーンの仕組みによって、結果がバンドラのチェーンに沿って渡され、次のPromiseに引き継がれます。Promiseの理解も怪しい…汗
参考;https://ja.javascript.info/promise-chaining
★ここで抑えておきたいポイント
- fetch()はPromiseのオブジェクトを返す(response.json()の戻り値もPromise)
- responseはJSONデータが格納されているresponseオブジェクト
- JSONデータの加工にはjson()メソッドを使う必要がある
POSTメソッド
前提として、私が作業している開発環境では認証が必要です。認証について触れることで勉強になりました。
Controller(Laravel)
public function store(Request $request)
{
$data = $request->all();
$validator = Validator::make($data, [
'name' => 'required|max:255',
'year' => 'required|max:255',
'company_headquarters' => 'required|max:255',
'what_company_does' => 'required'
]);
if ($validator->fails()) {
return response(['error' => $validator->errors(), 'Validation Error']);
}
$ceo = CEO::create($data);
return response(['ceo' => new CEOResource($ceo), 'message' => 'Created successfully'], 200);
}
axios(元のコード)
const data = {
name: this.name,
company_name: this.company_name,
year: this.year,
company_headquarters: this.company_headquarters,
what_company_does: this.what_company_does
};
axios.post('api/ceo', data)
.catch(function (error) {
console.log(error);
})
fetch APIで書き直し
const data = {
name: this.name,
company_name: this.company_name,
year: this.year,
company_headquarters: this.company_headquarters,
what_company_does: this.what_company_does
};
const token = sessionStorage.getItem('access_token');
fetch('http://localhost:8000/api/ceo', {
method: 'POST',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
"Accept": "application/json",
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.log(error);
});
重要なのが、headers
に記述しているAurthorization
です。
axiosではデフォルトのヘッダー情報をapp.js
に設定をしています。
// sessionStrageに保存しているAPI Tokenを取得
const api_token = window.sessionStorage.getItem('access_token');
// デフォルトでヘッダーに認証情報を付与
axios.defaults.headers.common['Authorization'] = "Bearer ".concat(api_token);
// 非同期でサーバーとやりとりするために使う
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
なので、axiosでは毎回Authorization
の記述をする必要がありません。
axios同様、fetch APIでも同様の設定をしたいのですが未解決で時間がかかっているため、後日記事を編集する形で投稿したいと思います。
Headerの中身を深掘り
X-CSRF-TOKEN
そもそもCSRFとは?
webアプリの脆弱性を突いて攻撃する方法のこと。攻撃者のメリットは、攻撃者が自ら処理を実行することなく、ユーザーを介して処理を実行させることができる点。
例)攻撃者が攻撃用のWebページを作成、ユーザがアクセス→攻撃用のリクエストがアクセスしたユーザから攻撃対象に送信→攻撃されたwebアプリケーションが不正リクエストを処理、意図しない処理が実行される
参考:https://qiita.com/mpyw/items/0595f07736cfa5b1f50c
CSRFと密接な関係にあるOriginについて触れます。
Originとは
- スキーム:https
- ホスト:example.com
- ポート:443
これらをまとめてOriginと呼びます。
CSRF対策として重要なポイントはHTTPリクエストを送らせないことです。
リクエストの結果表示はブラウザのデフォルト機能で防御することが可能(同一オリジンポリシーの仕組み)なため、開発者はアプリケーションが悪意のあるユーザからのHTTPリクエスト自体を送信させない対策が必要です。
そこでCSRFトークンが登場します(やっと)。
CSRFトークンとは、正規のページからアクセスが行われていることを証明するための値です。
X-CSRF-TOKENをヘッダー情報に付与することで、悪意のあるユーザと正規のユーザをAPIが判定することができます。
ただ、他にもストレージにトークンを保持させることで判定する方法もあったりするので、そこの違いについても今後理解を深めていきたいです。
Accept
クライアント側がどんなデータを処理できるかを表す。
今回の実装に関しては、Acceptがなくても実行可能なので、省略可能です。
Content-Type
リクエスト時にメディアタイプ(MIMEタイプ)を指定する役割を果たしています。
Content-Typeを指定しない状態でPOSTすると、私の環境ではValidationエラーが返ってきます。APIがJSONのリクエストを期待しているのに、メディアタイプが指定されていないとメディアタイプを判定できないためです。
Content-TypeにもMIMEタイプという種類があって、リクエストの内容によってタイプが変わります(今回はapplication/json
)。
基本的にはContent-Typeのヘッダは必須と考えて記述することが望ましいようです。
参考:https://tech.stmn.co.jp/entry/2021/03/15/183722
参考:https://blog.development-network.net/ung/プログラミング/content-type.html
Authorization
認証を受けるための証明書を保持します。
LaravelPassportで認証を強制しているので、今回はヘッダー情報にAuthorizationは必須となります。