使用授權和認證功能保護 Data Connect

Firebase Data Connect 提供強大的用戶端安全防護,包括:

  • 行動和網路用戶端授權
  • 個別查詢和變動層級的授權控管
  • 使用 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 環境外執行。

以這些預設存取層級為起點,您可以使用 where 篩選器和在伺服器上評估的一般運算語言 (CEL) 運算式,在 @auth 指令中定義複雜且穩固的授權檢查。

使用 @auth 指令實作常見的授權情境

預設存取層級是授權的起點

USER 存取層級是最實用的基本層級,適合做為起點。

完全安全存取權是以 USER 層級為基礎,加上可檢查使用者屬性、資源屬性、角色和其他檢查項目的篩選器和運算式。USER_ANONUSER_EMAIL_VERIFIED 層級是 USER 案例的變體。

您可以使用運算式語法,透過代表作業傳遞的驗證資料的 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
# 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 設為「公開」。

# 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 指令,以便查詢授權資料

常見的授權用途包括在資料庫中儲存自訂授權角色 (例如在特殊權限資料表中),並使用這些角色授權變異,以建立、更新或刪除資料。

您可以使用授權資料查詢,根據 userID 查詢角色,並使用 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!
}

用於變異

在下列範例實作中,UpdateMovieTitle 突變包含 query 欄位,可從 MoviePermission 擷取資料,並包含下列指令,確保作業安全無虞且穩定可靠:

  • @transaction 指令,確保所有授權查詢和檢查作業都會完成,或以不可分割的形式失敗。
  • @redact 指令可從回應中省略查詢結果,也就是說,授權檢查會在 Data Connect 伺服器上執行,但敏感資料不會向用戶端公開。
  • 一對 @check 指令,用於評估查詢結果的授權邏輯,例如測試特定使用者 ID 是否具有適當角色,可進行修改。

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 驗證的電子郵件地址存取權。

# 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 等預設存取層級是強大授權的起點,應搭配額外的篩選條件和以運算式為準的授權檢查使用。請務必審慎評估用途,再決定是否單獨使用。

Data Connect 可在您使用 Firebase CLI 部署至伺服器時,分析連結器程式碼,協助稽核授權策略。firebase deploy您可以透過這項稽核來檢查程式碼集。

部署連結器時,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 Bad Request 錯誤。
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)")

運算式可用的資料:要求、回應、this

資料的用途包括:

  • @auth(expr:)@check(expr:) 指令中,使用 CEL 運算式進行評估
  • 使用伺服器運算式指派,<field>_expr

@auth(expr:)@check(expr:) CEL 運算式都可以評估下列項目:

  • request.operationName
  • vars (request.variables 的別名)
  • auth (request.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 金鑰、刪除次數和空值 (請參閱突變參考資料)。

例如:

  • 在含有內嵌查詢的突變中,繫結會在 response.query.<fieldName>.<fieldName>.... 包含查詢資料,在本例中為 response.query.todoListresponse.query.todoList.priorityresponse
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、數字、字串、清單、對應、時間戳記或時間長度 從左到右
a==b a!=b 比較運算子 從左到右
a && b 條件式 AND 從左到右
a || b 條件式 OR 從左到右
a ? true_value : false_value 三元運算式 從左到右

驗證權杖中的資料

auth.token 物件可能包含下列值:

欄位 說明
email 與帳戶相關聯的電子郵件地址 (如有)。
email_verified true,前提是使用者已驗證自己有權存取 email 地址。部分供應商會自動驗證他們擁有的電子郵件地址。
phone_number 與帳戶相關聯的電話號碼 (如有)。
name 使用者的顯示名稱 (如有設定)。
sub 使用者的 Firebase UID。這項 ID 在專案中不得重複。
firebase.identities 與這個使用者帳戶相關聯的所有身分識別字典。字典的鍵可以是下列任一項:emailphonegoogle.comfacebook.comgithub.comtwitter.com。字典的值是與帳戶相關聯的每個身分識別提供者的專屬 ID 陣列。舉例來說,auth.token.firebase.identities["google.com"][0] 包含與帳戶相關聯的第一個 Google 使用者 ID。
firebase.sign_in_provider 用來取得這個權杖的登入資訊提供者。可以是下列任一字串:custompasswordphoneanonymousgoogle.comfacebook.comgithub.comtwitter.com
firebase.tenant 與帳戶相關聯的 tenantId (如有)。例如 tenant2-m6tyz

JWT ID 權杖中的其他欄位

您也可以存取下列 auth.token 欄位:

自訂權杖聲明
alg 演算法 "RS256"
iss 核發單位 專案的服務帳戶電子郵件地址
sub 主旨 專案的服務帳戶電子郵件地址
aud 目標對象 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat 核發時間 目前時間,以 UNIX Epoch 紀元時間為始 (單位為秒)
exp 到期時間 權杖到期時間,以秒為單位,自 UNIX Epoch 紀元時間開始算起。iat 最多可延後 3600 秒
注意:這項設定只會控管自訂權杖本身的到期時間,不過,一旦使用 signInWithCustomToken() 登入使用者,他們就會保持登入裝置的狀態,直到工作階段失效或使用者登出為止。
<claims> (選填) 要在權杖中加入的選用自訂聲明,可透過運算式中的 auth.token (或 request.auth.token) 存取。舉例來說,如果您建立自訂聲明 adminClaim,可以使用 auth.token.adminClaim 存取該聲明。

後續步驟