Teruhiro Komaki

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

Cloudflare WorkersでAPIを作っていますが、何かしら制限をかけないと、誰でもリクエストできてしまいます。

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

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

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

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

間違いがあれば、コメントしてもらえると嬉しいです。

環境

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

ドキュメント

firebase
firebase

Firebaseの準備

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

firebase
firebase

Authentication -> ログイン プロバイダ -> メール / パスワード を追加します。

firebase
firebase

アプリを追加します。

firebase
firebase

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

firebase
firebase

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

APIをリクエスト

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

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

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

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

html
html

リクエスト

<!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();
      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": "panoply-sync0w@icloud.com",
  "refreshToken": "APZUo0RksSngIb3GwemrAgDhSEmUXzJzXaZJpNnqavWk36C2Lz5fPgbbBb5cA-6nrIVd5YwXCtqNXYhNRhD4sBRePJ0N-suje9E0GCCh-aKhcFH77kPJcu64PLOo3vr-cAFgH1i4NBpexJK7svy8uW_dmwnHYH56mn-WuMk_jsPUP2VshZjIPhAcBr_x4GLWhWXo7ucvTnNMVbCHMpaBYKXDTIxn3DV7JuGmiO1LayCI_Xa0rkIdc-g",
  "expiresIn": "3600",
  "localId": "fLuVVeMWuhf6mkwDoI38Tqvo57s2"
}

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

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

html
html

リクエスト

<!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();
      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": "panoply-sync0w@icloud.com",
  "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 を貼り付けてリクエストしてみてください。

html
html

リクエスト

<!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();
      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": "panoply-sync0w@icloud.com",
      "passwordHash": "UkVEQUNURUQ=",
      "emailVerified": false,
      "passwordUpdatedAt": 1687361580458,
      "providerUserInfo": [
        {
          "providerId": "password",
          "federatedId": "panoply-sync0w@icloud.com",
          "email": "panoply-sync0w@icloud.com",
          "rawId": "panoply-sync0w@icloud.com"
        }
      ],
      "validSince": "1687361580",
      "lastLoginAt": "1687361580458",
      "createdAt": "1687361580458",
      "lastRefreshAt": "2023-06-21T15:33:00.458Z"
    }
  ]
}

確認メールを送信する

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

html
html

リクエスト

<!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();
      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": "panoply-sync0w@icloud.com"
}

確認メール

main
main

おわり

  • 今回、Firebase AuthenticationのREST APIを使ってみました。次は、Cloudflare WorkersでMiddlewareを作って、APIを保護したいと思います。