Bảo mật Data Connect bằng tính năng uỷ quyền và chứng thực

Firebase Data Connect cung cấp khả năng bảo mật mạnh mẽ ở phía máy khách bằng:

  • Uỷ quyền ứng dụng dành cho thiết bị di động và web
  • Các chế độ kiểm soát uỷ quyền riêng lẻ ở cấp truy vấn và cấp đột biến
  • Chứng thực ứng dụng bằng Firebase App Check.

Data Connect mở rộng tính năng bảo mật này bằng:

  • Uỷ quyền phía máy chủ
  • Bảo mật người dùng dự án Firebase và Cloud SQL bằng IAM.

Uỷ quyền truy vấn và thay đổi của ứng dụng

Data Connect được tích hợp đầy đủ với Firebase Authentication, vì vậy, bạn có thể sử dụng dữ liệu đa dạng thức về những người dùng đang truy cập vào dữ liệu của bạn (xác thực) trong thiết kế của mình để biết những dữ liệu mà những người dùng đó có thể truy cập (uỷ quyền).

Data Connect cung cấp một lệnh @auth cho các truy vấn và sự thay đổi cho phép bạn đặt cấp độ xác thực cần thiết để uỷ quyền cho thao tác. Hướng dẫn này giới thiệu về lệnh @auth kèm theo ví dụ.

Ngoài ra, Data Connect hỗ trợ việc thực thi các truy vấn được nhúng trong các đột biến, vì vậy, bạn có thể truy xuất các tiêu chí uỷ quyền bổ sung mà bạn đã lưu trữ trong cơ sở dữ liệu và sử dụng các tiêu chí đó trong các lệnh @check để quyết định xem các đột biến bao gồm có được uỷ quyền hay không. Đối với trường hợp uỷ quyền này, lệnh @redact cho phép bạn kiểm soát việc kết quả truy vấn có được trả về cho ứng dụng khách trong giao thức truyền tin hay không và truy vấn được nhúng bị bỏ qua trong các SDK được tạo. Tìm phần giới thiệu về các lệnh này, kèm theo ví dụ.

Tìm hiểu về lệnh @auth

Bạn có thể tham số hoá lệnh @auth để tuân theo một trong nhiều cấp truy cập được đặt trước, bao gồm nhiều trường hợp truy cập phổ biến. Các cấp này dao động từ PUBLIC (cho phép truy vấn và đột biến từ tất cả ứng dụng khách mà không cần xác thực bất kỳ loại nào) đến NO_ACCESS (không cho phép truy vấn và đột biến bên ngoài môi trường máy chủ đặc quyền bằng cách sử dụng SDK quản trị Firebase). Mỗi cấp độ này đều tương quan với các luồng xác thực do Firebase Authentication cung cấp.

Cấp độ Định nghĩa
PUBLIC Bất kỳ ai cũng có thể thực thi thao tác này, có hoặc không có xác thực.
PUBLIC Bất kỳ ai cũng có thể thực thi thao tác này, có hoặc không có xác thực.
USER_ANON Mọi người dùng đã xác định, bao gồm cả những người đã đăng nhập ẩn danh bằng Firebase Authentication, đều được phép thực hiện truy vấn hoặc thay đổi.
USER Mọi người dùng đã đăng nhập bằng Firebase Authentication đều được uỷ quyền để thực hiện truy vấn hoặc thay đổi, ngoại trừ người dùng đăng nhập ẩn danh.
USER_EMAIL_VERIFIED Bất kỳ người dùng nào đã đăng nhập bằng Firebase Authentication với địa chỉ email đã xác minh đều được uỷ quyền thực hiện truy vấn hoặc thay đổi.
NO_ACCESS Không thể thực thi thao tác này bên ngoài ngữ cảnh SDK quản trị.

Bằng cách sử dụng các cấp truy cập đặt trước này làm điểm xuất phát, bạn có thể xác định các bước kiểm tra uỷ quyền phức tạp và mạnh mẽ trong lệnh @auth bằng cách sử dụng bộ lọc where và biểu thức Ngôn ngữ biểu thức chung (CEL) được đánh giá trên máy chủ.

Sử dụng lệnh @auth để triển khai các trường hợp uỷ quyền phổ biến

Các cấp truy cập đặt trước là điểm xuất phát để uỷ quyền.

Cấp truy cập USER là cấp cơ bản hữu ích nhất để bắt đầu.

Quyền truy cập bảo mật hoàn toàn sẽ được xây dựng dựa trên cấp USER cùng với các bộ lọc và biểu thức kiểm tra các thuộc tính người dùng, thuộc tính tài nguyên, vai trò và các hoạt động kiểm tra khác. Các cấp độ USER_ANONUSER_EMAIL_VERIFIED là các biến thể của trường hợp USER.

Cú pháp biểu thức cho phép bạn đánh giá dữ liệu bằng cách sử dụng đối tượng auth đại diện cho dữ liệu xác thực được truyền bằng các thao tác, cả dữ liệu chuẩn trong mã thông báo xác thực và dữ liệu tuỳ chỉnh trong mã thông báo. Để biết danh sách các trường có trong đối tượng auth, hãy xem phần tham khảo.

Tất nhiên, có những trường hợp sử dụng mà PUBLIC là cấp truy cập chính xác để bắt đầu. Xin nhắc lại rằng cấp truy cập luôn là điểm xuất phát, đồng thời cần có các bộ lọc và biểu thức bổ sung để tăng cường bảo mật.

Hướng dẫn này hiện đưa ra các ví dụ về cách xây dựng trên USERPUBLIC.

Ví dụ về động lực

Các ví dụ về phương pháp hay nhất sau đây đề cập đến giản đồ sau đây cho một nền tảng viết blog có một số nội dung nhất định bị khoá sau một gói thanh toán.

Nền tảng như vậy có thể mô hình hoá 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")
}

Tài nguyên thuộc quyền sở hữu của người dùng

Firebase khuyên bạn nên viết các bộ lọc và biểu thức kiểm tra quyền sở hữu tài nguyên của người dùng, trong các trường hợp sau, quyền sở hữu Posts.

Trong các ví dụ sau, dữ liệu từ mã thông báo xác thực được đọc và so sánh bằng biểu thức. Mẫu thông thường là sử dụng các biểu thức như where: {authorUid: {eq_expr: "auth.uid"}} để so sánh authorUid được lưu trữ với auth.uid (mã nhận dạng người dùng) được truyền trong mã thông báo xác thực.

Tạo

Phương pháp uỷ quyền này bắt đầu bằng cách thêm auth.uid từ mã thông báo uỷ quyền vào mỗi Post mới dưới dạng trường authorUid để cho phép so sánh trong các kiểm thử uỷ quyền theo trình tự phụ.

# 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
  })
}
Cập nhật

Khi một ứng dụng khách tìm cách cập nhật Post, bạn có thể kiểm thử auth.uid đã truyền với authorUid đã lưu trữ.

# 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"
    }
  )
}
Xóa

Kỹ thuật tương tự cũng được dùng để cho phép các thao tác xoá.

# 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 }
}
Danh sách
# 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
  }
}

Lọc dữ liệu

Hệ thống uỷ quyền của Data Connect cho phép bạn ghi các bộ lọc tinh vi kết hợp với các cấp truy cập đặt trước như PUBLIC cũng như bằng cách sử dụng dữ liệu từ mã thông báo xác thực.

Hệ thống uỷ quyền cũng cho phép bạn chỉ sử dụng biểu thức mà không cần cấp truy cập cơ sở, như trong một số ví dụ sau.

Lọc theo thuộc tính tài nguyên

Ở đây, việc uỷ quyền không dựa trên mã thông báo xác thực vì cấp độ bảo mật cơ sở được đặt thành PUBLIC. Tuy nhiên, chúng ta có thể thiết lập rõ ràng các bản ghi trong cơ sở dữ liệu là phù hợp để truy cập công khai; giả sử chúng ta có Post bản ghi trong cơ sở dữ liệu với visibility được đặt thành "công khai".

# 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
  }
}
Lọc theo thông báo xác nhận quyền sở hữu của người dùng

Ở đây, giả sử bạn đã thiết lập các tuyên bố tuỳ chỉnh của người dùng truyền vào mã xác thực để xác định người dùng trong gói "pro" cho ứng dụng của bạn, được gắn cờ bằng trường auth.token.plan trong mã xác thực. Biểu thức của bạn có thể kiểm tra theo trường này.

# 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
  }
}
Lọc theo thứ tự + giới hạn

Hoặc bạn có thể đã đặt visibility trong các bản ghi Post để xác định rằng đó là nội dung dành cho người dùng "pro", nhưng đối với bản xem trước hoặc trang thông tin giới thiệu về dữ liệu, hãy giới hạn thêm số lượng bản ghi được trả về.

# 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
  }
}
Lọc theo vai trò

Nếu thông báo xác nhận quyền sở hữu tuỳ chỉnh xác định vai trò admin, bạn có thể kiểm thử và uỷ quyền cho các thao tác tương ứng.

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

Tìm hiểu về lệnh @check@redact

Lệnh @check xác minh rằng các trường đã chỉ định có trong kết quả truy vấn. Biểu thức Ngôn ngữ diễn đạt thông thường (CEL) được dùng để kiểm thử giá trị trường. Hành vi mặc định của lệnh này là kiểm tra và từ chối các nút có giá trị null.

Chỉ thị @redact sẽ loại bỏ một phần phản hồi từ ứng dụng. Các trường đã bị loại bỏ vẫn được đánh giá để xem có hiệu ứng phụ nào không (bao gồm cả thay đổi về dữ liệu và @check) và kết quả vẫn có sẵn cho các bước sau trong biểu thức CEL.

Trong Data Connect, các lệnh @check@redact thường được sử dụng trong ngữ cảnh kiểm tra uỷ quyền; hãy tham khảo phần thảo luận về việc tra cứu dữ liệu uỷ quyền.

Thêm các lệnh @check@redact để tra cứu dữ liệu uỷ quyền

Một trường hợp sử dụng phổ biến của việc uỷ quyền là lưu trữ các vai trò uỷ quyền tuỳ chỉnh trong cơ sở dữ liệu, chẳng hạn như trong bảng quyền đặc biệt và sử dụng các vai trò đó để uỷ quyền cho các đột biến tạo, cập nhật hoặc xoá dữ liệu.

Khi sử dụng tính năng tra cứu dữ liệu uỷ quyền, bạn có thể truy vấn các vai trò dựa trên mã nhận dạng người dùng và sử dụng biểu thức CEL để quyết định xem liệu hành vi thay đổi có được uỷ quyền hay không. Ví dụ: bạn có thể muốn viết một đột biến UpdateMovieTitle cho phép ứng dụng được uỷ quyền cập nhật tên phim.

Trong phần còn lại của cuộc thảo luận này, giả sử cơ sở dữ liệu ứng dụng đánh giá phim lưu trữ một vai trò uỷ quyền trong bảng MoviePermission.

# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["doc", "userId"]) {
  movie: Movie! # implies another field: movieId: UUID!
  userId: String! # Can also be a reference to a User table, doesn't matter
  role: String!
}

Trong ví dụ triển khai sau, đột biến UpdateMovieTitle bao gồm một trường query để truy xuất dữ liệu từ MoviePermission và các lệnh sau để đảm bảo thao tác an toàn và mạnh mẽ:

  • Lệnh @transaction để đảm bảo tất cả các truy vấn và kiểm tra uỷ quyền đều hoàn tất hoặc không thành công một cách nguyên tử.
  • Chỉ thị @redact để bỏ qua kết quả truy vấn khỏi phản hồi, nghĩa là quy trình kiểm tra uỷ quyền của chúng tôi được thực hiện trên máy chủ Data Connect nhưng dữ liệu nhạy cảm không được hiển thị cho ứng dụng.
  • Một cặp lệnh @check để đánh giá logic uỷ quyền trên kết quả truy vấn, chẳng hạn như kiểm thử để đảm bảo rằng một mã nhận dạng người dùng nhất định có vai trò thích hợp để thực hiện các sửa đổi.

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
  })
}

Các mẫu đối lập cần tránh trong quá trình uỷ quyền

Phần trước đề cập đến các mẫu cần tuân theo khi sử dụng lệnh @auth.

Bạn cũng nên lưu ý đến các mẫu đối lập quan trọng cần tránh.

Tránh truyền mã nhận dạng thuộc tính người dùng và tham số mã xác thực trong đối số truy vấn và đột biến

Firebase Authentication là một công cụ mạnh mẽ để trình bày luồng xác thực và ghi lại dữ liệu xác thực một cách an toàn, chẳng hạn như mã nhận dạng người dùng đã đăng ký và nhiều trường được lưu trữ trong mã xác thực.

Bạn không nên truyền mã nhận dạng người dùng và dữ liệu mã thông báo xác thực trong các đối số truy vấn và đột biến.

# 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
  }
}

Tránh sử dụng cấp truy cập USER mà không có bộ lọc nào

Như đã thảo luận nhiều lần trong hướng dẫn, các cấp truy cập cốt lõi như USER, USER_ANON, USER_EMAIL_VERIFIED là đường cơ sở và điểm xuất phát để kiểm tra quyền, được nâng cao bằng các bộ lọc và biểu thức. Việc sử dụng các cấp này mà không có bộ lọc hoặc biểu thức tương ứng để kiểm tra người dùng nào đang thực hiện yêu cầu về cơ bản tương đương với việc sử dụng cấp PUBLIC.

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

Tránh sử dụng cấp truy cập PUBLIC hoặc USER để tạo bản mô hình

Để đẩy nhanh quá trình phát triển, bạn có thể đặt tất cả các thao tác thành cấp truy cập PUBLIC hoặc cấp truy cập USER mà không cần cải tiến thêm để uỷ quyền cho tất cả các thao tác và cho phép bạn nhanh chóng kiểm thử mã.

Khi bạn đã tạo bản nguyên mẫu ban đầu theo cách này, hãy bắt đầu chuyển từ NO_ACCESS sang cấp độ uỷ quyền sẵn sàng phát hành với các cấp PUBLICUSER. Tuy nhiên, đừng triển khai các lớp này dưới dạng PUBLIC hoặc USER mà không thêm logic bổ sung như trong hướng dẫn này.

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

Sử dụng Firebase App Check để chứng thực ứng dụng

Xác thực và uỷ quyền là các thành phần quan trọng của tính bảo mật Data Connect. Tính năng xác thực và uỷ quyền kết hợp với tính năng chứng thực ứng dụng tạo nên một giải pháp bảo mật rất mạnh mẽ.

Với tính năng chứng thực thông qua Firebase App Check, các thiết bị chạy ứng dụng của bạn sẽ sử dụng một trình cung cấp chứng thực ứng dụng hoặc thiết bị để chứng thực rằng các hoạt động Data Connect bắt nguồn từ ứng dụng chính thống và các yêu cầu bắt nguồn từ một thiết bị chính thống, không bị can thiệp. Thông tin chứng thực này được đính kèm vào mọi yêu cầu mà ứng dụng của bạn gửi đến Data Connect.

Để tìm hiểu cách bật App Check cho Data Connect và đưa SDK ứng dụng vào ứng dụng, hãy xem thông tin tổng quan về App Check.

Cấp xác thực cho lệnh @auth(level)

Bảng sau đây liệt kê tất cả các cấp truy cập tiêu chuẩn và cấp truy cập tương đương trong CEL. Các cấp xác thực được liệt kê từ rộng đến hẹp – mỗi cấp bao gồm tất cả người dùng khớp với các cấp sau.

Cấp độ Định nghĩa
PUBLIC Bất kỳ ai cũng có thể thực thi thao tác này, có hoặc không có xác thực.

Những điều cần cân nhắc: Mọi người dùng đều có thể đọc hoặc sửa đổi dữ liệu. Firebase đề xuất cấp độ uỷ quyền này cho dữ liệu có thể duyệt công khai như trang thông tin sản phẩm hoặc nội dung nghe nhìn. Xem các ví dụ và phương pháp thay thế hay nhất.

Tương đương với @auth(expr: "true")

Không thể sử dụng bộ lọc và biểu thức @auth kết hợp với cấp truy cập này. Mọi biểu thức như vậy sẽ không thành công và báo lỗi yêu cầu xấu 400.
USER_ANON Mọi người dùng đã xác định, bao gồm cả những người đã đăng nhập ẩn danh bằng Firebase Authentication, đều được phép thực hiện truy vấn hoặc thay đổi.

Lưu ý: USER_ANON là tập hợp lớn hơn của USER.

Những điều cần cân nhắc: Xin lưu ý rằng bạn phải thiết kế cẩn thận các truy vấn và đột biến cho cấp độ uỷ quyền này. Cấp này cho phép người dùng đăng nhập một cách ẩn danh (tự động đăng nhập chỉ liên kết với thiết bị của người dùng) bằng Authentication và không tự thực hiện các bước kiểm tra khác, chẳng hạn như liệu dữ liệu có thuộc về người dùng hay không. Xem các ví dụ và phương pháp thay thế hay nhất.

Vì quy trình đăng nhập ẩn danh Authentication phát hành uid, nên cấp USER_ANON tương đương với
@auth(expr: "auth.uid != nil")
USER Mọi người dùng đã đăng nhập bằng Firebase Authentication đều được uỷ quyền để thực hiện truy vấn hoặc thay đổi, ngoại trừ người dùng đăng nhập ẩn danh.

Những điều cần cân nhắc: Xin lưu ý rằng bạn phải thiết kế cẩn thận các truy vấn và đột biến cho cấp độ uỷ quyền này. Cấp này chỉ kiểm tra xem người dùng đã đăng nhập bằng Authentication hay chưa và không tự thực hiện các bước kiểm tra khác, chẳng hạn như liệu dữ liệu có thuộc về người dùng hay không. Xem các ví dụ và phương pháp thay thế hay nhất.

Tương đương với @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED Bất kỳ người dùng nào đã đăng nhập bằng Firebase Authentication với địa chỉ email đã xác minh đều được uỷ quyền thực hiện truy vấn hoặc thay đổi.

Những điều cần cân nhắc: Vì quy trình xác minh email được thực hiện bằng Authentication, nên quy trình này dựa trên phương thức Authentication mạnh mẽ hơn, do đó, cấp độ này mang lại thêm tính bảo mật so với USER hoặc USER_ANON. Cấp này chỉ kiểm tra để đảm bảo rằng người dùng đã đăng nhập bằng Authentication bằng email đã xác minh và không tự thực hiện các bước kiểm tra khác, chẳng hạn như liệu dữ liệu có thuộc về người dùng hay không. Xem các ví dụ và phương pháp thay thế hay nhất.

Tương đương với @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Không thể thực thi thao tác này bên ngoài ngữ cảnh SDK quản trị.

Tương đương với @auth(expr: "false")

Tài liệu tham khảo CEL cho @auth(expr)@check(expr)

Như trong các ví dụ khác trong hướng dẫn này, bạn có thể và nên sử dụng các biểu thức được xác định trong Ngôn ngữ biểu thức chung (CEL) để kiểm soát quyền cho Data Connect bằng các lệnh @auth(expr:)@check.

Phần này trình bày cú pháp CEL liên quan đến việc tạo biểu thức cho các lệnh này.

Thông tin tham khảo đầy đủ về CEL được cung cấp trong quy cách CEL.

Kiểm thử các biến được truyền trong truy vấn và đột biến

Cú pháp @auth(expr) cho phép bạn truy cập và kiểm thử các biến từ truy vấn và sự thay đổi.

Ví dụ: bạn có thể đưa một biến toán tử, chẳng hạn như $status, vào bằng cách sử dụng vars.status.

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

Dữ liệu có sẵn cho biểu thức

Cả biểu thức CEL @auth(expr:)@check(expr:) đều có thể đánh giá các giá trị sau:

  • request.operationName
  • vars (bí danh của request.variables)
  • auth (bí danh của request.auth)

Ngoài ra, biểu thức @check(expr:) có thể đánh giá:

  • this (giá trị của trường hiện tại)

Đối tượng request.operationName

Đối tượng request.operarationName lưu trữ loại toán tử, truy vấn hoặc đột biến.

Đối tượng vars

Đối tượng vars cho phép biểu thức của bạn truy cập vào tất cả các biến được truyền trong truy vấn hoặc đột biến.

Bạn có thể sử dụng vars.<variablename> trong một biểu thức làm bí danh cho request.variables.<variablename> đủ điều kiện:

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

Đối tượng auth

Authentication xác định những người dùng yêu cầu quyền truy cập vào dữ liệu của bạn và cung cấp thông tin đó dưới dạng một đối tượng mà bạn có thể xây dựng trong biểu thức.

Trong bộ lọc và biểu thức, bạn có thể sử dụng auth làm bí danh cho request.auth.

Đối tượng auth chứa các thông tin sau:

  • uid: Mã nhận dạng người dùng duy nhất, được chỉ định cho người dùng yêu cầu.
  • token: Bản đồ các giá trị do Authentication thu thập.

Để biết thêm thông tin chi tiết về nội dung của auth.token, hãy xem phần Dữ liệu trong mã thông báo xác thực

Liên kết this

this liên kết sẽ đánh giá thành trường mà lệnh @check được đính kèm. Trong trường hợp cơ bản, bạn có thể đánh giá kết quả truy vấn có giá trị đơn.

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
  })
}

Nếu trường được trả về xuất hiện nhiều lần vì bất kỳ thành phần mẹ nào cũng là một danh sách, thì mỗi lần xuất hiện sẽ được kiểm thử bằng this liên kết với từng giá trị.

Đối với bất kỳ đường dẫn nào, nếu một thành phần cấp trên là null hoặc [], thì trường đó sẽ không được truy cập và quá trình đánh giá CEL sẽ bị bỏ qua đối với đường dẫn đó. Nói cách khác, quá trình đánh giá chỉ diễn ra khi thisnull hoặc không phải null, nhưng không bao giờ là undefined.

Khi chính trường là một danh sách hoặc đối tượng, this sẽ tuân theo cùng một cấu trúc (bao gồm tất cả các phần tử con được chọn trong trường hợp đối tượng), như minh hoạ trong ví dụ sau.

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
  })
}

Cú pháp biểu thức phức tạp

Bạn có thể viết biểu thức phức tạp hơn bằng cách kết hợp với toán tử &&||.

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

Phần sau đây mô tả tất cả toán tử có sẵn.

Toán tử và thứ tự ưu tiên của toán tử

Hãy sử dụng bảng sau đây để tham khảo các toán tử và thứ tự ưu tiên tương ứng.

Cho biểu thức tuỳ ý ab, một trường f và một chỉ mục i.

Toán tử Mô tả Tính liên kết
a[i] a() a.f Chỉ mục, lệnh gọi, quyền truy cập vào trường từ trái sang phải
!a -a Phủ định một ngôi phải sang trái
a/b a%b a*b Toán tử nhân từ trái sang phải
a+b a-b Toán tử cộng từ trái sang phải
a>b a>=b a<b a<=b Toán tử quy chiếu từ trái sang phải
a in b Sự tồn tại trong danh sách hoặc bản đồ từ trái sang phải
type(a) == t So sánh kiểu, trong đó t có thể là bool, int, float, số, chuỗi, danh sách, bản đồ, dấu thời gian hoặc thời lượng từ trái sang phải
a==b a!=b Toán tử so sánh từ trái sang phải
a && b AND có điều kiện từ trái sang phải
a || b Toán tử HOẶC có điều kiện từ trái sang phải
a ? true_value : false_value Biểu thức ba ngôi từ trái sang phải

Dữ liệu trong mã thông báo xác thực

Đối tượng auth.token có thể chứa các giá trị sau:

Trường Mô tả
email Địa chỉ email liên kết với tài khoản, nếu có.
email_verified true nếu người dùng đã xác minh rằng họ có quyền truy cập vào địa chỉ email. Một số nhà cung cấp tự động xác minh địa chỉ email mà họ sở hữu.
phone_number Số điện thoại được liên kết với tài khoản (nếu có).
name Tên hiển thị của người dùng, nếu được đặt.
sub UID Firebase của người dùng. Đây là giá trị duy nhất trong một dự án.
firebase.identities Từ điển của tất cả danh tính được liên kết với tài khoản của người dùng này. Các khoá của từ điển có thể là bất kỳ khoá nào sau đây: email, phone, google.com, facebook.com, github.com, twitter.com. Các giá trị của từ điển là các mảng gồm giá trị nhận dạng duy nhất cho từng nhà cung cấp danh tính được liên kết với tài khoản. Ví dụ: auth.token.firebase.identities["google.com"][0] chứa mã nhận dạng người dùng Google đầu tiên được liên kết với tài khoản.
firebase.sign_in_provider Nhà cung cấp dịch vụ đăng nhập dùng để lấy mã thông báo này. Có thể là một trong các chuỗi sau: custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com.
firebase.tenant tenantId được liên kết với tài khoản, nếu có. Ví dụ: tenant2-m6tyz

Các trường bổ sung trong mã thông báo nhận dạng JWT

Bạn cũng có thể truy cập vào các trường auth.token sau:

Xác nhận quyền sở hữu mã thông báo tuỳ chỉnh
alg Thuật toán "RS256"
iss Tổ chức phát hành Địa chỉ email của tài khoản dịch vụ của dự án
sub Chủ đề Địa chỉ email của tài khoản dịch vụ của dự án
aud Đối tượng "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Thời gian phát hành Thời gian hiện tại, tính bằng giây kể từ thời gian bắt đầu của hệ thống UNIX
exp Thời gian hết hạn Thời gian (tính bằng giây kể từ thời gian bắt đầu của hệ thống UNIX) mà mã thông báo hết hạn. Thời gian này có thể muộn tối đa 3600 giây so với iat.
Lưu ý: điều này chỉ kiểm soát thời điểm mã thông báo tuỳ chỉnh hết hạn. Tuy nhiên, sau khi bạn đăng nhập cho người dùng bằng signInWithCustomToken(), họ sẽ vẫn duy trì trạng thái đăng nhập vào thiết bị cho đến khi phiên của họ không hợp lệ hoặc người dùng đăng xuất.
<claims> (không bắt buộc) Các thông báo xác nhận quyền sở hữu tuỳ chỉnh không bắt buộc để đưa vào mã thông báo. Bạn có thể truy cập thông báo xác nhận quyền sở hữu này thông qua auth.token (hoặc request.auth.token) trong biểu thức. Ví dụ: nếu tạo một đơn khiếu nại tuỳ chỉnh adminClaim, bạn có thể truy cập vào đơn khiếu nại đó bằng auth.token.adminClaim.

Tiếp theo là gì?