PAYATHON 2026

如何结合 payment processor、Firebase 与 Auth0 登录?

支付阿杰

结论

建议按下面的方式划分职责:

  • Auth0 负责身份认证:确认用户身份,并向客户端签发 token。
  • 后端 API 负责授权和业务逻辑:验证 token、读写进度、创建支付订单。
  • Firebase/Firestore 或其他数据库负责持久化:保存用户资料、任务进度和支付状态。
  • payment processor 负责收款:支付平台的客户编号、订单编号与内部用户 ID 的对应关系保存在数据库中。

不要通过姓名或邮箱关联数据,也不要把 Auth0 token 当成用户数据库。可靠的身份标识通常是 token 中的 iss 和 sub,其中 sub 是用户在当前 Auth0 租户中的标识。

只要应用需要保存进度,数据库就不能省略。token 只能在一次 API 请求中证明用户身份,不能保存进度、订阅状态或付款记录。

Auth0 返回的数据分别有什么用途

Auth0 登录成功后,常见的 token 有两类:

  • ID token:供客户端使用,包含当前登录用户的信息,例如姓名、头像和邮箱。
  • Access token:发送给后端 API,证明客户端有权调用该 API。

客户端调用后端时,通常按下面的方式发送 Access token:

Authorization: Bearer eyJ...

后端必须验证:

  • token 的签名;
  • iss,也就是签发者;
  • aud,也就是目标 API;
  • token 是否过期;
  • 所需的权限或 scope。

验证通过后,后端从声明中读取 sub。不要让客户端额外提交 userId,再根据这个 ID 访问数据。攻击者可能替换 ID,从而读取或修改其他用户的数据。

姓名、头像和邮箱可以用于展示,但不能作为数据库主键,因为这些信息可能被修改、重复或缺失。

建议的数据模型

系统可以生成一个不依赖认证平台的内部用户 ID:

users
  id
  auth_issuer
  auth_subject
  email
  display_name
  payment_customer_id
  created_at

progress
  id
  user_id
  task_id
  completed_steps
  updated_at

payments
  id
  user_id
  provider
  provider_customer_id
  provider_payment_id
  status
  amount
  currency
  updated_at

users 表应对 (auth_issuer, auth_subject) 建立唯一约束。

用户第一次访问业务 API 时,后端执行一次“查找或创建”:

  1. 验证 Access token。
  2. 读取 iss 和 sub。
  3. 查找对应的内部用户。
  4. 如果用户不存在,则创建记录。
  5. 此后的进度和付款记录都关联这个内部用户 ID。

这种做法通常称为即时建档,不需要在用户完成注册时马上创建业务数据。

ASP.NET Core 中识别 Auth0 用户

下面是一个简化示例。具体配置名称可能因项目模板和 Auth0 SDK 版本而异,但验证原则相同。

builder.Services
    .AddAuthentication("Bearer")
    .AddJwtBearer("Bearer", options =>
    {
        options.Authority = "https://YOUR_AUTH0_DOMAIN/";
        options.Audience = "https://api.example.com";
    });

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

受保护的接口可以从验证后的身份中读取声明:

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/progress")]
[Authorize]
public class ProgressController : ControllerBase
{
    private readonly IUserRepository _users;
    private readonly IProgressRepository _progress;

    public ProgressController(
        IUserRepository users,
        IProgressRepository progress)
    {
        _users = users;
        _progress = progress;
    }

    [HttpPost]
    public async Task<IActionResult> Save(
        [FromBody] SaveProgressRequest request)
    {
        string? subject = User.FindFirstValue("sub")
            ?? User.FindFirstValue(ClaimTypes.NameIdentifier);

        string? issuer = User.FindFirst("iss")?.Value;

        if (string.IsNullOrWhiteSpace(subject) ||
            string.IsNullOrWhiteSpace(issuer))
        {
            return Unauthorized();
        }

        var user = await _users.FindOrCreateAsync(
            issuer,
            subject,
            User.FindFirstValue(ClaimTypes.Email));

        await _progress.UpsertAsync(
            user.Id,
            request.TaskId,
            request.CompletedSteps);

        return NoContent();
    }
}

public sealed record SaveProgressRequest(
    string TaskId,
    int CompletedSteps);

这里最重要的是,user.Id 由后端根据验证过的 token 确定,而不是直接采用客户端提交的用户编号。

Auth0 与 Firebase 应该如何组合

Firebase 不是一个单独的产品,需要区分其中的不同服务:

  • Firebase Authentication:负责身份认证。
  • Cloud Firestore / Realtime Database:负责数据存储。
  • Firebase Admin SDK:供受信任的后端访问 Firebase 服务。

Auth0 token 未经配置或转换,不能直接当作 Firebase Authentication token 使用。常见的组合方式有两种。

方案一:Auth0 加后端 API,再由后端访问 Firestore

这种方式的职责比较清楚:

C# 客户端
  -> 使用 Auth0 登录
  -> 携带 Auth0 Access token 调用后端
  -> 后端验证 token
  -> 后端通过 Firebase Admin SDK 读写 Firestore

客户端不需要直接登录 Firebase,也不需要获取 Firebase token。数据库的安全边界集中在后端,支付逻辑也更容易统一处理。

方案二:用 Auth0 身份换取 Firebase custom token

如果客户端必须直接访问 Firestore,可以增加一个受保护的 token exchange 接口:

  1. 客户端携带 Auth0 Access token 调用后端。
  2. 后端验证 Auth0 token。
  3. 后端把 Auth0 用户映射到内部用户或 Firebase UID。
  4. 后端通过 Firebase Admin SDK 创建 Firebase custom token。
  5. 客户端使用 custom token 登录 Firebase。
  6. Firestore Security Rules 根据 Firebase UID 限制数据访问。

概念示例如下:

[Authorize]
[HttpPost("firebase-token")]
public async Task<IActionResult> CreateFirebaseToken()
{
    string subject = GetRequiredSubject(User);
    string issuer = GetRequiredIssuer(User);

    var user = await _users.FindOrCreateAsync(issuer, subject);
    string firebaseToken =
        await FirebaseAdmin.Auth.FirebaseAuth.DefaultInstance
            .CreateCustomTokenAsync(user.Id.ToString());

    return Ok(new { token = firebaseToken });
}

不要把未经验证的 sub 或客户端提交的 UID 直接传给 Admin SDK。映射和签发必须在受信任的后端完成。

如果客户端不需要直接访问 Firestore,方案一一般更简单。

是否改用 Firebase Authentication

可以。Firebase Authentication 与 Auth0 多数情况下是替代关系,不需要让用户分别在两套系统中注册。

如果 Firebase Authentication 已经能满足登录方式、账号管理和平台支持需求,可以采用下面的流程:

Firebase Authentication
  -> 客户端获得 Firebase ID token
  -> 后端验证 Firebase ID token
  -> 使用 Firebase UID 查找内部用户
  -> 保存进度或发起支付

如果应用需要 Auth0 的企业身份源、复杂登录流程或授权能力,则可以继续使用 Auth0。选择哪一种主要取决于认证需求,与 payment processor 关系不大,因为支付平台通常不会要求使用特定的登录服务。

payment processor 如何关联正确用户

支付必须由后端发起,不能只依赖客户端提交的邮箱、金额或用户 ID。

建议采用下面的流程:

  1. 用户登录后,携带 Access token 调用后端。
  2. 后端验证 token,并取得内部用户 ID。
  3. 后端查询该用户的 payment_customer_id。
  4. 如果还没有支付客户记录,后端调用支付平台 API 创建客户,并保存 customer ID。
  5. 后端创建订单或结账会话。
  6. 把内部订单 ID 写入支付平台支持的 metadata 或 reference 字段。
  7. 支付平台通过 webhook 通知支付结果。
  8. 后端验证 webhook 签名,再通过支付平台的 payment/customer ID 查找本地记录。
  9. 后端更新订单、订阅或权限状态。

伪代码如下:

[Authorize]
[HttpPost("checkout")]
public async Task<IActionResult> CreateCheckout()
{
    var user = await ResolveCurrentUserAsync(User);

    if (user.PaymentCustomerId is null)
    {
        var customer = await _payments.CreateCustomerAsync(
            email: user.Email,
            reference: user.Id.ToString());

        await _users.SetPaymentCustomerIdAsync(
            user.Id,
            customer.Id);
    }

    var order = await _orders.CreatePendingAsync(user.Id);

    var checkout = await _payments.CreateCheckoutAsync(
        customerId: user.PaymentCustomerId!,
        metadata: new Dictionary<string, string>
        {
            ["order_id"] = order.Id.ToString()
        });

    return Ok(new { checkoutUrl = checkout.Url });
}

这里的 API 只是抽象示例,实际方法取决于支付平台及其 SDK。

不要仅根据支付成功后的浏览器跳转把订单标记为已付款。用户可能关闭页面,跳转地址也可能被伪造。支付状态应主要以通过签名验证的 webhook 为准,同时需要做好幂等处理,避免重复处理同一个 webhook。

安全和实现注意事项

  • 不要把 Auth0 Client Secret、Firebase 服务账号密钥或支付平台 Secret Key 放进 C# 桌面客户端。
  • 不要用姓名作为主键。
  • 邮箱可以作为联系信息保存,但不能作为唯一且永久的身份依据。
  • 不要把 Access token 存入用户表并作为长期标识。token 会过期,也会轮换。
  • 使用 HTTPS,并按照平台建议安全保存客户端 token。
  • 更新进度时,应由后端从认证上下文中确定用户,防止水平越权。
  • 支付金额和商品价格必须由后端计算,不能采用客户端提交的金额。
  • webhook 应验证签名、记录事件 ID,并防止重复处理同一事件。
  • 如果允许用户合并多个登录方式,需要单独设计账号关联流程,不能假定所有 provider 的 sub 都相同。
  • 如果只需要在本地保存单机进度,可以暂时采用本地存储。一旦需要跨设备同步、订阅或付款权益,就需要后端和持久化存储。

实际落地时,可以先保留 Auth0,建立一个 ASP.NET Core API,通过 iss + sub 创建内部用户。Firestore 和 payment processor 都由这个 API 访问。这样,认证、业务数据和支付彼此分离,同时又能通过内部用户 ID 稳定关联。

备注:内容仅供参考。