🧯

Cloud Functions for Firebase上のHonoでBasic/Bearer Authするだけの記事

2024/02/05に公開

Cloudflare Workers上で動かないライブラリがあったので、急遽Cloud Functionsを使って簡単なAPIを作成しました。

普通にできたのですが、元々Honoを利用して開発しているのでできれば合わせたいです。

調べるとこの方の記事にあるように他のLambda等のadapterを真似て、それをCF用に書き直すことでHonoが動いたとのことで、自分も参考にして動かしてみました。
https://zenn.dev/singularity/articles/firebase-hono

まずHonoをCloud Functionsで動かす

使い方は簡単で、以下のhander.tsをindex.tsの隣に置き、index.tsでHonoを普段通りに書き、最後にhanderでラップするだけです。

hander.ts
import { Request as FunctionRequest, Response } from "firebase-functions";
import { Hono } from "hono";
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
export const handler = (app: Hono<any>) => {
  return async (req: FunctionRequest, resp: Response) => {
    const url = new URL(`${req.protocol}://${req.hostname}${req.url}`);

    const headers = new Headers();

    // biome-ignore lint/complexity/noForEach: <explanation>
    Object.keys(req.headers).forEach((k) => {
      headers.set(k, req.headers[k] as string);
    });
    const body = req.body;

    const newRequest = ["GET", "HEAD"].includes(req.method)
      ? new Request(url, {
          headers,
          method: req.method,
        })
      : new Request(url, {
          headers,
          method: req.method,
          body: Buffer.from(
            typeof body === "string" ? body : JSON.stringify(body || {}),
          ),
        });
    const res = await app.fetch(newRequest);

    const contentType = res.headers.get("content-type");

    if (contentType?.includes("application/json")) {
      resp.json(await res.json());
    } else {
      resp.send(await res.text());
    }
  };
};
index.ts
import * as functions from "firebase-functions";
import { Hono } from "hono";
import { handler } from "./handler";

const app = new Hono();
app.get("/", (c) => c.json({ message: "Hono!" }));

// この`api`がfunction名になる
export const api = functions.https.onRequest(handler(app));

そして実行してみると…

$ npm run serve
...
✔  functions: Loaded functions definitions from source: api.
✔  functions[us-central1-api]: http function initialized (http://127.0.0.1:5001/<YOUR_APP_NAME>/us-central1/api).
...

のようにURLが表示され、

$ curl http://127.0.0.1:5001//<YOUR_APP_NAME>/us-central1/api
{"message":"Hono!"}

と実行できました!

Basic/Bearer Authを施す

ここで、このAPIに対して簡単な認証を施したいと思ったときは、Hono側のmiddlewareを用いて簡単に実現できます。

Basic Authの場合

以下のように2箇所追加するだけです。

index.ts
import * as functions from "firebase-functions";
import { Hono } from "hono";
import { handler } from "./handler";
import { basicAuth } from "hono/basic-auth"; // 追加

const app = new Hono();

// 追加
app.use(
  "*",
  basicAuth({
    username: "hono",
    password: "hono",
  }),
);

app.get("/", (c) => c.json({ message: "Hono!" }));

export const api = functions.https.onRequest(handler(app));

以下のように認証が効いていることがわかります。

# 素で叩く
$ curl http://127.0.0.1:5001/hoelai-13ecf/us-central1/api
Unauthorized

# user/passを指定して叩く
$ curl -u hono:hono http://127.0.0.1:5001/hoelai-13ecf/us-central1/api
{"message":"Hono!"}

Bearer Authの場合

ほぼ一緒です。tokenは hoge にしてます。

index.ts
import * as functions from "firebase-functions";
import { Hono } from "hono";
import { handler } from "./handler";
import { bearerAuth } from "hono/bearer-auth"; // 追加

const app = new Hono();

app.use("*", bearerAuth({ token: "hoge" })); // 追加
app.get("/", (c) => c.json({ message: "Hono!" }));

export const api = functions.https.onRequest(handler(app));

こちらも以下のように認証が効いていることがわかります。

# 素で叩く
$ curl http://127.0.0.1:5001/hoelai-13ecf/us-central1/api
Unauthorized

# headerを追加して叩く
$ curl -H 'Authorization: Bearer hoge' http://127.0.0.1:5001/hoelai-13ecf/us-central1/api
{"message":"Hono!"}

おわり

つまりCloud FunctionsでもHonoで色々できそうですね!

以上です。

Discussion