0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SST with OpenAuth Betaでエラー "Client xxxx is not authorized to use this redirect_uri: https://xxxxxx.cloudfront.net/callback" を解消する

Last updated at Posted at 2024-12-29

結論

OpenAuthのLambda Authorizerのexample等を使って、「カスタムドメインなしで」認可サーバーとクライアントを実装し、AWSにデプロイした時に、認可サーバーとクライアント間でホスト名が異なると表題のエラーが発生する場合がある。

これを解決するにはOpenAuthで認可サーバー作成時に使っている、@openauthjs/openauthauthorizerメソッド のオプションで 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)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?