Teruhiro Komaki

日々の生活や、プログラミング、Claris FileMakerに関する情報をメモしておく雑記帳です。

Firebase Authenticationを、Firebase Admin SDKではなくREST APIで使う方法

2023-06-22

Cloudflare Workersで、APIを作っているのですが、当然ですが、なにかしらの制限をかけないと、だれでもリクエストできてしまいます。

そこで、よく利用している、Firebase Authenticationを使い、APIを保護したいと思いました。

しかし、Cloudflare Workersは、まだNode.jsを完全にサポートしていないと思いますので、今回は、Firebase Admin SDKを使うのではなく、REST APIを使うことにしました。

色々なライブラリを見ていると、結局、REST APIをコールしているだけのようですし…

このブログは、基本的に自分用のメモなので、簡易的なHTMLを書いて実行しました。

間違いがあれば、コメント頂ければと思います。

環境

名称 バージョンなど
OS macOS Monterey 12.6.6

ドキュメント

harnessing-rest-api-instead-of-firebase-admin-sdk-01.webp

Firebaseの準備

Firebaseでプロジェクトを作成します。

harnessing-rest-api-instead-of-firebase-admin-sdk-02.webp

Authentication -> ログイン プロバイダ -> メール / パスワード を追加します。
harnessing-rest-api-instead-of-firebase-admin-sdk-03.webp

アプリを追加します。

harnessing-rest-api-instead-of-firebase-admin-sdk-04.webp

APIキーをメモしておきます。

harnessing-rest-api-instead-of-firebase-admin-sdk-05.webp

これで、Firebaseの準備がおわりました。

APIをリクエスト

簡単なHTMLを準備しました。

エディタから開くなり、live-serverを使うなりして、ブラウザで開きます。

メール/パスワードでサインアップ

ドキュメントは こちら です。

harnessing-rest-api-instead-of-firebase-admin-sdk-06.webp

リクエスト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sign up with email / password</title>
  <style>
    .container {
      max-width: 400px;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }

    .container label {
      display: flex;
      flex-direction: column;
    }
  </style>
</head>
<body>
<h1>Sign up with email / password</h1>
<div class="container">
  <label>Email:
    <input id="email" type="text" placeholder="Email">
  </label>
  <label>Password:
    <input id="password" type="password" placeholder="Password">
  </label>
  <button onclick="signUp()">SignUp</button>
  <hr>
  <label>Response:
    <textarea id="response" rows="5"></textarea>
  </label>
</div>
<script>
  async function signUp() {
    try {
      const email = document.querySelector('#email').value;
      const password = document.querySelector('#password').value;

      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
      const url = `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${apiKey}`;
      const body = {
        email: email,
        password: password,
        returnSecureToken: true,
      };
      const option = {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
      };

      const res = await fetch(url, option);
      const response = await res.json();
      document.querySelector('#response').value = response;
      console.log(response);
      setResponse(response);
    } catch (e) {
      console.error(e.message);
    }
  }

  function setResponse(data) {
    document.querySelector('#response').value = JSON.stringify(data);
  }
</script>
</body>
</html>

レスポンス

{
  "kind": "identitytoolkit#SignupNewUserResponse",
  "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhkMDNhZTdmNDczZjJjNmIyNTI3NmMwNjM2MGViOTk4ODdlMjNhYTkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmItcmVzdC1hcGktd2VpZHU1IiwiYXVkIjoiZmItcmVzdC1hcGktd2VpZHU1IiwiYXV0aF90aW1lIjoxNjg3MzYxNTgwLCJ1c2VyX2lkIjoiZkx1VlZlTVd1aGY2bWt3RG9JMzhUcXZvNTdzMiIsInN1YiI6ImZMdVZWZU1XdWhmNm1rd0RvSTM4VHF2bzU3czIiLCJpYXQiOjE2ODczNjE1ODAsImV4cCI6MTY4NzM2NTE4MCwiZW1haWwiOiJwYW5vcGx5LXN5bmMwd0BpY2xvdWQuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInBhbm9wbHktc3luYzB3QGljbG91ZC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.1LPI49PvK2fXyHxt_Y6j5-DqSJAbNLD3xyjgt_vIH-nv_eMobMS_DH2t55bH8UU3oEPNRSufxDkSldK7hceMwrP7UPGq8sjq_EqRcjwb328aGdDmfUMZ10SPPkBK0_uPxw-c84BJs99iCqo54x4l1O6yBPbQM0ILIj-aL5rKHqsPbcLjaRrYH4cT2_tsBn3185rK15XqDY5mUGz_KgnZhTgBYnwh9U5qP66bat9lGaE2eiXFOqANV8B2ShoXxf2WTtEaMAEGVM7eECov2QeNUvOGfckFmc1WymhXSao9NZmgA0HHlBZHItjqIf7nd2rBKscTiGpQ4Xeytm7Fz8otAA",
  "email": "[email protected]",
  "refreshToken": "APZUo0RksSngIb3GwemrAgDhSEmUXzJzXaZJpNnqavWk36C2Lz5fPgbbBb5cA-6nrIVd5YwXCtqNXYhNRhD4sBRePJ0N-suje9E0GCCh-aKhcFH77kPJcu64PLOo3vr-cAFgH1i4NBpexJK7svy8uW_dmwnHYH56mn-WuMk_jsPUP2VshZjIPhAcBr_x4GLWhWXo7ucvTnNMVbCHMpaBYKXDTIxn3DV7JuGmiO1LayCI_Xa0rkIdc-g",
  "expiresIn": "3600",
  "localId": "fLuVVeMWuhf6mkwDoI38Tqvo57s2"
}

メール/パスワードでサインイン

ドキュメントは こちら です。

harnessing-rest-api-instead-of-firebase-admin-sdk-07.webp

リクエスト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Sign in with email / password</title>
  <style>
    .container {
      max-width: 400px;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }

    .container label {
      display: flex;
      flex-direction: column;
    }
  </style>
</head>
<body>
<h1>Sign in with email / password</h1>
<div class="container">
  <label>Email:
    <input id="email" type="text" placeholder="Email">
  </label>
  <label>Password:
    <input id="password" type="password" placeholder="Password">
  </label>
  <button onclick="signIn()">SignIn</button>
  <hr>
  <label>Response:
    <textarea id="response" rows="5"></textarea>
  </label>
</div>
<script>
  async function signIn() {
    try {
      const email = document.querySelector('#email').value;
      const password = document.querySelector('#password').value;

      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
      const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${apiKey}`;
      const body = {
        email: email,
        password: password,
        returnSecureToken: true,
      };
      const option = {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
      };

      const res = await fetch(url, option);
      const response = await res.json();
      console.log(response);
      setResponse(response);
    } catch (e) {
      console.error(e.message);
    }
  }

  function setResponse(data) {
    document.querySelector('#response').value = JSON.stringify(data);
  }
</script>
</body>
</html>

レスポンス

{
  "kind": "identitytoolkit#VerifyPasswordResponse",
  "localId": "fLuVVeMWuhf6mkwDoI38Tqvo57s2",
  "email": "[email protected]",
  "displayName": "",
  "idToken": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjhkMDNhZTdmNDczZjJjNmIyNTI3NmMwNjM2MGViOTk4ODdlMjNhYTkiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmItcmVzdC1hcGktd2VpZHU1IiwiYXVkIjoiZmItcmVzdC1hcGktd2VpZHU1IiwiYXV0aF90aW1lIjoxNjg3MzYxNjI0LCJ1c2VyX2lkIjoiZkx1VlZlTVd1aGY2bWt3RG9JMzhUcXZvNTdzMiIsInN1YiI6ImZMdVZWZU1XdWhmNm1rd0RvSTM4VHF2bzU3czIiLCJpYXQiOjE2ODczNjE2MjQsImV4cCI6MTY4NzM2NTIyNCwiZW1haWwiOiJwYW5vcGx5LXN5bmMwd0BpY2xvdWQuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7ImVtYWlsIjpbInBhbm9wbHktc3luYzB3QGljbG91ZC5jb20iXX0sInNpZ25faW5fcHJvdmlkZXIiOiJwYXNzd29yZCJ9fQ.40IM5JldAR_BrQSR7eZB7mN7BbpY1aWufr6zOzbXMHNRW_C4Z4O-UBpuhi--p8ysjzM7WoL_GEW9ZY4ZJOl8-QAFXOGTpjMlHn3AA8pXi8gBIZiSqeomiXQrgGP1d5kXbVC6JmSRbFRnrdjuWaQXQAC8xmFZ4oJj4278n4ZrCheRebZeaVTo1inrMXCKlX2Zzj1SuChkKbl8Bi42h1AHQvhm87yaMN-1GRmYXZm3oWhMOVNPnLIp8B1AomGpOO5hm7rx2IxKNVL5g6PIWPyMIa1Bh1fSxOyg3ZIi0zsisvXDdobuyj571nQ8nf_4fw3rLxYMxwvLqEK4c7GL-wSeYA",
  "registered": true,
  "refreshToken": "APZUo0TAMclAMO6CtNYEj8DcSe_ssct9zxrnoaoZtigvyWREx5ORzwzat1PiD2SdngFFLbFT_FvttjYVf_orjtzrR_-S-yniX9lGEPhA2VS9L7w7REML5s92VSTYKhE13R8E3fXl-O7Qbfd6ip4MrUeZuWR43ZpER8mo_HDAuW7h_yDT1AA6RYAScrNbEyaoBbB2LcE8lXJkpgMXSIvpUIH6L448VZOKjepy-7fAfOK3fPlosA40bPQ",
  "expiresIn": "3600"
}

ユーザーデータを取得する

ドキュメントは こちら です。

IdToken には、↑の メール/パスワードでサインイン のレスポンスの idToken を貼り付けてリクエストしてみてください。

harnessing-rest-api-instead-of-firebase-admin-sdk-08.webp

リクエスト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Get user data</title>
  <style>
    .container {
      max-width: 400px;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }

    .container label {
      display: flex;
      flex-direction: column;
    }
  </style>
</head>
<body>
<h1>Get user data</h1>
<div class="container">
  <label>IdToken:
    <textarea id="idToken" placeholder="IdToken" rows="10"></textarea>
  </label>
  <button onclick="getUserData()">GetUserData</button>
  <hr>
  <label>Response:
    <textarea id="response" rows="5"></textarea>
  </label>
</div>
<script>
  async function getUserData() {
    try {
      const idToken = document.querySelector('#idToken').value;

      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
      const url = `https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=${apiKey}`;
      const body = {
        idToken: idToken,
      };
      const option = {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
      };

      const res = await fetch(url, option);
      const response = await res.json();
      console.log(response);
      setResponse(response);
    } catch (e) {
      console.error(e.message);
    }
  }

  function setResponse(data) {
    document.querySelector('#response').value = JSON.stringify(data);
  }
</script>
</body>
</html>

レスポンス

{
  "kind": "identitytoolkit#GetAccountInfoResponse",
  "users": [
    {
      "localId": "fLuVVeMWuhf6mkwDoI38Tqvo57s2",
      "email": "[email protected]",
      "passwordHash": "UkVEQUNURUQ=",
      "emailVerified": false,
      "passwordUpdatedAt": 1687361580458,
      "providerUserInfo": [
        {
          "providerId": "password",
          "federatedId": "[email protected]",
          "email": "[email protected]",
          "rawId": "[email protected]"
        }
      ],
      "validSince": "1687361580",
      "lastLoginAt": "1687361580458",
      "createdAt": "1687361580458",
      "lastRefreshAt": "2023-06-21T15:33:00.458Z"
    }
  ]
}

確認メールを送信する

ドキュメントは こちら です。

harnessing-rest-api-instead-of-firebase-admin-sdk-09.webp

リクエスト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Send email verification</title>
  <style>
    .container {
      max-width: 400px;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }

    .container label {
      display: flex;
      flex-direction: column;
    }
  </style>
</head>
<body>
<h1>Send email verification</h1>
<div class="container">
  <label>IdToken:
    <textarea id="idToken" placeholder="IdToken" rows="10"></textarea>
  </label>
  <button onclick="sendEmailVerification()">SendEmailVerification</button>
  <hr>
  <label>Response:
    <textarea id="response" rows="5"></textarea>
  </label>
</div>
<script>
  async function sendEmailVerification() {
    try {
      const idToken = document.querySelector('#idToken').value;

      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
      const url = `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apiKey}`;
      const body = {
        requestType: 'VERIFY_EMAIL',
        idToken: idToken,
      };
      const option = {
        method: 'POST',
        headers: {
          // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
          'X-Firebase-Locale': 'ja',
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(body),
      };

      const res = await fetch(url, option);
      const response = await res.json();
      console.log(response);
      setResponse(response);
    } catch (e) {
      console.error(e.message);
    }
  }

  function setResponse(data) {
    document.querySelector('#response').value = JSON.stringify(data);
  }
</script>
</body>
</html>

レスポンス

{
  "kind": "identitytoolkit#GetOobConfirmationCodeResponse",
  "email": "[email protected]"
}

確認メール

harnessing-rest-api-instead-of-firebase-admin-sdk-10.webp

おわり