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 tính năng bảo mật phía máy khách mạnh mẽ với:

  • Uỷ quyền cho ứng dụng di động và ứng dụng 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 cách:

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

Cho phép các truy vấn và đột biến 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 phong phú 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 bạn về 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 chỉ thị @auth cho các truy vấn và đột biến, 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 chỉ thị @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 chỉ thị @check để quyết định xem các đột biến kèm theo có được uỷ quyền hay không. Đối với trường hợp uỷ quyền này, chỉ thị @redact cho phép bạn kiểm soát việc kết quả truy vấn có được trả về cho các ứng dụng trong giao thức truyền dữ liệu và truy vấn nhúng bị bỏ qua trong các SDK đã tạo hay không. Tìm giới thiệu về các chỉ thị này, kèm theo ví dụ.

Tìm hiểu chỉ thị @auth

Bạn có thể tham số hoá chỉ thị @auth để tuân theo một trong số các cấp truy cập đặt sẵn nhằm bao gồm nhiều trường hợp truy cập phổ biến. Các cấp độ này có phạm vi từ PUBLIC (cho phép truy vấn và đột biến từ tất cả các máy khách mà không cần xác thực dưới bất kỳ hình thức nào) đến NO_ACCESS (không cho phép truy vấn và đột biến bên ngoài các môi trường máy chủ đặc quyền bằng Firebase Admin SDK). Mỗi cấp độ này 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ó hoặc không có thông tin xác thực đều có thể thực hiện thao tác này.
PUBLIC Bất kỳ ai có hoặc không có thông tin xác thực đều có thể thực hiện thao tác này.
USER_ANON Mọi người dùng được xác định, kể 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 đột biến.
USER Mọi người dùng đã đăng nhập bằng Firebase Authentication đều được phép thực hiện truy vấn hoặc đột biến, 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 có địa chỉ email đã xác minh đều được phép thực hiện truy vấn hoặc đột biến.
NO_ACCESS Bạn không thể thực hiện thao tác này bên ngoài ngữ cảnh Admin SDK.

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

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

Các cấp truy cập đặt sẵn là điểm bắt đầu cho việc 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 hoàn toàn an toàn sẽ dựa trên cấp USER, cộng với các bộ lọc và biểu thức kiểm tra 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 tiê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 tài liệu 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 phù hợp để bắt đầu. Một lần nữa, cấp độ truy cập luôn là điểm bắt đầu và bạn cần có thêm các bộ lọc và biểu thức để tăng cường bảo mật.

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

Ví dụ có tính khích lệ

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

Nền tảng như vậy có khả năng sẽ 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 đề xuất bạ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 uỷ quyền sẽ được đọc và so sánh bằng cách sử dụng các biểu thức. Mẫu điển hình là sử dụng các biểu thức như where: {authorUid: {eq_expr: "auth.uid"}} để so sánh một authorUid đã 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 một trường authorUid để cho phép so sánh trong các kiểm thử uỷ quyền tiếp theo.

# 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 cố gắng cập nhật Post, bạn có thể kiểm thử auth.uid đã truyền đối 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

Bạn có thể sử dụng cùng một kỹ thuật để uỷ quyền cho 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 viết các bộ lọc phức tạp kết hợp với các cấp truy cập đặt sẵn như PUBLIC cũng như bằng cách sử dụng dữ liệu từ mã thông báo uỷ quyền.

Hệ thống uỷ quyền cũng cho phép bạn chỉ sử dụng các biểu thức mà không cần cấp độ truy cập cơ bản, 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 uỷ quyền vì cấp độ bảo mật cơ bản được đặt thành PUBLIC. Tuy nhiên, chúng ta có thể đặt các bản ghi trong cơ sở dữ liệu một cách rõ ràng là phù hợp cho quyền 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

Giả sử bạn đã thiết lập các khai báo tuỳ chỉnh về người dùng truyền trong mã thông báo uỷ quyền để xác định người dùng trong gói "chuyên nghiệp" cho ứng dụng của bạn, được gắn cờ bằng trường auth.token.plan trong mã thông báo uỷ quyền. Biểu thức của bạn có thể kiểm tra 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 một lần nữa, 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 "chuyên nghiệp", nhưng đối với bản xem trước hoặc danh sách dữ liệu giới thiệ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 khai báo tuỳ chỉnh của bạn xác định một vai trò admin, bạn có thể kiểm thử và uỷ quyền các thao tác cho phù hợp.

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

Thêm chỉ thị @check@redact để tra cứu dữ liệu uỷ quyền

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

Khi sử dụng các lệnh 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ột mã nhận dạng người dùng và sử dụng các biểu thức CEL để quyết định xem thao tác biến đổ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 một ứng dụng khách được uỷ quyền cập nhật tiêu đề phim.

Trong phần còn lại của cuộc thảo luận này, hãy giả sử cơ sở dữ liệu của ứ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: ["movie", "user"]) {
  movie: Movie! # implies another field: movieId: UUID!
  user: User!
  role: String!
}

Sử dụng trong đột biến

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

  • Chỉ thị @transaction để đảm bảo tất cả các truy vấn và quy trình kiểm tra uỷ quyền đều được hoàn tất hoặc không thành công một cách tự động.
  • Chỉ thị @redact để bỏ qua kết quả truy vấn trong phản hồi, nghĩa là chế độ 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 máy khách.
  • Một cặp chỉ thị @check để đánh giá logic uỷ quyền trên kết quả truy vấn, chẳng hạn như kiểm thử rằng một userID nhất định có vai trò phù 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
  })
}

Sử dụng trong truy vấn

Việc tra cứu dữ liệu uỷ quyền cũng hữu ích để hạn chế các truy vấn dựa trên vai trò hoặc các hạn chế khác.

Trong ví dụ sau đây (cũng sử dụng giản đồ MoviePermission), truy vấn sẽ kiểm tra xem người yêu cầu có vai trò "quản trị viên" phù hợp để xem những người dùng có thể chỉnh sửa một bộ phim hay không.

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

Các mẫu chống đối 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 chỉ thị @auth.

Bạn cũng nên biết những phản mẫu 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à các tham số mã thông báo uỷ quyền trong các đối số truy vấn và đột biến

Firebase Authentication là một công cụ mạnh mẽ để trình bày các luồng xác thực và thu thập 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ã thông báo 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 uỷ quyền 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 bắt đầu cho các quy trình kiểm tra uỷ quyền, cần được tăng cường 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 xem 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 mẫu

Để tăng tốc quá trình phát triển, bạn có thể muốn đặ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 tất cả các thao tác và cho phép bạn kiểm thử mã một cách nhanh chóng.

Khi bạn đã thực hiện việc tạo mẫu ban đầu theo cách này, hãy bắt đầu chuyển từ NO_ACCESS sang uỷ quyền sẵn sàng cho sản xuất với các cấp PUBLICUSER. Tuy nhiên, đừng triển khai chúng 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,
  )
}

Tránh dựa vào địa chỉ email chưa được xác minh để uỷ quyền

Cấp quyền truy cập cho người dùng trên một miền cụ thể là một cách hiệu quả để hạn chế quyền truy cập. Tuy nhiên, bất kỳ ai cũng có thể khai báo rằng họ sở hữu một email trong quá trình đăng nhập. Đảm bảo rằng bạn chỉ cấp quyền truy cập cho những địa chỉ email đã được xác minh thông qua Xác thực 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
  })
}

Ngoài ra, hãy kiểm tra auth.token.email_verified

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_verified && 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
  })
}

Kiểm tra uỷ quyền bằng Giao diện dòng lệnh (CLI) của Firebase

Như đã đề cập trước đó, các cấp truy cập đặt sẵn như PUBLICUSER là điểm bắt đầu cho việc uỷ quyền mạnh mẽ và nên được dùng cùng với các chế độ kiểm tra uỷ quyền dựa trên biểu thức và bộ lọc bổ sung. Bạn không nên sử dụng các API này một cách độc lập mà không cân nhắc kỹ lưỡng trường hợp sử dụng.

Data Connect giúp bạn kiểm tra chiến lược uỷ quyền bằng cách phân tích mã trình kết nối khi bạn triển khai lên máy chủ bằng firebase deploy từ CLI Firebase. Bạn có thể sử dụng quy trình kiểm tra này để xem xét cơ sở mã của mình.

Khi bạn triển khai các trình kết nối, CLI sẽ đưa ra các đánh giá về mã thao tác hiện có, đã sửa đổi và mới trong trình kết nối của bạn.

Đối với các thao tác đã sửa đổi và thao tác mới, CLI sẽ đưa ra cảnh báo và nhắc bạn xác nhận khi bạn sử dụng một số cấp truy cập nhất định trong các thao tác mới hoặc khi bạn sửa đổi các thao tác hiện có để sử dụng các cấp truy cập đó.

Cảnh báo và lời nhắc luôn xuất hiện trong các trường hợp sau:

  • PUBLIC

Ngoài ra, cảnh báo và lời nhắc sẽ xuất hiện ở các cấp truy cập sau đây khi bạn không tăng cường các cấp truy cập này bằng bộ lọc thông qua auth.uid:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

Chặn cảnh báo về thao tác không an toàn bằng đối số @auth(insecureReason:)

Trong nhiều trường hợp, bạn sẽ kết luận rằng việc sử dụng các cấp truy cập PUBLICUSER* là hoàn toàn phù hợp.

Khi trình kết nối của bạn chứa nhiều thao tác, bạn có thể muốn đầu ra kiểm tra bảo mật rõ ràng và phù hợp hơn, loại bỏ những thao tác thường kích hoạt cảnh báo, nhưng bạn biết rằng mình có cấp truy cập chính xác.

Bạn có thể chặn cảnh báo cho các thao tác như vậy bằng @auth(insecureReason:). Ví dụ:

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

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 bảo mật Data Connect. Xác thực và uỷ quyền kết hợp với 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 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 thao tác Data Connect bắt nguồn từ ứng dụng chính thống của bạn 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. 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 của App Check vào ứng dụng, hãy xem phần App Checktổng quan.

Các cấp xác thực cho chỉ thị @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ác cấp truy cập tương đương trong CEL. Các cấp xác thực được liệt kê từ phạm vi rộng đến phạm vi hẹp – mỗi cấp bao gồm tất cả người dùng phù hợp với các cấp sau.

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

Lưu ý: 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, chẳng hạn 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 hay nhất và các lựa chọn thay thế.

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

Không thể kết hợp các bộ lọc và biểu thức @auth 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 400 yêu cầu không hợp lệ.
USER_ANON Mọi người dùng được xác định, kể 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 đột biến.

Lưu ý: USER_ANON là một siêu tập hợp của USER.

Lưu ý: 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 ẩn danh (tính năng 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 hay nhất và các lựa chọn thay thế.

Vì quy trình đăng nhập ẩn danh Authentication sẽ phát hành một 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 phép thực hiện truy vấn hoặc đột biến, ngoại trừ người dùng đăng nhập ẩn danh.

Lưu ý: 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 có đăng nhập bằng Authentication hay không 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 hay nhất và các lựa chọn thay thế.

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 có địa chỉ email đã xác minh đều được phép thực hiện truy vấn hoặc đột biến.

Lưu ý: 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 khả năng bảo mật bổ sung so với USER hoặc USER_ANON. Cấp này chỉ kiểm tra xem người dùng có đăng nhập bằng Authentication bằng email đã xác minh hay không 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 hay nhất và các lựa chọn thay thế.

Tương đương với @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Bạn không thể thực hiện thao tác này bên ngoài ngữ cảnh Admin SDK.

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

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

Như trong các ví dụ ở những nơi 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 thông thường (CEL) để kiểm soát việc uỷ quyền cho Data Connect bằng cách sử dụng các chỉ thị @auth(expr:)@check.

Phần này trình bày cú pháp CEL có liên quan đến việc tạo biểu thức cho các chỉ thị 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à đột biến.

Ví dụ: bạn có thể thêm một biến thao tác, chẳng hạn như $status, 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 các biểu thức: request, response, this

Bạn sử dụng dữ liệu cho:

  • Đánh giá bằng các biểu thức CEL trong chỉ thị @auth(expr:)@check(expr:)
  • Chỉ định bằng cách sử dụng biểu thức máy chủ, <field>_expr.

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

  • request.operationName
  • vars (bí danh cho request.variables)
  • auth (bí danh cho request.auth)

Trong các đột biến, bạn có thể truy cập và chỉ định nội dung của:

  • response (để kiểm tra kết quả một phần trong logic nhiều bước)

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

  • this (giá trị của trường hiện tại)
  • response (để kiểm tra kết quả một phần trong logic nhiều bước)

Liên kết request.operationName

Liên kết request.operarationName lưu trữ loại thao tác, có thể là truy vấn hoặc đột biến.

Liên kết vars (request.vars)

Liên kết vars cho phép các 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ể 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'")

Liên kết auth (request.auth)

Authentication xác định những người dùng yêu cầu 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 liên kết mà bạn có thể tạo trong các biểu thức.

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

Liên kết uỷ quyền chứa những 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 về nội dung của auth.token, hãy xem phần Dữ liệu trong mã thông báo uỷ quyền

Liên kết response

Liên kết response chứa dữ liệu mà máy chủ đang tập hợp để phản hồi một truy vấn hoặc đột biến khi dữ liệu đó đang được tập hợp.

Khi thao tác diễn ra, khi mỗi bước hoàn tất thành công, response sẽ chứa dữ liệu phản hồi từ các bước đã hoàn tất thành công.

Liên kết response được cấu trúc theo hình dạng của thao tác được liên kết, bao gồm (nhiều) trường lồng nhau và (nếu có) các truy vấn được nhúng.

Xin lưu ý rằng khi bạn truy cập vào dữ liệu phản hồi truy vấn được nhúng, các trường có thể chứa mọi loại dữ liệu, tuỳ thuộc vào dữ liệu được yêu cầu trong truy vấn được nhúng; khi bạn truy cập vào dữ liệu do các trường đột biến như _insert_delete trả về, các trường này có thể chứa khoá UUID, số lượt xoá, giá trị rỗng (xem tài liệu tham khảo về đột biến).

Ví dụ:

  • Trong một đột biến chứa truy vấn được nhúng, liên kết response chứa dữ liệu tra cứu tại response.query.<fieldName>.<fieldName>...., trong trường hợp này là 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
    }
  }
}
  • Trong một đột biến nhiều bước, chẳng hạn như có nhiều trường _insert, liên kết response chứa dữ liệu một phần tại response.<fieldName>.<fieldName>...., trong trường hợp này là 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.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

Liên kết this

Liên kết this đánh giá trường mà chỉ thị @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ó một giá trị.

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ỳ phần tử nào là một danh sách, thì mỗi lần xuất hiện sẽ được kiểm thử bằng this được 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 phần tử 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 này 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 cả tất cả các phần tử con được chọn trong trường hợp là đố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 các biểu thức phức tạp hơn bằng cách kết hợp với các toán tử &&||.

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

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

Toán tử và mức độ ưu tiên của toán tử

Hãy tham khảo bảng sau đây để biết các toán tử và mức độ ưu tiên tương ứng của chúng.

Cho trước các 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 kết hợp
a[i] a() a.f Chỉ mục, cuộc gọi, quyền truy cập vào trường từ trái sang phải
!a -a Phép 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ử quan hệ 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, number, string, list, map, timestamp hoặc duration từ trái sang phải
a==b a!=b Toán tử so sánh từ trái sang phải
a && b Điều kiện VÀ từ trái sang phải
a || b Điều kiện HOẶC từ trái sang phải
a ? true_value : false_value Biểu thức bậc ba 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 được 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. Giá trị này là duy nhất trong một dự án.
firebase.identities Từ điển chứ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. 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à mảng gồm các giá trị nhận dạng duy nhất cho mỗi nhà cung cấp dịch vụ 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 được 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 đây:

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 tài khoản dịch vụ của dự án
sub Chủ đề Địa chỉ email 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 hơn tối đa 3.600 giây so với iat.
Lưu ý: chế độ này chỉ kiểm soát thời gian hết hạn của chính mã thông báo tuỳ chỉnh. Nhưng sau khi bạn đăng nhập cho người dùng bằng signInWithCustomToken(), họ sẽ vẫn đă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 tin xác nhận tuỳ chỉnh không bắt buộc để đưa vào mã thông báo, có thể truy cập thông qua auth.token (hoặc request.auth.token) trong các biểu thức. Ví dụ: nếu tạo một câu lệnh tuỳ chỉnh adminClaim, bạn có thể truy cập vào câu lệnh đó bằng auth.token.adminClaim.

Tiếp theo là gì?