先日、CSR
、SSR
、SSG
について学習していました。
その中で、CSRだとAPIキーやトークンがブラウザ上で確認できるということを知りました。
私が作ったReactのプロダクトはAPIを使用しているけど、大丈夫?
.envファイルに定義したけど、それは意味ないの?など様々な疑問が発生しました。
CSRの場合、なぜ確認できてしまうのか、どのように対策したらいいのか自分なりに学んだことをまとめてみました。
今回、レンダリングについて3種類学びました。私なりに以下で理解しました。
※詳しい解説はこちらがおすすめです。
CSR:クライエントサイドレンダリング
- ブラウザ側でHTML、DOMを作る
- Reactのみで記載した場合はほとんどCSR
- tsxやjsxファイルからjsを作り、そのjsが実行されることにより、ブラウザでHTMLを作ってくれる
SSR:サーバーサイドレンダリング
サーバー側でHTMLを作る、ブラウザは取得したHTMLをもとにDOMを作成する
SSG:スタティックサイドジェネレーション
サーバー側でHTMLを作るが、ビルド時のみ生成する。(SSRはリクエストする毎に生成する)
APIキーを本当に見ることができるのか、実際に試してみました。
まずは、tsxファイル上で、URLとAPIキーを直接書いて定義し、確認してみました。
App.tsx
// 省略
const [movies, setMovies] = useState([]);
const options = {
method: 'GET',
headers: {
accept: 'application/json',
Authorization:
'Bearer APIキー', // APIキーを直接書いた
},
};
async function fetchData() {
const res = await fetch(
'https://api.themoviedb.org/3/movie/popular?language=ja-JP&page=1',
options
);
const data = await res.json();
const moviesData = data.results.map(
(movie) => new Movie(movie.id, movie.original_title, movie.overview)
);
setMovies(moviesData);
}
// 以下省略
全量はこちら
App.tsx
import { useEffect, useState } from 'react';
import { Movie } from './domain/movies.ts';
import './App.css';
function App() {
const [movies, setMovies] = useState([]);
const options = {
method: 'GET',
headers: {
accept: 'application/json',
Authorization:
'Bearer APIキー',
},
};
async function fetchData() {
const res = await fetch(
'https://api.themoviedb.org/3/movie/popular?language=ja-JP&page=1',
options
);
const data = await res.json();
const moviesData = data.results.map(
(movie) => new Movie(movie.id, movie.original_title, movie.overview)
);
setMovies(moviesData);
}
useEffect(() => {
fetchData();
}, []);
return (
div>
div className="flex sm:ml-64">
div>一覧表示するdiv>
div>
{movies.map((movie) => {
return (
div key={movie.id}>
h2>{movie.title}h2>
p>{movie.overview}p>
div>
);
})}
div>
div>
div>
>
);
}
export default App;
直接書いた結果
開発者ツールよりSourcesを確認Authorization : Bearer ~
の箇所がAPIキーになります。簡単に確認できました。
誰でも簡単にAPIキーを取得することができます。
APIを使用するときは、URLやAPIキーを、.env
ファイルに定義しています。
ここで以下の疑問や考えがでてきました。
- App.tsx上に定義すると、tsxファイルを元にjsファイルを作成、
それを元にブラウザでHTMLを作るから、確認できてしまう - .envファイルは別ファイルに定義して、定義した環境変数をtsx上で使用している
- そこから、.envファイルは直接定義するのを阻止するためにある?と疑問に思いました。
.env
ファイルに定義して確認してみました。
App.tsx
// 省略
const [movies, setMovies] = useState([]);
+ const apiKey = import.meta.env.VITE_TMDB_API_TOKEN;
const options = {
method: 'GET',
headers: {
accept: 'application/json',
+ Authorization: `Bearer ${apiKey}`,
- Authorization: 'Bearer APIキー',
},
};
// 省略
.env
VITE_TMDB_API_TOKEN=APIキー
App.tsxの全量はこちら
App.tsx
import { useEffect, useState } from 'react';
import { Movie } from './domain/movie.ts';
import './App.css';
function App() {
const [movies, setMovies] = useState([]);
+ const apiKey = import.meta.env.VITE_TMDB_API_TOKEN;
const options = {
method: 'GET',
headers: {
accept: 'application/json',
+ Authorization: `Bearer ${apiKey}`,
- Authorization: 'Bearer APIキー',
},
};
async function fetchData() {
const res = await fetch(
'https://api.themoviedb.org/3/movie/popular?language=ja-JP&page=1',
options
);
const data = await res.json();
const moviesData = data.results.map(
(movie) => new Movie(movie.id, movie.original_title, movie.overview)
);
setMovies(moviesData);
}
useEffect(() => {
fetchData();
}, []);
return (
div>
div className="flex sm:ml-64">
div>一覧表示するdiv>
div>
{movies.map((movie) => {
return (
div key={movie.id}>
h2>{movie.title}h2>
p>{movie.overview}p>
div>
);
})}
div>
div>
div>
>
);
}
export default App;
.envに書いた結果
Sourcesでは確認できなくなっています。.envファイルも見当たりません。
Networkタブでは確認ができてしまいました。.env
ファイルに定義しても確認ができてしまいます。
.envはコード管理をしやすくするために定義するもの
.env
はファイル内にURLやAPIキーなどを定義することで一元管理できます。
また、.gitignore
で.envを定義すれば、コミットされることはなくGitHubなどに公開することを阻止できます。
.envに定義、環境変数としてtsx上で使用してもコンパイル時には実際の値が定義されるため、ブラウザ上では確認できます。
.envファイルなら環境変数で使用するから安全と最初は思っていましたが、実際は違いました。
え?私プロダクトをビルドしてデプロイしたもの沢山あるけど、それもキーばれている?と思い、確認しました。
先ほど作成したプロジェクトをnpm run build
でビルド
ビルドをするとtsxファイルなどプロジェクト全体が一つのjsファイルにまとめられ、dist
配下に配置、一つにまとめられたjsがブラウザで実行され、HTMLが作られます。
そのため、jsとcss、ひな形のHTMLのみがブラウザで確認できます。
npx serve dist
コマンドを実行し、ビルドしたものをブラウザ上で確認してみました。
Sourcesにて、ビルドで作られたindex-B0le57MV.js
ファイルからAPIキーを簡単に見つけられました。
Networkでもindex-B0le57MV.js
ファイルから確認できました。
ビルドをすれば安全と考えましたが、違いました。
これらのことから、CSRの場合はブラウザでばれてしまう恐れがあるということがわかりました。
SSRでレンダリングする
CSRの場合、jsファイルをブラウザ上で実行し、HTMLを作成します。
SSRの場合、tsxや定義しているAPIキーはサーバー上で使用、HTMLを作成します。
ブラウザはサーバーから取得したHTMLを表示するのみのため、APIキーが見られることはありません。
CSRを使用しても問題ない仕組みを利用する
Supabaseの場合
これまでの勉強でSupabaseを使用する機会が多くありました。
Supabaseのライブラリを使用する場合、APIキーを取得、指定する必要がありました。
これまで、.env
を作り管理していましたが、CSRでレンダリングすると、簡単にURLとシークレットキーはブラウザ上で調べられます。
しかし、Supabaseでは、アクセスするテーブルのRow Level Security (RLS)の設定
をENABLE
にすることで対策が可能です。テーブルの更新や削除を阻止することができます。
今まで、RLSをdisabledにすることを何となく行い、CSRでテーブルへアクセスできるようにしていました。
今回、なぜdisabledにしないといけないのかがわかりました。
今回、この事象について調べてみようと思ったのは、Remix+React-router-v7のハンズオンに取り組んだことがきっかけでした。
Remix+React-router-v7の場合、clientLoader→CSR、loader→SSR、SSGにしたいファイル名を定義ファイルに記載する→SSG
で簡単にレンダリング方法を変えることができます。
最初は少し難しいなと感じたものの、メリットがわかるととても便利!と思いました。
loaderに変えるだけで、APIキーをtsxファイルに直接書いてもブラウザで確認できなくなります。(個人的にはすごい!となりました。)
ぜひ、試してみてもらいたいです。
Views: 0