使用授权和证明实现安全的数据连接

Firebase Data Connect 提供强大的客户端安全保障,包括:

  • 移动和 Web 客户端授权
  • 各个查询和突变级授权控制
  • 使用 Firebase App Check 进行应用认证。

Data Connect 通过以下方式扩展了此安全性:

  • 服务器端授权
  • 使用 IAM 确保 Firebase 项目和 Cloud SQL 用户安全。

授权客户端查询和变更

Data ConnectFirebase Authentication 完全集成,因此您可以在设计中利用有关访问您数据的用户(身份验证)的丰富数据,来确定这些用户可以访问哪些数据(授权)。

Data Connect 为查询和突变提供了一个 @auth 指令,可用于设置授权操作所需的身份验证级别。本指南通过示例介绍了 @auth 指令

此外,Data Connect 支持执行嵌入在突变中的查询,因此您可以检索存储在数据库中的其他授权条件,并在 @check 指令中使用这些条件来确定封闭的突变是否已获得授权。对于此授权情况,@redact 指令可让您控制是否通过有线协议将查询结果返回给客户端,以及是否在生成的 SDK 中省略嵌入式查询。查看这些指令的简介和示例

了解 @auth 指令

您可以对 @auth 指令进行参数化,以遵循涵盖许多常见访问场景的多种预设访问权限级别之一。这些级别从 PUBLIC(允许所有客户端进行查询和变更,无需任何类型的身份验证)到 NO_ACCESS(禁止在使用 Firebase Admin SDK 的特权服务器环境之外进行查询和变更)不等。每个级别都与 Firebase Authentication 提供的身份验证流程相关联。

级别 定义
PUBLIC 任何人都可以在经过或未经过身份验证的情况下执行该操作。
PUBLIC 任何人都可以在经过或未经过身份验证的情况下执行该操作。
USER_ANON 任何已识别的用户(包括使用 Firebase Authentication 匿名登录的用户)都有权执行查询或突变。
USER 已使用 Firebase Authentication 登录的任何用户都可以执行查询或突变,但匿名登录用户除外。
USER_EMAIL_VERIFIED 任何已使用 Firebase Authentication 登录且电子邮件地址经过验证的用户均有权执行查询或突变。
NO_ACCESS 此操作无法在 Admin SDK 上下文之外执行。

您可以从这些预设的访问权限级别入手,在 @auth 指令中使用 where 过滤条件和在服务器上评估的通用表达式语言 (CEL) 表达式来定义复杂而强大的授权检查。

使用 @auth 指令实现常见的授权场景

预设的访问权限级别是授权的起点

USER 访问权限级别是最广泛适用的基本级别,可作为入门级别。

完全安全的访问权限将以 USER 级为基础,并添加用于检查用户属性、资源属性、角色和其他检查的过滤器和表达式。USER_ANONUSER_EMAIL_VERIFIED 级别是 USER 变体。

借助表达式语法,您可以使用表示通过操作传递的身份验证数据的 auth 对象来评估数据,包括身份验证令牌中的标准数据和令牌中的自定义数据。如需查看 auth 对象中可用的字段列表,请参阅参考部分

@auth

当然,在某些用例中,PUBLIC 是正确的初始访问权限级别。同样,访问权限级别始终是一个起点,要实现强大的安全性,还需要添加其他过滤条件和表达式。

本指南现在提供了有关如何基于 USERPUBLIC 进行构建的示例。

一个激励人心的例子

以下最佳实践示例适用于以下架构:博客平台的部分内容需要付费才能访问。

此类平台可能会对 UsersPosts 进行建模。

type User @table(key: "uid") {
  uid: String!
  name: String
  birthday: Date
  createdAt: Timestamp! @default(expr: "request.time")
}

type Post @table {
  author: User!
  text: String!
  # "one of 'draft', 'public', or 'pro'"
  visibility: String! @default(value: "draft")
  # "the time at which the post should be considered published. defaults to
  # immediately"
  publishedAt: Timestamp! @default(expr: "request.time")
  createdAt: Timestamp! @default(expr: "request.time")
  updatedAt: Timestamp! @default(expr: "request.time")
}

用户拥有的资源

Firebase 建议您编写用于测试资源所有权(在以下情况下为 Posts 的所有权)的过滤条件和表达式。

在以下示例中,系统会使用表达式读取和比较身份验证令牌中的数据。典型模式是使用 where: {authorUid: {eq_expr: "auth.uid"}} 等表达式将存储的 authorUid 与身份验证令牌中传递的 auth.uid(用户 ID)进行比较。

创建

此授权实践首先将身份验证令牌中的 auth.uid 添加到每个新的 Post 中作为 authorUid 字段,以便在后续授权测试中进行比较。

# Create a new post as the current user
mutation CreatePost($text: String!, $visibility: String) @auth(level: USER) {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}
更新

当客户端尝试更新 Post 时,您可以针对存储的 authorUid 测试传递的 auth.uid

# Update one of the current user's posts
mutation UpdatePost($id: UUID!, $text: String, $visibility: String) @auth(level:USER) {
  post_update(
    # only update posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
    data: {
      text: $text
      visibility: $visibility
      # insert the current server time for updatedAt
      updatedAt_expr: "request.time"
    }
  )
}
删除

授权删除操作时也使用相同的技术。

# Delete one of the current user's posts
mutation DeletePost($id: UUID!) @auth(level: USER) {
  post_delete(
    # only delete posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
  )
}
# Common display information for a post
fragment DisplayPost on Post {
  id, text, createdAt, updatedAt
  author { uid, name }
}
列表
# List all posts belonging to the current user
query ListMyPosts @auth(level: USER) {
  posts(where: {
    userUid: {eq_expr: "auth.uid"}
  }) {
    # See the fragment above
    ...DisplayPost
    # also show visibility since it is user-controlled
    visibility
  }
}
获取
# Get a post only if it belongs to the current user
query GetMyPost($id: UUID!) @auth(level: USER) {
  post(key: {id: $id},
    first: {where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}}
      }}, {
      # See the fragment above
      ...DisplayPost
      # also show visibility since it is user-controlled
      visibility
  }
}

过滤数据

Data Connect 的授权系统可让您编写复杂的过滤器,这些过滤器可以与预设的访问权限级别(如 PUBLIC)相结合,也可以使用来自身份验证令牌的数据。

授权系统还允许您仅使用表达式,而不使用基本访问权限级别,如以下部分示例所示。

按资源属性过滤

在此示例中,授权不是基于身份验证令牌,因为基本安全级别设置为 PUBLIC。不过,我们可以明确将数据库中的记录设置为适合公开访问;假设我们的数据库中有 Post 条记录,其中 visibility 设置为“public”。

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
按用户声明过滤

在此示例中,假设您已设置自定义用户声明,这些声明会传递到身份验证令牌中,以标识应用中采用“专业版”方案的用户,并在身份验证令牌中通过 auth.token.plan 字段进行标记。您的表达式可以针对此字段进行测试。

# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
  posts(where: {
    # display both public posts and "pro" posts
    visibility: {in: ['public', 'pro']},
    # only display articles that are already published
    publishedAt: {lt_expr: "request.time"},
  }) {
    # see the fragment above
    ...DisplayPost
    # show visibility so pro users can see which posts are pro\
    visibility
  }
}
按顺序过滤 + 限制

或者,您可能已在 Post 记录中设置 visibility,以标识这些记录是面向“专业版”用户的内容,但为了预览或预告数据列表,您需要进一步限制返回的记录数量。

# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
  posts(
    where: {
      # show only pro posts
      visibility: {eq: "pro"}
      # that have already been published more than 30 days ago
      publishedAt: {lt_time: {now: true, sub: {days: 30}}}
    },
    # order by publish time
    orderBy: [{publishedAt: DESC}],
    # only return two posts
    limit: 2
  ) {
    # See the fragment above
    ...DisplayPost
  }
}
按角色过滤

如果您的自定义声明定义了 admin 角色,您可以相应地测试和授权操作。

# List all posts unconditionally iff the current user has an admin claim
query AdminListPosts @auth(expr: "auth.token.admin == true") {
  posts { ...DisplayPost }
}

添加 @check@redact 指令以查找授权数据

一种常见的授权使用情形是将自定义授权角色存储在数据库中(例如,存储在特殊权限表中),并使用这些角色来授权创建、更新或删除数据的变更。

通过使用授权数据查找,您可以根据用户 ID 查询角色,并使用 CEL 表达式来决定是否授权变动。例如,您可能需要编写一个 UpdateMovieTitle 突变,让经过授权的客户端更新电影片名。

在本文的其余部分,我们假设电影评论应用数据库将授权角色存储在 MoviePermission 表中。

# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["movie", "user"]) {
  movie: Movie! # implies another field: movieId: UUID!
  user: User!
  role: String!
}

在 mutation 中使用

在以下示例实现中,UpdateMovieTitle 突变包含一个用于从 MoviePermission 检索数据的 query 字段,以及以下指令来确保操作安全可靠:

  • 一种 @transaction 指令,用于确保所有授权查询和检查以原子方式完成或失败。
  • @redact 指令可从响应中省略查询结果,这意味着我们的授权检查是在 Data Connect 服务器上执行的,但敏感数据不会向客户端公开。
  • 一对 @check 指令,用于评估查询结果的授权逻辑,例如测试给定的 userID 是否具有进行修改的相应角色。

mutation UpdateMovieTitle($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    # Step 1a: Use @check to test if the user has any role associated with the movie
    # Here the `this` binding refers the lookup result, i.e. a MoviePermission object or null
    # The `this != null` expression could be omitted since rejecting on null is default behavior
    ) @check(expr: "this != null", message: "You do not have access to this movie") {
      # Step 1b: Check if the user has the editor role for the movie
      # Next we execute another @check; now `this` refers to the contents of the `role` field
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

在查询中使用

授权数据查找还可用于根据角色或其他限制来限制查询。

在以下示例中,该示例也使用 MoviePermission 架构,查询会检查请求者是否具有适当的“管理员”角色来查看可以编辑电影的用户。

query GetMovieEditors($movieId: UUID!) @auth(level: PUBLIC) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

授权中要避免的反模式

上一部分介绍了使用 @auth 指令时应遵循的模式。

您还应了解要避免的重要反模式。

避免在查询和突变实参中传递用户属性 ID 和身份验证令牌参数

Firebase Authentication 是一款强大的工具,可用于呈现身份验证流程并安全地捕获身份验证数据,例如注册的用户 ID 和存储在身份验证令牌中的众多字段。

不建议在查询和突变实参中传递用户 ID 和身份验证令牌数据。

# Antipattern!
# This incorrectly allows any user to view any other user's posts
query AllMyPosts($userId: String!) @auth(level: USER) {
  posts(where: {authorUid: {eq: $userId}}) {
    id, text, createdAt
  }
}

避免在没有任何过滤器的情况下使用 USER 访问权限级别

如本指南中多次讨论的那样,USERUSER_ANONUSER_EMAIL_VERIFIED 等核心访问权限级别是授权检查的基准和起点,可通过过滤器和表达式进行增强。如果不使用相应的过滤条件或表达式来检查哪个用户正在执行请求,则使用这些级别实际上等同于使用 PUBLIC 级别。

# Antipattern!
# This incorrectly allows any user to view all documents
query ListDocuments @auth(level: USER) {
  documents {
    id
    title
    text
  }
}

避免在原型设计中使用 PUBLICUSER 访问权限级别

为了加快开发速度,您可能会倾向于将所有操作设置为 PUBLIC 访问权限级别或 USER 访问权限级别,而无需进一步增强功能来授权所有操作,从而让您快速测试代码。

当您以这种方式完成初步原型设计后,开始从 NO_ACCESS 切换到使用 PUBLICUSER 级别的生产就绪型授权。不过,请勿在不添加其他逻辑的情况下将它们部署为 PUBLICUSER,如本指南所示。

# Antipattern!
# This incorrectly allows anyone to delete any post
mutation DeletePost($id: UUID!) @auth(level: PUBLIC) {
  post: post_delete(
    id: $id,
  )
}

避免基于未经验证的电子邮件地址进行授权

向特定网域中的用户授予访问权限是限制访问权限的好方法。 不过,任何人都可以在登录时声明自己拥有某个电子邮件地址。确保您仅向已通过 Firebase Authentication 验证的电子邮件地址授予访问权限。

# Antipattern!
# Anyone can claim an email address during sign-in
mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

另请查看 auth.token.email_verified

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_veri&&fied  auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

使用 Firebase CLI 审核授权

如前所述,预设的访问权限级别(例如 PUBLICUSER)是实现强大授权的起点,应与基于其他过滤条件和表达式的授权检查搭配使用。在未仔细考虑使用情形的情况下,不应单独使用这些参数。

当您使用 Firebase CLI 中的 firebase deploy 部署到服务器时,Data Connect 会通过分析连接器代码来帮助您审核授权策略。您可以使用此审核来帮助您检查代码库。

部署连接器时,CLI 会输出连接器中现有、已修改和新操作代码的评估结果。

对于修改后的操作和新操作,当您在新操作中使用某些访问权限级别,或者修改现有操作以使用这些访问权限级别时,CLI 会发出警告并提示您进行确认。

系统始终会在以下情况下显示警告和提示:

  • PUBLIC

如果您不使用 auth.uid 通过过滤条件来扩充以下访问权限级别,系统会显示警告和提示:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

使用 @auth(insecureReason:) 实参禁止显示不安全操作警告

在许多情况下,您会发现使用 PUBLICUSER* 访问权限级别非常合适。

如果连接器包含许多操作,您可能希望获得更清晰、更相关的安全审核输出,其中省略通常会触发警告但您知道具有正确访问权限的操作。

您可以使用 @auth(insecureReason:) 抑制此类操作的警告。 例如:

query listItem @auth(level: PUBLIC, insecureReason: "This operation is safe to expose to the public.")
  {
    items {
      id name
    }
  }

使用 Firebase App Check 进行应用认证

身份验证和授权是 Data Connect 安全性的关键组成部分。将身份验证和授权与应用证明相结合,可打造非常可靠的安全解决方案。

通过 Firebase App Check 进行证明时,运行您的应用的设备将使用应用或设备证明提供方,证明 Data Connect 操作来自您的正版应用,并且请求来自真实的、未经篡改的设备。此证明会附加到您的应用向 Data Connect 发出的每个请求。

如需了解如何为 Data Connect 启用 App Check 并将相应的客户端 SDK 添加到应用中,请查看 App Check 概览

@auth(level) 指令的身份验证级别

下表列出了所有标准访问权限级别及其对应的 CEL 等效项。 身份验证级别按从宽泛到严格的顺序列出 - 每个级别都涵盖符合后续级别的所有用户。

级别 定义
PUBLIC 任何人都可以在经过或未经过身份验证的情况下执行该操作。

注意事项:任何用户都可以读取或修改数据。 对于可公开浏览的数据(例如商品或媒体内容列表),Firebase 建议使用此授权级别。请参阅最佳实践示例和替代方案

相当于 @auth(expr: "true")

@auth 过滤条件和表达式不能与此访问权限级别结合使用。任何此类表达式都会失败并显示 400 错误请求错误。
USER_ANON 任何已识别的用户(包括使用 Firebase Authentication 匿名登录的用户)都有权执行查询或突变。

注意:USER_ANONUSER 的超集。

注意事项:请注意,您必须为此授权级别精心设计查询和突变。此级别允许用户使用 Authentication匿名方式登录(自动登录仅与用户设备相关联),并且本身不会执行其他检查,例如检查数据是否属于用户。请参阅最佳实践示例和替代方案

由于 Authentication 匿名登录流程会发出 uid,因此 USER_ANON 级别相当于
@auth(expr: "auth.uid != nil")
USER 已使用 Firebase Authentication 登录的任何用户均有权执行查询或突变,匿名登录用户除外。

注意事项:请注意,您必须为此授权级别精心设计查询和突变。此级别仅检查用户是否已使用 Authentication 登录,不会自行执行其他检查,例如检查数据是否属于该用户。请参阅最佳实践示例和替代方案

相当于 @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED 任何已使用 Firebase Authentication 登录且电子邮件地址经过验证的用户均有权执行查询或突变。

注意事项:由于电子邮件验证是使用 Authentication 执行的,因此它基于更强大的 Authentication 方法,与 USERUSER_ANON 相比,此级别可提供额外的安全性。此级别仅检查用户是否已使用经过验证的电子邮件地址通过 Authentication 登录,不会自行执行其他检查,例如检查数据是否属于该用户。请参阅最佳实践示例和替代方案

相当于 @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS 此操作无法在 Admin SDK 上下文之外执行。

相当于 @auth(expr: "false")

@auth(expr) 的 CEL 参考文档

如本指南其他位置的示例所示,您可以使用通用表达式语言 (CEL) 中定义的表达式,通过 @auth(expr:)@check 指令来控制 Data Connect 的授权。

本部分介绍了与为这些指令创建表达式相关的 CEL 语法。

如需获取完整的 CEL 参考信息,请参阅 CEL 规范

在查询和变更中传入的测试变量

借助 @auth(expr) 语法,您可以访问和测试查询和突变中的变量。

例如,您可以使用 vars.status 包含操作变量(例如 $status)。

mutation Update($id: UUID!, $status: Any) @auth(expr: "has(vars.status)")

可用于表达式的数据:request、response、this

您使用数据是为了:

  • @auth(expr:)@check(expr:) 指令中使用 CEL 表达式进行评估
  • 使用服务器表达式进行分配,<field>_expr

@auth(expr:)@check(expr:) CEL 表达式都可以评估以下内容:

  • request.operationName
  • varsrequest.variables 的别名)
  • authrequest.auth 的别名)

在突变中,您可以访问和分配以下内容:

  • response(用于检查多步逻辑中的部分结果)

此外,@check(expr:) 表达式可以评估:

  • this(当前字段的值)
  • response(用于检查多步逻辑中的部分结果)

request.operationName 绑定

request.operarationName 绑定存储操作类型,可以是查询或变更。

vars 绑定 (request.vars)

借助 vars 绑定,表达式可以访问查询或变更中传递的所有变量。

您可以在表达式中使用 vars.<variablename> 作为完全限定名 request.variables.<variablename> 的别名:

# The following are equivalent
mutation StringType($v: String!) @auth(expr: "vars.v == 'hello'")
mutation StringType($v: String!) @auth(expr: "request.variables.v == 'hello'")

auth 绑定 (request.auth)

Authentication 可识别请求访问您的数据的用户,并以绑定的形式(您可在表达式中利用)提供信息。

在过滤条件和表达式中,您可以使用 auth 作为 request.auth 的别名。

身份验证绑定包含以下信息:

  • uid:分配给请求用户的唯一身份用户 ID。
  • token:由 Authentication 收集的值的映射。

如需详细了解 auth.token 的内容,请参阅身份验证令牌中的数据

response 绑定

response 绑定包含服务器在响应查询或变更时正在组装的数据

随着操作的进行,每当成功完成一个步骤时,response 都会包含成功完成的步骤的响应数据。

response 绑定根据其关联操作的形状进行结构化,包括(多个)嵌套字段和(如果适用)嵌入式查询。

请注意,当您访问嵌入式查询响应数据时,字段可以包含任何数据类型,具体取决于嵌入式查询中请求的数据;当您访问突变字段(例如 _insert_delete)返回的数据时,这些数据可能包含 UUID 键、删除数量、null(请参阅突变参考)。

例如:

  • 在包含嵌入式查询的 mutation 中,response 绑定包含 response.query.<fieldName>.<fieldName>.... 中的查找数据,在本例中为 response.query.todoListresponse.query.todoList.priority
mutation CheckTodoPriority(
  $uniqueListName: String!
) {
  # This query is identified as `response.query`
  query @check(expr: "response.query.todoList.priority == 'high'", message: "This list is not for high priority items!") {
    # This field is identified as `response.query.todoList`
    todoList(where: { name: $uniqueListName }) {
      # This field is identified as `response.query.todoList.priority`
      priority
    }
  }
}
  • 在多步突变中(例如,具有多个 _insert 字段),response 绑定在 response.<fieldName>.<fieldName>.... 处包含部分数据,在本例中为 response.todoList_insert.id
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoLis<t_insert.id" # -- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

this 绑定

绑定 this 的计算结果为 @check 指令所附加到的字段。在基本情况下,您可能会评估单值查询结果。

mutation UpdateMovieTitle (
  $movieId: UUID!,
  $newTitle: String!)
  @auth(level: USER)
  @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    ) {
      # Check if the user has the editor role for the movie. `this` is the string value of `role`.
      # If the parent moviePermission is null, the @check will also fail automatically.
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

如果返回的字段因任何祖先是列表而多次出现,则会使用绑定到每个值的 this 对每次出现进行测试。

对于任何给定路径,如果某个祖先是 null[],则无法到达该字段,并且系统会跳过该路径的 CEL 评估。换句话说,只有当 thisnull 或非 null 时,才会进行评估,但绝不会为 undefined

如果字段本身是列表或对象,则 this 遵循相同的结构(包括对象中选中的所有后代),如以下示例所示。

mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query {
    moviePermissions( # Now we query for a list of all matching MoviePermissions.
      where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
    # This time we execute the @check on the list, so `this` is the list of objects.
    # We can use the `.exists` macro to check if there is at least one matching entry.
    ) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
      role
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

复杂表达式语法

您可以结合使用 &&|| 运算符来编写更复杂的表达式。

mutation UpsertUser($username: String!) @auth(expr: "(auth != n&&ull)  (vars.username == ';joe')")

以下部分介绍了所有可用的运算符。

运算符和运算符优先级

使用下表作为运算符及其相应优先级的参考。

给定任意表达式 ab、字段 f 和索引 i

运算符 说明 关联度
a[i] a() a.f 索引、调用、字段访问 从左到右
!a -a 一元否定 从右到左
a/b a%b a*b 乘法运算符 从左到右
a+b a-b 加法运算符 从左到右
a>b a>=b a<b a<=b 关系运算符 从左到右
a in b 存在于列表或映射中 从左到右
type(a) == t 类型比较,其中 t 可以是 bool、int、float、number、string、list、map、timestamp 或 duration 从左到右
a==b a!=b 比较运算符 从左到右
a && b 条件“与” 从左到右
a || b 条件“或” 从左到右
a ? true_value : false_value 三元表达式 从左到右

身份验证令牌中的数据

auth.token 对象可能包含以下值:

字段 说明
email 与账号关联的电子邮件地址(如果存在)。
email_verified 如果用户已验证他们可以访问 email 地址,则为 true。某些提供方会自动验证他们拥有的电子邮件地址。
phone_number 与账号关联的电话号码(如果有)。
name 用户的显示名(如果已设置)。
sub 用户的 Firebase UID。此 UID 在项目中是唯一的。
firebase.identities 与此用户账号关联的所有身份的字典。字典的键可以是以下任一值:emailphonegoogle.comfacebook.comgithub.comtwitter.com。字典的值是与账号关联的每个身份提供方的唯一标识符的数组。例如,auth.token.firebase.identities["google.com"][0] 包含与该账号关联的第一个 Google 用户 ID。
firebase.sign_in_provider 用于获取此令牌的登录服务提供方。可以是以下任一字符串:custompasswordphoneanonymousgoogle.comfacebook.comgithub.comtwitter.com
firebase.tenant 与账号关联的租户 ID(如有)。例如 tenant2-m6tyz

JWT ID 令牌中的其他字段

您还可以访问以下 auth.token 字段:

自定义令牌声明
alg 算法 "RS256"
iss 颁发者 您项目的服务账号电子邮件地址
sub 主题 您项目的服务账号电子邮件地址
aud 受众 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat 颁发时间 当前时间(与 UNIX 计时原点之间相隔的秒数)
exp 到期时间 令牌到期的时间(与 UNIX 计时原点之间相隔的秒数),该时间可能比 iat 晚最多 3600 秒
注意:这仅会控制自定义令牌本身的过期时间。但是,一旦您使用 signInWithCustomToken() 让用户登录,他们将一直在设备上保持登录状态,直到其会话失效或用户退出账号为止。
<claims>(可选) 要包含在令牌中的可选自定义声明,可通过表达式中的 auth.token(或 request.auth.token)进行访问。例如,如果您创建了自定义声明 adminClaim,则可以使用 auth.token.adminClaim 访问该声明。

后续步骤