Skip to content
Auth

令牌安全和行级安全

当你在 Supabase 项目中启用 OAuth 2.1 时,第三方应用可以代表用户访问他们的数据。行级安全(RLS)策略对于控制每个 OAuth 客户端可以访问哪些数据非常重要。

🌐 When you enable OAuth 2.1 in your Supabase project, third-party applications can access user data on their behalf. Row Level Security (RLS) policies are crucial for controlling exactly what data each OAuth client can access.

OAuth 令牌是如何与 RLS 一起工作的 #

🌐 How OAuth tokens work with RLS

Supabase Auth 发出的 OAuth 访问令牌是 JWT,包含所有标准的 Supabase 声明以及特定于 OAuth 的声明。这意味着你现有的 RLS 策略仍然有效,同时你可以添加特定于 OAuth 的逻辑来创建更细粒度的访问控制。

🌐 OAuth access tokens issued by Supabase Auth are JWTs that include all standard Supabase claims plus OAuth-specific claims. This means your existing RLS policies continue to work, and you can add OAuth-specific logic to create granular access controls.

令牌结构 #

🌐 Token structure

每个 OAuth 访问令牌都包含:

🌐 Every OAuth access token includes:

1
{
2
"sub": "user-uuid",
3
"role": "authenticated",
4
"aud": "authenticated",
5
"user_id": "user-uuid",
6
"email": "user@example.com",
7
"client_id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
8
"aal": "aal1",
9
"amr": [{ "method": "password", "timestamp": 1735815600 }],
10
"session_id": "session-uuid",
11
"iss": "https://<project-ref>.supabase.co/auth/v1",
12
"iat": 1735815600,
13
"exp": 1735819200
14
}

关键的 OAuth 特定声明是:

🌐 The key OAuth-specific claim is:

声明描述
client_id获取此令牌的 OAuth 客户端的唯一标识符

你可以在 RLS 策略中使用这个声明,为不同的客户端授予不同的权限。

🌐 You can use this claim in RLS policies to grant different permissions to different clients.

在 RLS 中提取 OAuth 声明 #

🌐 Extracting OAuth claims in RLS

在你的策略中使用 auth.jwt() 函数来访问令牌声明:

🌐 Use the auth.jwt() function to access token claims in your policies:

1
-- Get the client ID from the token
2
(auth.jwt() ->> 'client_id')
3
4
-- Check if the token is from an OAuth client
5
(auth.jwt() ->> 'client_id') IS NOT NULL
6
7
-- Check if the token is from a specific client
8
(auth.jwt() ->> 'client_id') = 'mobile-app-client-id'

OAuth 的常见 RLS 模式 #

🌐 Common RLS patterns for OAuth

模式1:授予特定客户完全访问权限 #

🌐 Pattern 1: Grant specific client full access

允许特定的 OAuth 客户端访问所有用户数据:

🌐 Allow a specific OAuth client to access all user data:

1
CREATE POLICY "Mobile app can access user data"
2
ON user_data FOR ALL
3
USING (
4
auth.uid() = user_id AND
5
(auth.jwt() ->> 'client_id') = 'mobile-app-client-id'
6
);

模式2:授予多个客户端只读访问权限 #

🌐 Pattern 2: Grant multiple clients read-only access

允许多个 OAuth 客户端读取数据,但不能修改它:

🌐 Allow several OAuth clients to read data, but not modify it:

1
CREATE POLICY "Third-party apps can read profiles"
2
ON profiles FOR SELECT
3
USING (
4
auth.uid() = user_id AND
5
(auth.jwt() ->> 'client_id') IN (
6
'analytics-client-id',
7
'reporting-client-id',
8
'dashboard-client-id'
9
)
10
);

模式3:限制OAuth客户端访问敏感数据 #

🌐 Pattern 3: Restrict sensitive data from OAuth clients

防止 OAuth 客户端访问敏感数据:

🌐 Prevent OAuth clients from accessing sensitive data:

1
CREATE POLICY "OAuth clients cannot access payment info"
2
ON payment_methods FOR ALL
3
USING (
4
auth.uid() = user_id AND
5
(auth.jwt() ->> 'client_id') IS NULL -- Only direct user sessions
6
);

模式4:客户专用数据访问 #

🌐 Pattern 4: Client-specific data access

不同的客户端访问不同的数据子集:

🌐 Different clients access different subsets of data:

1
-- Analytics client can only read aggregated data
2
CREATE POLICY "Analytics client reads summaries"
3
ON user_metrics FOR SELECT
4
USING (
5
auth.uid() = user_id AND
6
(auth.jwt() ->> 'client_id') = 'analytics-client-id'
7
);
8
9
-- Admin client can read and modify all data
10
CREATE POLICY "Admin client full access"
11
ON user_data FOR ALL
12
USING (
13
auth.uid() = user_id AND
14
(auth.jwt() ->> 'client_id') = 'admin-client-id'
15
);

真实案例 #

🌐 Real-world examples

示例 1:多平台应用 #

🌐 Example 1: Multi-platform application

你有一个网页应用、移动应用和第三方集成:

🌐 You have a web app, mobile app, and third-party integrations:

1
-- Web app: Full access
2
CREATE POLICY "Web app full access"
3
ON profiles FOR ALL
4
USING (
5
auth.uid() = user_id AND
6
(
7
(auth.jwt() ->> 'client_id') = 'web-app-client-id'
8
OR (auth.jwt() ->> 'client_id') IS NULL -- Direct user sessions
9
)
10
);
11
12
-- Mobile app: Read-only access to profiles
13
CREATE POLICY "Mobile app reads profiles"
14
ON profiles FOR SELECT
15
USING (
16
auth.uid() = user_id AND
17
(auth.jwt() ->> 'client_id') = 'mobile-app-client-id'
18
);
19
20
-- Third-party integration: Limited data access
21
CREATE POLICY "Integration reads public data"
22
ON profiles FOR SELECT
23
USING (
24
auth.uid() = user_id AND
25
(auth.jwt() ->> 'client_id') = 'integration-client-id' AND
26
is_public = true
27
);

自定义访问令牌钩子 #

🌐 Custom access token hooks

自定义访问令牌钩子 可以与 OAuth 令牌一起使用,允许你根据 OAuth 客户端注入自定义声明。这对于自定义标准 JWT 声明,比如 audience (aud),或者添加特定客户端的元数据特别有用。

自定义受众声明 #

🌐 Customizing the audience claim

一个常见的用例是为不同的 OAuth 客户端定制 audience 声明。这允许第三方服务验证令牌是专门为它们颁发的:

🌐 A common use case is customizing the audience claim for different OAuth clients. This allows third-party services to validate that tokens were issued specifically for them:

1
Deno.serve(async (req) => {
2
const { user, claims, client_id } = await req.json()
3
4
// Customize audience based on OAuth client
5
if (client_id === 'mobile-app-client-id') {
6
return new Response(
7
JSON.stringify({
8
claims: {
9
aud: 'https://api.myapp.com',
10
app_version: '2.0.0',
11
},
12
}),
13
{ headers: { 'Content-Type': 'application/json' } }
14
)
15
}
16
17
if (client_id === 'analytics-partner-id') {
18
return new Response(
19
JSON.stringify({
20
claims: {
21
aud: 'https://analytics.partner.com',
22
access_level: 'read-only',
23
},
24
}),
25
{ headers: { 'Content-Type': 'application/json' } }
26
)
27
}
28
29
// Default audience for non-OAuth flows
30
return new Response(JSON.stringify({ claims: {} }), {
31
headers: { 'Content-Type': 'application/json' },
32
})
33
})

audience 申明尤其对以下情况很重要:

🌐 The audience claim is especially important for:

  • 第三方验证JWT:服务可以验证令牌是否是为它们特定的API颁发的
  • 多租户应用:针对不同客户端应用的不同受众
  • 合规性:满足要求受众验证的安全要求

添加特定客户的声明 #

🌐 Adding client-specific claims

你也可以根据 OAuth 客户端添加自定义声明和元数据:

🌐 You can also add custom claims and metadata based on the OAuth client:

1
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
2
3
Deno.serve(async (req) => {
4
const { user, claims, client_id } = await req.json()
5
6
const supabase = createClient(Deno.env.get('SUPABASE_URL')!, Deno.env.get('SUPABASE_SECRET_KEY')!)
7
8
// Add custom claims based on OAuth client
9
let customClaims = {}
10
11
if (client_id === 'mobile-app-client-id') {
12
customClaims.aud = 'https://mobile.myapp.com'
13
customClaims.app_version = '2.0.0'
14
customClaims.platform = 'mobile'
15
} else if (client_id === 'analytics-client-id') {
16
customClaims.aud = 'https://analytics.myapp.com'
17
customClaims.read_only = true
18
customClaims.data_retention_days = 90
19
} else if (client_id?.startsWith('mcp-')) {
20
// MCP AI agents
21
const { data: agent } = await supabase
22
.from('approved_ai_agents')
23
.select('name, max_data_retention_days')
24
.eq('client_id', client_id)
25
.single()
26
27
customClaims.aud = `https://mcp.myapp.com/${client_id}`
28
customClaims.ai_agent = true
29
customClaims.agent_name = agent?.name
30
customClaims.max_retention = agent?.max_data_retention_days
31
}
32
33
return new Response(JSON.stringify({ claims: customClaims }), {
34
headers: { 'Content-Type': 'application/json' },
35
})
36
})

在 RLS 中使用这些自定义声明:

🌐 Use these custom claims in RLS:

1
-- Policy based on custom claims
2
CREATE POLICY "Read-only clients cannot modify"
3
ON user_data FOR UPDATE
4
USING (
5
auth.uid() = user_id AND
6
(auth.jwt() -> 'user_metadata' ->> 'read_only')::boolean IS NOT TRUE
7
);
8
9
-- Policy based on audience claim
10
CREATE POLICY "Only specific audience can access"
11
ON api_data FOR SELECT
12
USING (
13
auth.uid() = user_id AND
14
(auth.jwt() ->> 'aud') IN (
15
'https://api.myapp.com',
16
'https://mobile.myapp.com'
17
)
18
);

安全最佳实践 #

🌐 Security best practices

1. 最小权限原则 #

🌐 1. Principle of least privilege

只给 OAuth 客户端他们需要的最低权限:

🌐 Grant OAuth clients only the minimum permissions they need:

1
-- Bad: Grant all access by default
2
CREATE POLICY "OAuth clients full access"
3
ON user_data FOR ALL
4
USING (auth.uid() = user_id);
5
6
-- Good: Grant specific access per client
7
CREATE POLICY "Specific client specific access"
8
ON user_data FOR SELECT
9
USING (
10
auth.uid() = user_id AND
11
(auth.jwt() ->> 'client_id') = 'trusted-client-id'
12
);

2. 为 OAuth 客户端设置单独策略 #

🌐 2. Separate policies for OAuth clients

为 OAuth 客户端创建专门的策略,而不是把它们和用户策略混在一起:

🌐 Create dedicated policies for OAuth clients rather than mixing them with user policies:

1
-- User access
2
CREATE POLICY "Users access their own data"
3
ON user_data FOR ALL
4
USING (
5
auth.uid() = user_id AND
6
(auth.jwt() ->> 'client_id') IS NULL
7
);
8
9
-- OAuth client access (separate policy)
10
CREATE POLICY "OAuth clients limited access"
11
ON user_data FOR SELECT
12
USING (
13
auth.uid() = user_id AND
14
(auth.jwt() ->> 'client_id') IN ('client-1', 'client-2')
15
);

3. 定期审查 OAuth 客户端 #

🌐 3. Regularly audit OAuth clients

跟踪并查看哪些客户有权限:

🌐 Track and review which clients have access:

1
-- View all active OAuth clients
2
SELECT
3
oc.client_id,
4
oc.name,
5
oc.created_at,
6
COUNT(DISTINCT s.user_id) as active_users
7
FROM auth.oauth_clients oc
8
LEFT JOIN auth.sessions s ON s.client_id = oc.client_id
9
WHERE s.created_at > NOW() - INTERVAL '30 days'
10
GROUP BY oc.client_id, oc.name, oc.created_at;

测试你的政策 #

🌐 Testing your policies

在部署到生产环境之前,务必先测试你的 RLS 策略:

🌐 Always test your RLS policies before deploying to production:

1
-- Test as a specific OAuth client
2
SET request.jwt.claims = '{
3
"sub": "test-user-uuid",
4
"role": "authenticated",
5
"client_id": "test-client-id"
6
}';
7
8
-- Test queries
9
SELECT * FROM user_data WHERE user_id = 'test-user-uuid';
10
11
-- Reset
12
RESET request.jwt.claims;

或者使用 Supabase 仪表板的 RLS 策略测试器

🌐 Or use the Supabase Dashboard's RLS policy tester.

故障排除 #

🌐 Troubleshooting

策略对 OAuth 客户端不起作用 #

🌐 Policy not working for OAuth client

问题:尽管有有效的令牌,OAuth 客户端仍然无法访问数据。

检查

  1. 确认保单包含客户的 client_id
  2. 确保表上启用了 RLS
  3. 检查是否有冲突的限制性政策
  4. 用秘密密钥测试以隔离 RLS 问题
1
-- Debug: See what client_id is in the token
2
SELECT auth.jwt() ->> 'client_id';
3
4
-- Debug: Test without RLS
5
SET LOCAL role = service_role;
6
SELECT * FROM your_table;

政策太宽松 #

🌐 Policy too permissive

问题:OAuth 客户端访问了它不该访问的数据。

解决方案:使用 AS RESTRICTIVE 策略来添加额外的约束条件:

1
-- This policy runs in addition to permissive policies
2
CREATE POLICY "Restrict OAuth clients"
3
ON sensitive_data
4
AS RESTRICTIVE
5
FOR ALL
6
TO authenticated
7
USING (
8
-- OAuth clients cannot access this table at all
9
(auth.jwt() ->> 'client_id') IS NULL
10
);

无法区分用户和 OAuth 客户端 #

🌐 Can't differentiate between users and OAuth clients

问题:需要对直接用户会话和 OAuth 应用不同的逻辑。

解决方案:检查是否存在 client_id

1
-- Direct user sessions (no OAuth)
2
CREATE POLICY "Direct users full access"
3
ON user_data FOR ALL
4
USING (
5
auth.uid() = user_id AND
6
(auth.jwt() ->> 'client_id') IS NULL
7
);
8
9
-- OAuth clients (limited access)
10
CREATE POLICY "OAuth clients read only"
11
ON user_data FOR SELECT
12
USING (
13
auth.uid() = user_id AND
14
(auth.jwt() ->> 'client_id') IS NOT NULL
15
);

下一步 #

🌐 Next steps