9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【CSR】なぜCSRだとAPIキーがバレちゃうの?どうしたらいいの?

Posted at

はじめに

先日、CSRSSRSSGについて学習していました。
その中で、CSRだとAPIキーやトークンがブラウザ上で確認できるということを知りました。

私が作ったReactのプロダクトはAPIを使用しているけど、大丈夫?
.envファイルに定義したけど、それは意味ないの?など様々な疑問が発生しました。

CSRの場合、なぜ確認できてしまうのか、どのように対策したらいいのか自分なりに学んだことをまとめてみました。

CSR、SSR、SSGとは?

今回、レンダリングについて3種類学びました。私なりに以下で理解しました。
※詳しい解説はこちらがおすすめです。

CSR:クライエントサイドレンダリング

  • ブラウザ側でHTML、DOMを作る
  • Reactのみで記載した場合はほとんどCSR
  • tsxやjsxファイルからjsを作り、そのjsが実行されることにより、ブラウザでHTMLを作ってくれる

SSR:サーバーサイドレンダリング

サーバー側でHTMLを作る、ブラウザは取得したHTMLをもとにDOMを作成する

SSG:スタティックサイドジェネレーション

サーバー側でHTMLを作るが、ビルド時のみ生成する。(SSRはリクエストする毎に生成する)

実際にAPIキーが確認できるか試してみた

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キーを取得することができます。

image.png

Networkからも確認できます。
image.png

.envファイルに環境変数で設定していれば大丈夫…?

APIを使用するときは、URLやAPIキーを、.envファイルに定義しています。

ここで以下の疑問や考えがでてきました。

  1. App.tsx上に定義すると、tsxファイルを元にjsファイルを作成、
    それを元にブラウザでHTMLを作るから、確認できてしまう
  2. .envファイルは別ファイルに定義して、定義した環境変数をtsx上で使用している
  3. そこから、.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ファイルも見当たりません。
image.png

Networkタブでは確認ができてしまいました。
.envファイルに定義しても確認ができてしまいます。
image.png

.envはコード管理をしやすくするために定義するもの

.envはファイル内にURLやAPIキーなどを定義することで一元管理できます。
また、.gitignoreで.envを定義すれば、コミットされることはなくGitHubなどに公開することを阻止できます。

.envに定義、環境変数としてtsx上で使用してもコンパイル時には実際の値が定義されるため、ブラウザ上では確認できます。
.envファイルなら環境変数で使用するから安全と最初は思っていましたが、実際は違いました。

ビルドしたものでも同じ?

え?私プロダクトをビルドしてデプロイしたもの沢山あるけど、それもキーばれている?と思い、確認しました。

先ほど作成したプロジェクトをnpm run buildでビルド
ビルドをするとtsxファイルなどプロジェクト全体が一つのjsファイルにまとめられ、dist配下に配置、一つにまとめられたjsがブラウザで実行され、HTMLが作られます。

そのため、jsとcss、ひな形のHTMLのみがブラウザで確認できます。

image.png

npx serve distコマンドを実行し、ビルドしたものをブラウザ上で確認してみました。

Sourcesにて、ビルドで作られたindex-B0le57MV.jsファイルからAPIキーを簡単に見つけられました。
image.png

Networkでもindex-B0le57MV.jsファイルから確認できました。

image.png

ビルドをすれば安全と考えましたが、違いました。

これらのことから、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にすることで対策が可能です。テーブルの更新や削除を阻止することができます。

Pasted image 20250703085248.png

今まで、RLSをdisabledにすることを何となく行い、CSRでテーブルへアクセスできるようにしていました。
今回、なぜdisabledにしないといけないのかがわかりました。

おわりに

今回、この事象について調べてみようと思ったのは、Remix+React-router-v7のハンズオンに取り組んだことがきっかけでした。

Remix+React-router-v7の場合、clientLoader→CSR、loader→SSR、SSGにしたいファイル名を定義ファイルに記載する→SSG
で簡単にレンダリング方法を変えることができます。

最初は少し難しいなと感じたものの、メリットがわかるととても便利!と思いました。
loaderに変えるだけで、APIキーをtsxファイルに直接書いてもブラウザで確認できなくなります。(個人的にはすごい!となりました。)
ぜひ、試してみてもらいたいです。

参考

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?