เชื่อมต่อข้อมูลอย่างปลอดภัยด้วยการให้สิทธิ์และการรับรอง

Firebase Data Connect มีการรักษาความปลอดภัยฝั่งไคลเอ็นต์ที่มีประสิทธิภาพด้วยสิ่งต่อไปนี้

  • การให้สิทธิ์ไคลเอ็นต์บนอุปกรณ์เคลื่อนที่และเว็บ
  • การควบคุมการให้สิทธิ์ระดับการค้นหาและระดับการดัดแปลงแต่ละรายการ
  • การรับรองแอปด้วย Firebase App Check

Data Connect ขยายความปลอดภัยนี้ด้วยสิ่งต่อไปนี้

  • การให้สิทธิ์ฝั่งเซิร์ฟเวอร์
  • ความปลอดภัยของผู้ใช้โปรเจ็กต์ Firebase และ Cloud SQL ด้วย IAM

ให้สิทธิ์การค้นหาและการดัดแปลงไคลเอ็นต์

Data Connect ผสานรวมกับ Firebase Authentication อย่างเต็มรูปแบบ คุณจึงใช้ข้อมูลที่เป็นริชมีเดียเกี่ยวกับผู้ใช้ที่เข้าถึงข้อมูลของคุณ (การตรวจสอบสิทธิ์) ในการออกแบบเพื่อกำหนดข้อมูลที่จะให้ผู้ใช้เหล่านั้นเข้าถึงได้ (การให้สิทธิ์)

Data Connect มีคำสั่ง @auth สําหรับการค้นหาและการดัดแปลง ซึ่งช่วยให้คุณกําหนดระดับการตรวจสอบสิทธิ์ที่จําเป็นในการให้สิทธิ์การดําเนินการได้ คู่มือนี้จะแนะนำคำสั่ง @auth พร้อมตัวอย่าง

นอกจากนี้ Data Connect ยังรองรับการดำเนินการค้นหาที่ฝังอยู่ในมิวเทชัน เพื่อให้คุณเรียกข้อมูลเกณฑ์การให้สิทธิ์เพิ่มเติมที่เก็บไว้ในฐานข้อมูลได้ และใช้เกณฑ์เหล่านั้นในคำสั่ง @check เพื่อตัดสินใจว่ามิวเทชันที่รวมอยู่นั้นได้รับอนุญาตหรือไม่ ในกรณีการให้สิทธิ์นี้ คำสั่ง @redact ช่วยให้คุณควบคุมได้ว่าจะให้ระบบแสดงผลลัพธ์การค้นหาแก่ไคลเอ็นต์ใน Wire Protocol และละเว้นการค้นหาที่ฝังไว้ใน 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 และนิพจน์ Common Expression Language (CEL) ที่ประเมินในเซิร์ฟเวอร์

ใช้คําแนะนํา @auth เพื่อใช้สถานการณ์การให้สิทธิ์ทั่วไป

ระดับการเข้าถึงที่กำหนดไว้ล่วงหน้าเป็นจุดเริ่มต้นสำหรับการให้สิทธิ์

ระดับการเข้าถึง USER เป็นระดับพื้นฐานที่มีประโยชน์มากที่สุดสำหรับการเริ่มต้น

การเข้าถึงที่ปลอดภัยอย่างสมบูรณ์จะอิงตามระดับ USER รวมถึงตัวกรองและนิพจน์ที่ตรวจสอบแอตทริบิวต์ผู้ใช้ แอตทริบิวต์ทรัพยากร บทบาท และการตรวจสอบอื่นๆ ระดับ USER_ANON และ USER_EMAIL_VERIFIED เป็นรูปแบบต่างๆ ของเคส USER

ไวยากรณ์นิพจน์ช่วยให้คุณประเมินข้อมูลได้โดยใช้ออบเจ็กต์ auth ที่แสดงข้อมูลการตรวจสอบสิทธิ์ที่ส่งพร้อมกับการดำเนินการ ทั้งข้อมูลมาตรฐานในโทเค็นการตรวจสอบสิทธิ์และข้อมูลที่กำหนดเองในโทเค็น ดูรายการช่องที่มีอยู่ในออบเจ็กต์ auth ได้ในส่วนส่วนอ้างอิง

แน่นอนว่ามีกรณีการใช้งานที่ PUBLIC เป็นระดับการเข้าถึงที่ถูกต้องสำหรับการเริ่มต้น โปรดทราบว่าระดับการเข้าถึงเป็นจุดเริ่มต้นเสมอ และต้องใช้ตัวกรองและนิพจน์เพิ่มเติมเพื่อรักษาความปลอดภัยที่เข้มงวด

ตอนนี้คู่มือนี้แสดงตัวอย่างวิธีสร้างใน USER และ PUBLIC

ตัวอย่างที่สร้างแรงจูงใจ

ตัวอย่างแนวทางปฏิบัติแนะนำต่อไปนี้ใช้กับสคีมาต่อไปนี้สำหรับแพลตฟอร์มการเขียนบล็อกที่มีเนื้อหาบางอย่างล็อกไว้หลังแพ็กเกจการชำระเงิน

แพลตฟอร์มดังกล่าวมีแนวโน้มที่จะจำลอง Users และPosts

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 (รหัสผู้ใช้) ที่ส่งในโทเค็นการตรวจสอบสิทธิ์

สร้าง

แนวทางปฏิบัติเกี่ยวกับการให้สิทธิ์นี้เริ่มต้นด้วยการเพิ่ม 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 คุณสามารถทดสอบ auth.uid ที่ส่งเทียบกับ authorUid ที่เก็บไว้

# 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 เป็น "สาธารณะ"

# 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
  }
}
กรองตามการอ้างสิทธิ์ของผู้ใช้

ในที่นี้ สมมติว่าคุณได้ตั้งค่าการอ้างสิทธิ์ของผู้ใช้ที่กําหนดเองซึ่งส่งโทเค็นการตรวจสอบสิทธิ์เพื่อระบุผู้ใช้ในแพ็กเกจ "Pro" สําหรับแอป โดยทําเครื่องหมายด้วยช่อง 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
  }
}
กรองตามลําดับ + จํากัด

หรือคุณอาจตั้งค่า visibility ในระเบียน Post เพื่อระบุว่าเป็นเนื้อหาที่พร้อมให้บริการสำหรับผู้ใช้ "Pro" แต่สำหรับข้อมูลตัวอย่างหรือข้อมูลพรีวิว ให้จำกัดจำนวนระเบียนที่แสดงผลเพิ่มเติม

# 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

คำสั่ง @check จะยืนยันว่าช่องที่ระบุมีอยู่ในผลการค้นหา นิพจน์ Common Expression Language (CEL) ใช้เพื่อทดสอบค่าฟิลด์ ลักษณะการทํางานเริ่มต้นของคําสั่งคือตรวจสอบและปฏิเสธโหนดที่มีค่า null

คำสั่ง @redact จะปกปิดข้อมูลบางส่วนในการตอบกลับจากไคลเอ็นต์ ระบบจะยังคงประเมินช่องที่ถูกปกปิดเพื่อหาผลข้างเคียง (รวมถึงการเปลี่ยนแปลงข้อมูลและ @check) และผลลัพธ์จะยังคงใช้ได้กับขั้นตอนต่อๆ ไปในนิพจน์ CEL

ใน Data Connect คำสั่ง @check และ @redact มักใช้ในบริบทของการตรวจสอบการให้สิทธิ์ โปรดดูการอภิปรายเกี่ยวกับการค้นหาข้อมูลการให้สิทธิ์

เพิ่มคําแนะนํา @check และ @redact เพื่อค้นหาข้อมูลการให้สิทธิ์

Use Case การให้สิทธิ์ที่พบบ่อยเกี่ยวข้องกับการจัดเก็บบทบาทการให้สิทธิ์ที่กําหนดเองในฐานข้อมูล เช่น ในตารางสิทธิ์พิเศษ และใช้บทบาทเหล่านั้นเพื่ออนุญาตให้ทําการเปลี่ยนแปลงเพื่อสร้าง อัปเดต หรือลบข้อมูล

เมื่อใช้การค้นหาข้อมูลการให้สิทธิ์ คุณจะค้นหาบทบาทตามรหัสผู้ใช้ได้และใช้นิพจน์ CEL เพื่อตัดสินใจว่ามีการอนุญาตให้ทำการเปลี่ยนแปลงหรือไม่ เช่น คุณอาจต้องเขียน UpdateMovieTitle mutation ที่อนุญาตให้ไคลเอ็นต์ที่ได้รับอนุญาตอัปเดตชื่อภาพยนตร์

ในส่วนที่เหลือของการสนทนานี้ เราจะสมมติว่าฐานข้อมูลแอปรีวิวภาพยนตร์จัดเก็บบทบาทการให้สิทธิ์ไว้ในตาราง 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!
}

ในตัวอย่างการใช้งานต่อไปนี้ UpdateMovieTitle mutation มีฟิลด์ query เพื่อดึงข้อมูลจาก MoviePermission และคำสั่งต่อไปนี้เพื่อให้การดำเนินการปลอดภัยและมีประสิทธิภาพ

  • คำสั่ง @transaction เพื่อให้การค้นหาและการตรวจสอบการให้สิทธิ์ทั้งหมดเสร็จสมบูรณ์หรือดำเนินการไม่สำเร็จพร้อมกัน
  • คำสั่ง @redact เพื่อละเว้นผลการค้นหาจากการตอบกลับ ซึ่งหมายความว่าการตรวจสอบการให้สิทธิ์จะดำเนินการในเซิร์ฟเวอร์ Data Connect แต่จะไม่แสดงข้อมูลที่ละเอียดอ่อนต่อไคลเอ็นต์
  • คำสั่ง @check 2 รายการเพื่อประเมินตรรกะการให้สิทธิ์ในผลการค้นหา เช่น การทดสอบว่ารหัสผู้ใช้หนึ่งๆ มีบทบาทที่เหมาะสมในการทําการแก้ไข

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

รูปแบบที่ไม่พึงประสงค์ที่ควรหลีกเลี่ยงในการให้สิทธิ์

ส่วนก่อนหน้านี้กล่าวถึงรูปแบบที่ควรทำตามเมื่อใช้คำสั่ง @auth

นอกจากนี้ คุณควรระวังรูปแบบที่ไม่พึงประสงค์ที่สำคัญซึ่งควรหลีกเลี่ยงด้วย

หลีกเลี่ยงการส่งรหัสแอตทริบิวต์ผู้ใช้และพารามิเตอร์โทเค็นการตรวจสอบสิทธิ์ในอาร์กิวเมนต์การค้นหาและมิวเทชัน

Firebase Authentication เป็นเครื่องมือที่มีประสิทธิภาพในการนำเสนอขั้นตอนการตรวจสอบสิทธิ์และบันทึกข้อมูลการตรวจสอบสิทธิ์อย่างปลอดภัย เช่น รหัสผู้ใช้ที่ลงทะเบียนและช่องต่างๆ จำนวนมากที่จัดเก็บไว้ในโทเค็นการตรวจสอบสิทธิ์

เราไม่แนะนําให้ส่งรหัสผู้ใช้และข้อมูลโทเค็นการตรวจสอบสิทธิ์ในอาร์กิวเมนต์การค้นหาและอาร์กิวเมนต์การดัดแปลง

# 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 ที่ไม่มีตัวกรองใดๆ

ดังที่ได้อธิบายไว้หลายครั้งในคู่มือ ระดับการเข้าถึงหลัก เช่น USER, USER_ANON, USER_EMAIL_VERIFIED คือพื้นฐานและจุดเริ่มต้นในการตรวจสอบการให้สิทธิ์ ซึ่งจะได้รับการปรับปรุงด้วยตัวกรองและนิพจน์ การใช้ระดับเหล่านี้โดยไม่มีตัวกรองหรือนิพจน์ที่สอดคล้องกันซึ่งจะตรวจสอบว่าผู้ใช้รายใดกำลังดำเนินการตามคำขอนั้นๆ เทียบเท่ากับการใช้ระดับ PUBLIC

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

หลีกเลี่ยงการใช้ระดับการเข้าถึง PUBLIC หรือ USER ในการทำต้นแบบ

หากต้องการเร่งการพัฒนา คุณอาจต้องการตั้งค่าการดำเนินการทั้งหมดเป็นระดับการเข้าถึงPUBLICหรือระดับการเข้าถึงUSERโดยไม่ต้องเพิ่มประสิทธิภาพเพิ่มเติมเพื่ออนุญาตให้ดำเนินการทั้งหมดและให้คุณทดสอบโค้ดได้อย่างรวดเร็ว

เมื่อสร้างต้นแบบขั้นต้นด้วยวิธีนี้แล้ว ให้เริ่มเปลี่ยนจากNO_ACCESSเป็นการให้สิทธิ์ที่พร้อมใช้งานจริงด้วยระดับ PUBLIC และ USER อย่างไรก็ตาม อย่าติดตั้งใช้งานเป็น PUBLIC หรือ USER โดยไม่เพิ่มตรรกะเพิ่มเติมตามที่แสดงในคู่มือนี้

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

ใช้ Firebase App Check สำหรับการรับรองแอป

การตรวจสอบสิทธิ์และการให้สิทธิ์เป็นองค์ประกอบสําคัญของData Connectความปลอดภัย การตรวจสอบสิทธิ์และการให้สิทธิ์ร่วมกับการรับรองแอปเป็นโซลูชันด้านความปลอดภัยที่มีประสิทธิภาพมาก

เมื่อใช้การรับรองผ่าน Firebase App Check อุปกรณ์ที่เรียกใช้แอปของคุณจะใช้ผู้ให้บริการการรับรองแอปหรืออุปกรณ์ที่รับรองว่าการดำเนินการ Data Connect นั้นมาจากแอปที่ถูกต้องและคำขอนั้นมาจากอุปกรณ์ที่ถูกต้องและไม่มีการดัดแปลง การรับรองนี้จะแนบไปกับคําขอทั้งหมดที่แอปส่งไปยัง Data Connect

หากต้องการดูวิธีเปิดใช้ App Check สําหรับ Data Connect และรวม SDK ฝั่งไคลเอ็นต์ไว้ในแอป โปรดดูภาพรวมของ App Check

ระดับการตรวจสอบสิทธิ์สําหรับคําแนะนํา @auth(level)

ตารางต่อไปนี้แสดงระดับการเข้าถึงมาตรฐานทั้งหมดและระดับ CEL ที่เทียบเท่า ระดับการตรวจสอบสิทธิ์จะแสดงจากระดับกว้างไปจนถึงระดับแคบ โดยแต่ละระดับจะรวมผู้ใช้ทั้งหมดที่ตรงกับระดับต่อไปนี้

ระดับ คำจำกัดความ
PUBLIC การดำเนินการนี้สามารถดำเนินการโดยทุกคนที่มีหรือไม่มีการตรวจสอบสิทธิ์ก็ได้

ข้อควรพิจารณา: ผู้ใช้ทุกคนจะอ่านหรือแก้ไขข้อมูลได้ Firebase ขอแนะนําให้ใช้การให้สิทธิ์ระดับนี้กับข้อมูลที่เรียกดูได้แบบสาธารณะ เช่น ข้อมูลผลิตภัณฑ์หรือสื่อ ดูตัวอย่างแนวทางปฏิบัติแนะนำและทางเลือกอื่นๆ

เทียบเท่ากับ @auth(expr: "true")

ใช้ตัวกรองและนิพจน์ @auth ร่วมกับระดับการเข้าถึงนี้ไม่ได้ นิพจน์ดังกล่าวจะดำเนินการไม่สำเร็จโดยมีข้อผิดพลาด 400 Bad Request
USER_ANON ผู้ใช้ที่ระบุตัวตน รวมถึงผู้ใช้ที่เข้าสู่ระบบแบบไม่ระบุตัวตนด้วย Firebase Authentication ได้รับอนุญาตให้ทำการค้นหาหรือการดัดแปลง

หมายเหตุ: USER_ANON เป็นเซตที่รวม USER ไว้ด้วย

ข้อควรพิจารณา: โปรดทราบว่าคุณต้องออกแบบการค้นหาและการดัดแปลงอย่างรอบคอบสำหรับการให้สิทธิ์ระดับนี้ ระดับนี้อนุญาตให้ผู้ใช้เข้าสู่ระบบโดยไม่ระบุตัวตน (การลงชื่อเข้าใช้อัตโนมัติที่เชื่อมโยงกับอุปกรณ์ของผู้ใช้เท่านั้น) ด้วย 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 ที่มีประสิทธิภาพมากขึ้น ระดับนี้จึงมีความปลอดภัยมากกว่า USER หรือ USER_ANON ระดับนี้จะตรวจสอบเฉพาะว่าผู้ใช้เข้าสู่ระบบด้วย Authentication ด้วยอีเมลที่ยืนยันแล้วเท่านั้น และไม่ทำการตรวจสอบอื่นๆ ด้วยตนเอง เช่น ข้อมูลเป็นของผู้ใช้หรือไม่ ดูตัวอย่างแนวทางปฏิบัติแนะนำและทางเลือกอื่นๆ

เทียบเท่ากับ @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS การดำเนินการนี้ไม่สามารถดำเนินการนอกบริบท Admin SDK

เทียบเท่ากับ @auth(expr: "false")

ข้อมูลอ้างอิง CEL สำหรับ @auth(expr) และ @check(expr)

ดังที่แสดงในตัวอย่างอื่นๆ ในคู่มือนี้ คุณสามารถใช้และควรใช้นิพจน์ที่กําหนดไว้ใน Common Expression Language (CEL) เพื่อควบคุมการให้สิทธิ์สําหรับ Data Connect โดยใช้คําสั่ง @auth(expr:) และ @check

ส่วนนี้อธิบายไวยากรณ์ CEL ที่เกี่ยวข้องกับการสร้างนิพจน์สําหรับคําสั่งเหล่านี้

ข้อมูลอ้างอิงทั้งหมดสำหรับ CEL มีอยู่ในข้อกำหนด CEL

ทดสอบตัวแปรที่ส่งในคําค้นหาและมิวเทชัน

ไวยากรณ์ @auth(expr) ช่วยให้คุณเข้าถึงและทดสอบตัวแปรจากการค้นหาและการกลายพันธุ์ได้

เช่น คุณรวมตัวแปรการดำเนินการ เช่น $status ได้โดยใช้ vars.status

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

ข้อมูลที่แสดงในนิพจน์

ทั้งนิพจน์ CEL @auth(expr:) และ @check(expr:) สามารถประเมินข้อมูลต่อไปนี้ได้

  • request.operationName
  • vars (ชื่อแทนของ request.variables)
  • auth (ชื่อแทนของ request.auth)

นอกจากนี้ นิพจน์ @check(expr:) ยังประเมินสิ่งต่อไปนี้ได้

  • this (ค่าของช่องปัจจุบัน)

ออบเจ็กต์ request.operationName

ออบเจ็กต์ request.operarationName จะจัดเก็บประเภทการดำเนินการ ซึ่งอาจเป็นการค้นหาหรือการดัดแปลง

ออบเจ็กต์ 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

Authentication จะระบุผู้ใช้ที่ขอสิทธิ์เข้าถึงข้อมูลของคุณและระบุข้อมูลดังกล่าวเป็นออบเจ็กต์ที่คุณนำไปใช้ต่อในนิพจน์ได้

คุณสามารถใช้ auth เป็นชื่อแทนของ request.auth ในตัวกรองและนิพจน์

ออบเจ็กต์ auth มีข้อมูลต่อไปนี้

  • uid: รหัสผู้ใช้ที่ไม่ซ้ำกันซึ่งกำหนดให้กับผู้ใช้ที่ส่งคำขอ
  • token: แผนที่ค่าที่ Authentication รวบรวม

ดูรายละเอียดเพิ่มเติมเกี่ยวกับเนื้อหาของ auth.token ได้ที่ข้อมูลในโทเค็นการตรวจสอบสิทธิ์

การเชื่อมโยง 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 สำหรับเส้นทางนั้น กล่าวคือ การประเมินจะเกิดขึ้นก็ต่อเมื่อ this เป็น null หรือไม่ใช่ null เท่านั้น แต่จะไม่มีการประเมินเมื่อ this เป็น 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 != null) && (vars.username == 'joe')")

ส่วนต่อไปนี้จะอธิบายโอเปอเรเตอร์ทั้งหมดที่ใช้ได้

โอเปอเรเตอร์และลําดับความสําคัญของโอเปอเรเตอร์

ใช้ตารางต่อไปนี้เป็นข้อมูลอ้างอิงสำหรับโอเปอเรเตอร์และลําดับความสําคัญที่สอดคล้องกัน

พิจารณานิพจน์ a และ b ที่กำหนดเอง ฟิลด์ 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 อาจเป็นบูลีน int ลอย ตัวเลข สตริง รายการ แผนที่ การประทับเวลา หรือระยะเวลา ซ้ายไปขวา
a==b a!=b โอเปอเรเตอร์การเปรียบเทียบ ซ้ายไปขวา
a && b AND แบบมีเงื่อนไข ซ้ายไปขวา
a || b OR แบบมีเงื่อนไข ซ้ายไปขวา
a ? true_value : false_value นิพจน์ Ternary ซ้ายไปขวา

ข้อมูลในโทเค็นการตรวจสอบสิทธิ์

ออบเจ็กต์ auth.token อาจมีค่าต่อไปนี้

ช่อง คำอธิบาย
email อีเมลที่เชื่อมโยงกับบัญชี (หากมี)
email_verified true หากผู้ใช้ยืนยันว่ามีสิทธิ์เข้าถึงอีเมล email ผู้ให้บริการบางรายจะยืนยันอีเมลที่ตนเป็นเจ้าของโดยอัตโนมัติ
phone_number หมายเลขโทรศัพท์ที่เชื่อมโยงกับบัญชี (หากมี)
name ชื่อที่แสดงของผู้ใช้ หากตั้งค่าไว้
sub UID Firebase ของผู้ใช้ ซึ่งต้องไม่ซ้ำกันภายในโปรเจ็กต์
firebase.identities พจนานุกรมของข้อมูลประจำตัวทั้งหมดที่เชื่อมโยงกับบัญชีของผู้ใช้รายนี้ คีย์ของพจนานุกรมอาจเป็น email, phone, google.com, facebook.com, github.com, twitter.com ก็ได้ ค่าของพจนานุกรมคืออาร์เรย์ของตัวระบุที่ไม่ซ้ำกันสำหรับผู้ให้บริการข้อมูลประจำตัวแต่ละรายที่เชื่อมโยงกับบัญชี เช่น auth.token.firebase.identities["google.com"][0] มีรหัสผู้ใช้ Google รายการแรกที่เชื่อมโยงกับบัญชี
firebase.sign_in_provider ผู้ให้บริการการลงชื่อเข้าใช้ที่ใช้รับโทเค็นนี้ โดยอาจเป็นสตริงใดก็ได้ต่อไปนี้ custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com
firebase.tenant tenantId ที่เชื่อมโยงกับบัญชี หากมี เช่น tenant2-m6tyz

ช่องเพิ่มเติมในโทเค็นระบุตัวตน JWT

นอกจากนี้ คุณยังเข้าถึงช่อง auth.token ต่อไปนี้ได้ด้วย

การอ้างสิทธิ์โทเค็นที่กำหนดเอง
alg อัลกอริทึม "RS256"
iss ผู้ออก อีเมลบัญชีบริการของโปรเจ็กต์
sub เรื่อง อีเมลบัญชีบริการของโปรเจ็กต์
aud กลุ่มเป้าหมาย "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat เวลาที่ออก เวลาปัจจุบันเป็นวินาทีนับตั้งแต่ Epoch ของ UNIX
exp เวลาหมดอายุ เวลาเป็นวินาทีนับตั้งแต่ Epoch ของ UNIX ที่โทเค็นจะหมดอายุ โดยช้ากว่า iat ได้สูงสุด 3,600 วินาที
หมายเหตุ: ตัวเลือกนี้จะควบคุมเฉพาะเวลาที่โทเค็นที่กำหนดเองจะหมดอายุเท่านั้น แต่เมื่อคุณลงชื่อเข้าใช้ผู้ใช้โดยใช้ signInWithCustomToken() ผู้ใช้จะยังคงลงชื่อเข้าใช้อุปกรณ์จนกว่าเซสชันจะใช้งานไม่ได้หรือผู้ใช้ออกจากระบบ
<claims> (ไม่บังคับ) การอ้างสิทธิ์ที่กำหนดเองซึ่งไม่บังคับที่จะรวมไว้ในโทเค็น ซึ่งเข้าถึงได้ผ่าน auth.token (หรือ request.auth.token) ในนิพจน์ เช่น หากคุณสร้างการอ้างสิทธิ์ที่กำหนดเอง adminClaim คุณจะเข้าถึงการอ้างสิทธิ์ดังกล่าวได้โดยใช้ auth.token.adminClaim

ขั้นตอนถัดไปคือ