結論
OpenAuthのLambda Authorizerのexample等を使って、「カスタムドメインなしで」認可サーバーとクライアントを実装し、AWSにデプロイした時に、認可サーバーとクライアント間でホスト名が異なると表題のエラーが発生する場合がある。
これを解決するにはOpenAuthで認可サーバー作成時に使っている、@openauthjs/openauth
の authorizerメソッド
のオプションで allowメソッドを上書きしてやるといい。
もう少し詳しく
当該エラーは下記で発生している。
node_modules/@openauthjs/openauth/dist/esm/authorizer.js
if (!await allow({
clientID: client_id,
redirectURI: redirect_uri,
audience
}, c.req.raw))
throw new UnauthorizedClientError(client_id, redirect_uri);
コードを追っていくと、ここで使っているallow()は下記の実装になっている。
const allow = input.allow ?? (async (input2, req) => {
const redir = new URL(input2.redirectURI).hostname;
if (redir === "localhost" || redir === "127.0.0.1") {
return true;
}
const forwarded = req.headers.get("x-forwarded-host");
const host = forwarded ? new URL(`https://` + forwarded).hostname : new URL(req.url).hostname;
return isDomainMatch(redir, host);
});
見ての通り、「認可サーバーとリダイレクト先が同一のホスト名」であるか、あるいは「X-Forwarded-Host指定した元のホスト名とリダイレクト先が同一のホスト名」でないとfalseになるような実装になっている。
しかし、例えばブラウザから302で認可サーバーにリダイレクトする時には、仕様上X-Forwarded-Hostを勝手に追加するわけにはいかない。なので、既存のデフォルトのallowメソッドを上書きして、自分の環境に合わせたallowメソッドをauthorizer初期化時に指定してやる必要がある。
例えば下記のようになる。
OpenAuthを使ったLambda Authorizerの例
import { authorizer } from "@openauthjs/openauth"
import { handle } from "hono/aws-lambda"
import { DynamoStorage } from "@openauthjs/openauth/storage/dynamo"
import { PasswordAdapter } from "@openauthjs/openauth/adapter/password"
import { PasswordUI } from "@openauthjs/openauth/ui/password"
import { subjects } from "./subjects"
import { isDomainMatch } from "@openauthjs/openauth/util"
async function getUser(email: string) {
// Get user from database
// Return user ID
return "123";
}
const app = authorizer({
storage: DynamoStorage({
table: Resource.FeldsparTable.name,
}),
subjects,
providers: {
password: PasswordAdapter(
PasswordUI({
sendCode: async (email, code) => {
console.log(email, code)
},
}),
),
},
success: async (ctx, value) => {
if (value.provider === "password") {
return ctx.subject("user", {
id: await getUser(value.email),
})
}
throw new Error("Invalid provider")
},
// ---- デフォルト実装を使わず、自前で実装したallow()で上書きする ----
allow: (async (input2, req) => {
const redir = new URL(input2.redirectURI).hostname;
if (redir === "localhost" || redir === "127.0.0.1") {
return true;
}
// 例えば下記のような形でホワイトリスト方式で許可する
const host = new URL("許可するURL").hostname;
return isDomainMatch(redir, host);
}),
})
// @ts-ignore
export const handler = handle(app)