0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ対策】APIキーをクライアントサイドで管理することのリスクと解決法

Posted at

はじめに

今回、Vanila JSでOpenWeather APIを利用したアプリ開発中に発生した問題とその解決方法を紹介します。

この記事は、私自身の学習過程の中で得た気づきをまとめたものです。
まだ理解が浅い部分や誤りが含まれている可能性があります。
もし間違いや勘違いがありましたら、ご指摘していただけると嬉しいです。

アプリ概要

Screenshot 2025-10-12 at 0.00.11.png
都市名を入力し、検索するとその都市名、温度、天気が表示されるという非常にシンプルな構成です。

問題点

APIキーが取得できてしまう

APIキーを取得されると
・APIキー不正利用による無料枠を超えての課金
・攻撃者による大量クエストにより、サービス停止
・悪意ある利用による違法活動
などのセキュリティリスクにつながる。

APIキーを.envで管理しても以下の方法で公開されてしまう
・開発者ツールのネットワークで表示されてしまう。
Screenshot 2025-10-10 at 17.00.16.png
・ビルド : ビルド後に作成されるdist/assetsの中にAPIキーが表示されてしまう。

解決策

APIキーをサーバーサイドで管理する

クライアントサイドと違い、攻撃者はサーバーを見ることができないためサーバーサイドで管理することにより、安全に管理することができる。

今回は、Next.jsで開発する。

具体的な実装

Next.jsをインストールする。

npx create-next-app@latest

.envファイルを作成する。

touch .env

.envファイルに自分で作成したAPIキーを記述する。

.env
OPENWEATHER_API_KEY=YOUR_API_KEY

.gitignore.envの記述があるか確認する。

クライアントサイドとサーバーサイドに分離する。
今回は、シンプルなアプリなのでapp/page.tsxにクライアントサイドの記述をする。
サーバーサイドは、app/api/route.tsに記述する。

app/api/route.ts
import { NextRequest, NextResponse } from "next/server";

const API_KEY = process.env.OPENWEATHER_API_KEY;

export async function GET(request: NextRequest) {
  const searchParams = request.nextUrl.searchParams;
  const cityName = searchParams.get("cityName");

  if (!cityName?.trim()) {
    return NextResponse.json(
      { error: "都市名が入力されていません。" },
      { status: 400 }
    );
  }
  if (!API_KEY) {
    return NextResponse.json(
      { error: "APIキーが設定されていません。" },
      { status: 500 }
    );
  }
  const res = await fetch(
    `https://api.openweathermap.org/data/2.5/weather?q=${cityName}&appid=${API_KEY}`
  );

  if (!res.ok) {
    if (res.status == 404) {
      return NextResponse.json(
        { error: "都市が見つかりませんでした。" },
        { status: 404 }
      );
    } else if (res.status == 401) {
      return NextResponse.json(
        { error: "APIキーが正しくありません。" },
        { status: 401 }
      );
    } else {
      return NextResponse.json(
        { error: "エラーが発生しました。" },
        { status: res.status }
      );
    }
  }

  const data = await res.json();
  return NextResponse.json(data);
}

API_KEYという変数を定義して、.envからAPIキーを読み込む。

app/page.tsx
"use client";

import { useState } from "react";

interface WeatherData {
  name: string;
  main: {
    temp: number;
  };
  weather: Array<{
    main: string;
  }>;
}

export default function Home() {
  const [cityName, setCityName] = useState("");
  const [weatherData, setWeatherData] = useState<WeatherData | null>(null);

  const getCurrentWeather = async (cityName: string) => {
    try {
      const res = await fetch(`/api?cityName=${cityName}`);
      if (!res.ok) {
        const errorData = await res.json();
        throw new Error(errorData);
      }

      const data = await res.json();
      setWeatherData(data);
      return data;
    } catch (err) {
      console.error(err);
    }
  };

  const handleSearch = () => {
    if (cityName.trim()) {
      getCurrentWeather(cityName);
      setCityName("");
    }
  };
  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="text-center space-y-4">
        <h1 className="font-bold text-4xl mb-6">Weather Forecast</h1>
        <input
          type="text"
          className="outline-none border rounded-md mr-1"
          value={cityName}
          onChange={(e) => setCityName(e.target.value)}
          placeholder="都市名を入力"
        />
        <button
          type="submit"
          className="bg-gray-700 rounded-md p-1 cursor-pointer hover:bg-gray-500"
          onClick={handleSearch}
        >
          検索
        </button>
        {weatherData && (
          <div className="mt-6 space-y-2">
            <p className=" text-xl">{weatherData.name}</p>
            <p className="text-3xl font-bold">
              {(weatherData.main.temp - 273.15).toFixed(1)}</p>
            <p className="text-xl">{weatherData.weather[0].main}</p>
          </div>
        )}
      </div>
    </div>
  );
}

これらの実装により、APIキーが流出していないことを確認できました。
Screenshot 2025-10-12 at 0.42.03.png

まとめ

このように.envはサーバーサイド側で管理することにより、APIキーを安全に管理することができました。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?