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?

権限は大丈夫なはずなのにPATCH時に401 Unauthorizedとなる理由と解決方法

Posted at

権限は大丈夫なはずなのにPATCH時に401 Unauthorizedとなる理由と解決方法

TL;DR

RLS(Row Level Security)や列レベル権限でpassword列を非公開にしていると、Prefer: return=representation付きのPATCH/DELETEが401 Unauthorizedになることがあります。
原因は、更新後にPostgRESTがSELECT *を自動実行し、password列を読み込めず権限エラーになることです。
解決策は、URLに?select=を付けて返却列を明示的に限定するだけで解消します。

前提

usersテーブル例

create table public.users(
  name        text primary key,
  password    text,  -- 本番ではハッシュ推奨
  money       int,
  last_access date
);

権限制御の要件

  • GET(Read): 誰でもOK
  • POST(Create): 誰でもOK
  • PATCH/DELETE(Update/Delete): x-user-namex-user-password ヘッダが行の namepassword と一致している場合のみOK
  • password列は外部に返さない

RLS および列権限の例

-- RLS有効
alter table public.users enable row level security;

-- password列はSELECTも含め権限を与えない
revoke all on public.users from anon, authenticated;
grant  select (name, money, last_access) on public.users to anon, authenticated;

-- UPDATEポリシー
create policy "update when header matches"
on users
for update
using (
  name     = (current_setting('request.headers', true)::json ->> 'x-user-name')
  and
  password = (current_setting('request.headers', true)::json ->> 'x-user-password')
);

-- DELETEポリシー
create policy "delete when header matches"
on users
for delete
using (
  name     = (current_setting('request.headers', true)::json ->> 'x-user-name')
  and
  password = (current_setting('request.headers', true)::json ->> 'x-user-password')
);

-- INSERT/SELECTも誰でもOK
create policy "public read" on users for select using (true);
create policy "public insert" on users for insert with check (true);

症状:PATCHが401 Unauthorizedになる

以下のような実装で、PATCHリクエスト時に401 Unauthorizedエラーが発生します。

def update_user_data(
    name="aa",
    password="margomasdfe",
    update_fields={"money": 1010}
):
    url = f"{SUPABASE_URL}/rest/v1/users?name=eq.{name}"
    headers = {
        "apikey": SUPABASE_API_KEY,
        "Authorization": f"Bearer {SUPABASE_API_KEY}",
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Prefer": "return=representation",
        "x-user-name": name,
        "x-user-password": password,
    }
    data = json.dumps(update_fields).encode()
    req  = urllib.request.Request(url, data=data, headers=headers, method="PATCH")
    urllib.request.urlopen(req)      # -> HTTP Error 401: Unauthorized

原因

Prefer: return=representationが付いている場合、
PostgRESTはPATCH成功後に自動でSELECT(しかもSELECT *)を実行し、レスポンスに返そうとします。
しかし、password列のSELECT権限がないため、権限エラー(401 Unauthorized)が発生します。
※ 注意: UPDATEやDELETE自体は成功しているが、返却時のSELECTでエラーが発生します。

解決策:返却列を限定する

passwordを除外し、URLに?select=name,money,last_accessのように付けるだけで解決できます。

import urllib.request, urllib.parse, json

def update_user_data_fixed(
    name="user_name",
    password="password",
    update_fields={"money": 1010},
):
    url = (
        f"{SUPABASE_URL}/rest/v1/users"
        f"?name=eq.{urllib.parse.quote(name)}"
        "&select=name,money,last_access"
    )

    headers = {
        "apikey": SUPABASE_API_KEY,
        "Authorization": f"Bearer {SUPABASE_API_KEY}",
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Prefer": "return=representation",
        "x-user-name": name,
        "x-user-password": password,
    }

    req = urllib.request.Request(
        url,
        data=json.dumps(update_fields).encode(),
        headers=headers,
        method="PATCH",
    )
    with urllib.request.urlopen(req) as res:
        return json.loads(res.read().decode())

print(update_user_data_fixed())

ポイント

  • URLに?select=name,money,last_accessと返却列を限定することで、PostgRESTがアップデート後のSELECTでpassword列を読み込まなくなり、401 Unauthorizedエラーを回避できます。

まとめ

Supabase/PostgRESTでPrefer: return=representation付きのPATCH/DELETEを実行する際、権限制御で非公開なカラムがある場合はURLに?select=を追加して返却列を限定してください。
これにより、「更新はできるのに401 Unauthorized」が発生する問題を解消できます。

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?