Teruhiro Komaki's Blog (Temporary)

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

ドキュメント

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

リクエスト

 1<!DOCTYPE html>
 2<html lang="ja">
 3<head>
 4  <meta charset="UTF-8">
 5  <title>Sign up with email / password</title>
 6  <style>
 7    .container {
 8      max-width: 400px;
 9      display: flex;
10      flex-direction: column;
11      gap: 1rem;
12    }
13
14    .container label {
15      display: flex;
16      flex-direction: column;
17    }
18  </style>
19</head>
20<body>
21<h1>Sign up with email / password</h1>
22<div class="container">
23  <label>Email:
24    <input id="email" type="text" placeholder="Email">
25  </label>
26  <label>Password:
27    <input id="password" type="password" placeholder="Password">
28  </label>
29  <button onclick="signUp()">SignUp</button>
30  <hr>
31  <label>Response:
32    <textarea id="response" rows="5"></textarea>
33  </label>
34</div>
35<script>
36  async function signUp() {
37    try {
38      const email = document.querySelector('#email').value;
39      const password = document.querySelector('#password').value;
40
41      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
42      const url = `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${apiKey}`;
43      const body = {
44        email: email,
45        password: password,
46        returnSecureToken: true,
47      };
48      const option = {
49        method: 'POST',
50        headers: {
51          'Content-Type': 'application/json',
52        },
53        body: JSON.stringify(body),
54      };
55
56      const res = await fetch(url, option);
57      const response = await res.json();
58      setResponse(response);
59    } catch (e) {
60      console.error(e.message);
61    }
62  }
63
64  function setResponse(data) {
65    document.querySelector('#response').value = JSON.stringify(data);
66  }
67</script>
68</body>
69</html>

レスポンス

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

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

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

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

リクエスト

 1<!DOCTYPE html>
 2<html lang="ja">
 3<head>
 4  <meta charset="UTF-8">
 5  <title>Sign in with email / password</title>
 6  <style>
 7    .container {
 8      max-width: 400px;
 9      display: flex;
10      flex-direction: column;
11      gap: 1rem;
12    }
13
14    .container label {
15      display: flex;
16      flex-direction: column;
17    }
18  </style>
19</head>
20<body>
21<h1>Sign in with email / password</h1>
22<div class="container">
23  <label>Email:
24    <input id="email" type="text" placeholder="Email">
25  </label>
26  <label>Password:
27    <input id="password" type="password" placeholder="Password">
28  </label>
29  <button onclick="signIn()">SignIn</button>
30  <hr>
31  <label>Response:
32    <textarea id="response" rows="5"></textarea>
33  </label>
34</div>
35<script>
36  async function signIn() {
37    try {
38      const email = document.querySelector('#email').value;
39      const password = document.querySelector('#password').value;
40
41      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
42      const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${apiKey}`;
43      const body = {
44        email: email,
45        password: password,
46        returnSecureToken: true,
47      };
48      const option = {
49        method: 'POST',
50        headers: {
51          'Content-Type': 'application/json',
52        },
53        body: JSON.stringify(body),
54      };
55
56      const res = await fetch(url, option);
57      const response = await res.json();
58      setResponse(response);
59    } catch (e) {
60      console.error(e.message);
61    }
62  }
63
64  function setResponse(data) {
65    document.querySelector('#response').value = JSON.stringify(data);
66  }
67</script>
68</body>
69</html>

レスポンス

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

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

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

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

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

リクエスト

 1<!DOCTYPE html>
 2<html lang="ja">
 3<head>
 4  <meta charset="UTF-8">
 5  <title>Get user data</title>
 6  <style>
 7    .container {
 8      max-width: 400px;
 9      display: flex;
10      flex-direction: column;
11      gap: 1rem;
12    }
13
14    .container label {
15      display: flex;
16      flex-direction: column;
17    }
18  </style>
19</head>
20<body>
21<h1>Get user data</h1>
22<div class="container">
23  <label>IdToken:
24    <textarea id="idToken" placeholder="IdToken" rows="10"></textarea>
25  </label>
26  <button onclick="getUserData()">GetUserData</button>
27  <hr>
28  <label>Response:
29    <textarea id="response" rows="5"></textarea>
30  </label>
31</div>
32<script>
33  async function getUserData() {
34    try {
35      const idToken = document.querySelector('#idToken').value;
36
37      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
38      const url = `https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=${apiKey}`;
39      const body = {
40        idToken: idToken,
41      };
42      const option = {
43        method: 'POST',
44        headers: {
45          'Content-Type': 'application/json',
46        },
47        body: JSON.stringify(body),
48      };
49
50      const res = await fetch(url, option);
51      const response = await res.json();
52      setResponse(response);
53    } catch (e) {
54      console.error(e.message);
55    }
56  }
57
58  function setResponse(data) {
59    document.querySelector('#response').value = JSON.stringify(data);
60  }
61</script>
62</body>
63</html>

レスポンス

 1{
 2  "kind": "identitytoolkit#GetAccountInfoResponse",
 3  "users": [
 4    {
 5      "localId": "fLuVVeMWuhf6mkwDoI38Tqvo57s2",
 6      "email": "[email protected]",
 7      "passwordHash": "UkVEQUNURUQ=",
 8      "emailVerified": false,
 9      "passwordUpdatedAt": 1687361580458,
10      "providerUserInfo": [
11        {
12          "providerId": "password",
13          "federatedId": "[email protected]",
14          "email": "[email protected]",
15          "rawId": "[email protected]"
16        }
17      ],
18      "validSince": "1687361580",
19      "lastLoginAt": "1687361580458",
20      "createdAt": "1687361580458",
21      "lastRefreshAt": "2023-06-21T15:33:00.458Z"
22    }
23  ]
24}

確認メールを送信する

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

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

リクエスト

 1<!DOCTYPE html>
 2<html lang="ja">
 3<head>
 4  <meta charset="UTF-8">
 5  <title>Send email verification</title>
 6  <style>
 7    .container {
 8      max-width: 400px;
 9      display: flex;
10      flex-direction: column;
11      gap: 1rem;
12    }
13
14    .container label {
15      display: flex;
16      flex-direction: column;
17    }
18  </style>
19</head>
20<body>
21<h1>Send email verification</h1>
22<div class="container">
23  <label>IdToken:
24    <textarea id="idToken" placeholder="IdToken" rows="10"></textarea>
25  </label>
26  <button onclick="sendEmailVerification()">SendEmailVerification</button>
27  <hr>
28  <label>Response:
29    <textarea id="response" rows="5"></textarea>
30  </label>
31</div>
32<script>
33  async function sendEmailVerification() {
34    try {
35      const idToken = document.querySelector('#idToken').value;
36
37      const apiKey = 'AIzaSyCLDaLd0GCIKIF0hK1sBW_j0IbBfDpJyS0';
38      const url = `https://identitytoolkit.googleapis.com/v1/accounts:sendOobCode?key=${apiKey}`;
39      const body = {
40        requestType: 'VERIFY_EMAIL',
41        idToken: idToken,
42      };
43      const option = {
44        method: 'POST',
45        headers: {
46          // https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
47          'X-Firebase-Locale': 'ja',
48          'Content-Type': 'application/json',
49        },
50        body: JSON.stringify(body),
51      };
52
53      const res = await fetch(url, option);
54      const response = await res.json();
55      setResponse(response);
56    } catch (e) {
57      console.error(e.message);
58    }
59  }
60
61  function setResponse(data) {
62    document.querySelector('#response').value = JSON.stringify(data);
63  }
64</script>
65</body>
66</html>

レスポンス

1{
2  "kind": "identitytoolkit#GetOobConfirmationCodeResponse",
3  "email": "[email protected]"
4}

確認メール

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

おわり

Tags:
comments powered by Disqus