Menghubungkan Data Aman dengan otorisasi dan pengesahan

Firebase Data Connect memberikan keamanan sisi klien yang canggih dengan:

  • Otorisasi klien seluler dan web
  • Kontrol otorisasi tingkat kueri dan mutasi individual
  • Pengesahan aplikasi dengan Firebase App Check.

Data Connect memperluas keamanan ini dengan:

  • Otorisasi sisi server
  • Keamanan pengguna project Firebase dan Cloud SQL dengan IAM.

Memberikan otorisasi kueri dan mutasi klien

Data Connect terintegrasi sepenuhnya dengan Firebase Authentication, sehingga Anda dapat menggunakan data lengkap tentang pengguna yang mengakses data Anda (autentikasi) dalam desain Anda untuk menentukan data yang dapat diakses pengguna tersebut (otorisasi).

Data Connect menyediakan direktif @auth untuk kueri dan mutasi yang memungkinkan Anda menetapkan tingkat autentikasi yang diperlukan untuk mengizinkan operasi. Panduan ini memperkenalkan direktif @auth, dengan contoh.

Selain itu, Data Connect mendukung eksekusi kueri yang disematkan dalam mutasi, sehingga Anda dapat mengambil kriteria otorisasi tambahan yang telah disimpan dalam database, dan menggunakan kriteria tersebut dalam direktif @check untuk memutuskan apakah mutasi yang melampirkan diizinkan. Untuk kasus otorisasi ini, direktif @redact memungkinkan Anda mengontrol apakah hasil kueri ditampilkan ke klien dalam protokol melalui kabel dan kueri tersemat dihilangkan dalam SDK yang dihasilkan. Temukan pengantar untuk arahan ini, beserta contohnya.

Memahami perintah @auth

Anda dapat memparameterkan direktif @auth untuk mengikuti salah satu dari beberapa tingkat akses preset yang mencakup banyak skenario akses umum. Tingkatan ini berkisar dari PUBLIC (yang memungkinkan kueri dan mutasi dari semua klien tanpa autentikasi apa pun) hingga NO_ACCESS (yang tidak mengizinkan kueri dan mutasi di luar lingkungan server dengan hak istimewa menggunakan Firebase Admin SDK). Setiap level ini dikorelasikan dengan alur autentikasi yang disediakan oleh Firebase Authentication.

Tingkat Definisi
PUBLIC Operasi dapat dijalankan oleh siapa saja dengan atau tanpa autentikasi.
PUBLIC Operasi dapat dijalankan oleh siapa saja dengan atau tanpa autentikasi.
USER_ANON Setiap pengguna yang teridentifikasi, termasuk pengguna yang telah login secara anonim dengan Firebase Authentication, diizinkan untuk melakukan kueri atau mutasi.
USER Setiap pengguna yang telah login dengan Firebase Authentication diizinkan untuk melakukan kueri atau mutasi, kecuali pengguna login anonim.
USER_EMAIL_VERIFIED Setiap pengguna yang telah login dengan Firebase Authentication dengan alamat email yang diverifikasi diizinkan untuk melakukan kueri atau mutasi.
NO_ACCESS Operasi ini tidak dapat dijalankan di luar konteks Admin SDK.

Dengan menggunakan tingkat akses preset ini sebagai titik awal, Anda dapat menentukan pemeriksaan otorisasi yang kompleks dan andal dalam direktif @auth menggunakan filter where dan ekspresi Common Expression Language (CEL) yang dievaluasi di server.

Gunakan direktif @auth untuk menerapkan skenario otorisasi umum

Tingkat akses preset adalah titik awal untuk otorisasi.

Tingkat akses USER adalah tingkat dasar yang paling berguna untuk memulai.

Akses yang sepenuhnya aman akan dibangun di tingkat USER ditambah filter dan ekspresi yang memeriksa atribut pengguna, atribut resource, peran, dan pemeriksaan lainnya. Tingkat USER_ANON dan USER_EMAIL_VERIFIED adalah variasi pada kasus USER.

Sintaksis ekspresi memungkinkan Anda mengevaluasi data menggunakan objek auth yang merepresentasikan data autentikasi yang diteruskan dengan operasi, baik data standar dalam token auth maupun data kustom dalam token. Untuk mengetahui daftar kolom yang tersedia di objek auth, lihat bagian referensi.

Tentu saja ada kasus penggunaan saat PUBLIC adalah tingkat akses yang tepat untuk dimulai. Sekali lagi, tingkat akses selalu menjadi titik awal, dan filter serta ekspresi tambahan diperlukan untuk keamanan yang kuat.

Panduan ini kini memberikan contoh cara membangun USER dan PUBLIC.

Contoh yang memotivasi

Contoh praktik terbaik berikut merujuk pada skema berikut untuk platform blog dengan konten tertentu yang dikunci di balik paket pembayaran.

Platform tersebut kemungkinan akan memodelkan Users danPosts.

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

Resource milik pengguna

Firebase merekomendasikan agar Anda menulis filter dan ekspresi yang menguji kepemilikan pengguna atas suatu resource, dalam kasus berikut, kepemilikan Posts.

Dalam contoh berikut, data dari token autentikasi dibaca dan dibandingkan menggunakan ekspresi. Pola umumnya adalah menggunakan ekspresi seperti where: {authorUid: {eq_expr: "auth.uid"}} untuk membandingkan authorUid yang disimpan dengan auth.uid (ID pengguna) yang diteruskan dalam token autentikasi.

Buat

Praktik otorisasi ini dimulai dengan menambahkan auth.uid dari token auth ke setiap Post baru sebagai kolom authorUid untuk memungkinkan perbandingan dalam pengujian otorisasi berikutnya.

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

Saat klien mencoba memperbarui Post, Anda dapat menguji auth.uid yang diteruskan terhadap authorUid yang disimpan.

# 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"
    }
  )
}
Hapus

Teknik yang sama digunakan untuk mengizinkan operasi penghapusan.

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

Memfilter Data

Sistem otorisasi Data Connect memungkinkan Anda menulis filter canggih yang dikombinasikan dengan tingkat akses preset seperti PUBLIC serta dengan menggunakan data dari token autentikasi.

Sistem otorisasi juga memungkinkan Anda menggunakan ekspresi saja, tanpa tingkat akses dasar, seperti yang ditunjukkan dalam beberapa contoh berikut.

Memfilter menurut atribut resource

Di sini, otorisasi tidak didasarkan pada token autentikasi karena tingkat keamanan dasar ditetapkan ke PUBLIC. Namun, kita dapat secara eksplisit menetapkan data dalam database kita sebagai cocok untuk akses publik; asumsikan kita memiliki Post data dalam database kita dengan visibility ditetapkan ke "public".

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
Memfilter menurut klaim pengguna

Di sini, asumsikan Anda telah menyiapkan klaim pengguna kustom yang meneruskan token autentikasi untuk mengidentifikasi pengguna dalam paket "pro" untuk aplikasi Anda, yang ditandai dengan kolom auth.token.plan dalam token autentikasi. Ekspresi Anda dapat diuji terhadap kolom ini.

# 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
  }
}
Filter menurut urutan + batas

Atau, Anda mungkin telah menetapkan visibility dalam rekaman Post untuk mengidentifikasi bahwa rekaman tersebut adalah konten yang tersedia untuk pengguna "pro", tetapi untuk pratinjau atau teaser listingan data, batasi lebih lanjut jumlah rekaman yang ditampilkan.

# 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
  }
}
Filter menurut peran

Jika klaim kustom Anda menentukan peran admin, Anda dapat menguji dan mengizinkan operasi yang sesuai.

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

Tambahkan direktif @check dan @redact untuk mencari data otorisasi

Kasus penggunaan otorisasi umum melibatkan penyimpanan peran otorisasi kustom di database Anda, misalnya dalam tabel izin khusus, dan menggunakan peran tersebut untuk mengizinkan mutasi guna membuat, memperbarui, atau menghapus data.

Dengan menggunakan pencarian data otorisasi, Anda dapat membuat kueri untuk peran berdasarkan userID dan menggunakan ekspresi CEL untuk memutuskan apakah mutasi diizinkan. Misalnya, Anda mungkin ingin menulis mutasi UpdateMovieTitle yang memungkinkan klien yang berwenang memperbarui judul film.

Untuk pembahasan selanjutnya, asumsikan database aplikasi ulasan film menyimpan peran otorisasi dalam tabel 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!
}

Penggunaan dalam mutasi

Dalam contoh penerapan berikut, mutasi UpdateMovieTitle mencakup kolom query untuk mengambil data dari MoviePermission, dan direktif berikut untuk memastikan operasi aman dan andal:

  • Direktif @transaction untuk memastikan semua kueri dan pemeriksaan otorisasi selesai atau gagal secara atomik.
  • Direktif @redact untuk menghilangkan hasil kueri dari respons, yang berarti pemeriksaan otorisasi kami dilakukan di server Data Connect, tetapi data sensitif tidak diekspos ke klien.
  • Pasangan direktif @check untuk mengevaluasi logika otorisasi pada hasil kueri, seperti menguji bahwa userID tertentu memiliki peran yang sesuai untuk melakukan modifikasi.

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

Penggunaan dalam kueri

Pencarian data otorisasi juga berguna untuk membatasi kueri berdasarkan peran atau batasan lainnya.

Dalam contoh berikut, yang juga menggunakan skema MoviePermission, kueri memeriksa apakah pemohon memiliki peran "admin" yang sesuai untuk melihat pengguna yang dapat mengedit film.

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

Antipola yang harus dihindari dalam otorisasi

Bagian sebelumnya membahas pola yang harus diikuti saat menggunakan direktif @auth.

Anda juga harus mewaspadai antipola penting yang harus dihindari.

Hindari meneruskan ID atribut pengguna dan parameter token otorisasi dalam argumen kueri dan mutasi

Firebase Authentication adalah alat canggih untuk menyajikan alur autentikasi dan mengambil data autentikasi dengan aman seperti ID pengguna terdaftar dan berbagai kolom yang disimpan dalam token autentikasi.

Sebaiknya jangan meneruskan ID pengguna dan data token autentikasi dalam argumen kueri dan mutasi.

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

Hindari penggunaan tingkat akses USER tanpa filter apa pun

Seperti yang dibahas beberapa kali dalam panduan, tingkat akses inti seperti USER, USER_ANON, USER_EMAIL_VERIFIED adalah dasar dan titik awal untuk pemeriksaan otorisasi, yang akan ditingkatkan dengan filter dan ekspresi. Menggunakan tingkat ini tanpa filter atau ekspresi yang sesuai yang memeriksa pengguna mana yang melakukan permintaan pada dasarnya setara dengan menggunakan tingkat PUBLIC.

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

Hindari penggunaan tingkat akses PUBLIC atau USER untuk pembuatan prototipe

Untuk mempercepat pengembangan, Anda mungkin tergoda untuk menetapkan semua operasi ke tingkat akses PUBLIC atau ke tingkat akses USER tanpa peningkatan lebih lanjut untuk mengizinkan semua operasi dan memungkinkan Anda menguji kode dengan cepat.

Setelah Anda melakukan pembuatan prototipe awal dengan cara ini, mulailah beralih dari NO_ACCESS ke otorisasi siap produksi dengan tingkat PUBLIC dan USER. Namun, jangan men-deploy-nya sebagai PUBLIC atau USER tanpa menambahkan logika tambahan seperti yang ditunjukkan dalam panduan ini.

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

Hindari mendasarkan otorisasi pada alamat email yang belum diverifikasi

Memberi akses kepada pengguna di domain tertentu adalah cara yang tepat untuk membatasi akses. Namun, siapa pun dapat mengklaim kepemilikan email selama proses login. Pastikan Anda hanya memberikan akses ke alamat email yang telah diverifikasi melalui Firebase Authentication.

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

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

Mengaudit otorisasi dengan Firebase CLI

Seperti yang ditunjukkan sebelumnya, tingkat akses preset seperti PUBLIC dan USER adalah titik awal untuk otorisasi yang kuat, dan harus digunakan dengan pemeriksaan otorisasi berbasis filter dan ekspresi tambahan. API ini tidak boleh digunakan sendiri tanpa mempertimbangkan kasus penggunaannya dengan cermat.

Data Connect membantu Anda mengaudit strategi otorisasi dengan menganalisis kode konektor saat Anda men-deploy ke server menggunakan firebase deploy dari Firebase CLI. Anda dapat menggunakan audit ini untuk membantu meninjau codebase.

Saat Anda men-deploy konektor, CLI akan menampilkan penilaian untuk kode operasi yang ada, diubah, dan baru di konektor Anda.

Untuk operasi yang diubah dan baru, CLI akan mengeluarkan peringatan dan meminta konfirmasi saat Anda menggunakan tingkat akses tertentu dalam operasi baru, atau saat Anda mengubah operasi yang ada untuk menggunakan tingkat akses tersebut.

Peringatan dan perintah selalu muncul untuk:

  • PUBLIC

Selain itu, peringatan dan perintah muncul di tingkat akses berikut saat Anda tidak menambahkannya dengan filter menggunakan auth.uid:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

Menyembunyikan peringatan operasi tidak aman dengan argumen @auth(insecureReason:)

Dalam banyak kasus, Anda akan menyimpulkan bahwa penggunaan tingkat akses PUBLIC dan USER* sangat tepat.

Jika konektor Anda berisi banyak operasi, Anda mungkin menginginkan output audit keamanan yang lebih jelas dan relevan yang menghilangkan operasi yang biasanya memicu peringatan, tetapi Anda tahu memiliki tingkat akses yang benar.

Anda dapat menyembunyikan peringatan untuk operasi tersebut dengan @auth(insecureReason:). Contoh:

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

Menggunakan Firebase App Check untuk pengesahan aplikasi

Autentikasi dan otorisasi adalah komponen penting dari keamanan Data Connect. Autentikasi dan otorisasi yang dikombinasikan dengan pengesahan aplikasi menghasilkan solusi keamanan yang sangat kuat.

Dengan pengesahan melalui Firebase App Check, perangkat yang menjalankan aplikasi Anda akan menggunakan penyedia pengesahan aplikasi atau perangkat yang menyatakan bahwa operasi Data Connect berasal dari aplikasi asli Anda dan permintaan berasal dari perangkat asli yang tidak dimodifikasi. Pengesahan ini dilampirkan pada setiap permintaan yang dibuat aplikasi Anda ke Data Connect.

Untuk mempelajari cara mengaktifkan App Check untuk Data Connect dan menyertakan SDK kliennya di aplikasi Anda, lihat ringkasan App Check.

Tingkat autentikasi untuk direktif @auth(level)

Tabel berikut mencantumkan semua tingkat akses standar dan padanannya dalam CEL. Tingkat autentikasi dicantumkan dari yang luas hingga yang sempit -- setiap tingkat mencakup semua pengguna yang cocok dengan tingkat berikutnya.

Tingkat Definisi
PUBLIC Operasi dapat dijalankan oleh siapa saja dengan atau tanpa autentikasi.

Pertimbangan: Data dapat dibaca atau diubah oleh pengguna mana pun. Firebase merekomendasikan tingkat otorisasi ini untuk data yang dapat dilihat secara publik seperti listingan produk atau media. Lihat contoh dan alternatif praktik terbaik.

Setara dengan @auth(expr: "true")

Filter dan ekspresi @auth tidak dapat digunakan bersamaan dengan tingkat akses ini. Ekspresi tersebut akan gagal dengan error permintaan buruk 400.
USER_ANON Setiap pengguna yang teridentifikasi, termasuk pengguna yang telah login secara anonim dengan Firebase Authentication, diizinkan untuk melakukan kueri atau mutasi.

Catatan: USER_ANON adalah superset dari USER.

Pertimbangan: Perhatikan bahwa Anda harus mendesain kueri dan mutasi dengan cermat untuk tingkat otorisasi ini. Level ini memungkinkan pengguna login secara anonim (login otomatis yang terikat hanya ke perangkat pengguna) dengan Authentication, dan dengan sendirinya tidak melakukan pemeriksaan lain, misalnya, apakah data milik pengguna. Lihat contoh dan alternatif praktik terbaik.

Karena alur login anonim Authentication mengeluarkan uid, tingkat USER_ANON setara dengan
@auth(expr: "auth.uid != nil")
USER Setiap pengguna yang telah login dengan Firebase Authentication diizinkan untuk melakukan kueri atau mutasi, kecuali pengguna login anonim.

Pertimbangan: Perhatikan bahwa Anda harus mendesain kueri dan mutasi dengan cermat untuk tingkat otorisasi ini. Level ini hanya memeriksa apakah pengguna login dengan Authentication, dan tidak dengan sendirinya melakukan pemeriksaan lain, misalnya, apakah data milik pengguna. Lihat contoh dan alternatif praktik terbaik.

Setara dengan @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED Setiap pengguna yang telah login dengan Firebase Authentication dengan alamat email yang diverifikasi diizinkan untuk melakukan kueri atau mutasi.

Pertimbangan: Karena verifikasi email dilakukan menggunakan Authentication, verifikasi ini didasarkan pada metode Authentication yang lebih andal, sehingga tingkat ini memberikan keamanan tambahan dibandingkan dengan USER atau USER_ANON. Tingkat ini hanya memeriksa apakah pengguna login dengan Authentication menggunakan email terverifikasi, dan tidak dengan sendirinya melakukan pemeriksaan lain, misalnya, apakah data milik pengguna. Lihat contoh dan alternatif praktik terbaik.

Setara dengan @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Operasi ini tidak dapat dijalankan di luar konteks Admin SDK.

Setara dengan @auth(expr: "false")

Referensi CEL untuk @auth(expr)

Seperti yang ditunjukkan dalam contoh di tempat lain dalam panduan ini, Anda dapat dan harus menggunakan ekspresi yang ditentukan dalam Common Expression Language (CEL) untuk mengontrol otorisasi untuk Data Connect menggunakan direktif @auth(expr:) dan @check.

Bagian ini mencakup sintaksis CEL yang relevan untuk membuat ekspresi bagi direktif ini.

Informasi referensi lengkap untuk CEL disediakan dalam spesifikasi CEL.

Variabel pengujian yang diteruskan dalam kueri dan mutasi

Sintaksis @auth(expr) memungkinkan Anda mengakses dan menguji variabel dari kueri dan mutasi.

Misalnya, Anda dapat menyertakan variabel operasi, seperti $status, menggunakan vars.status.

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

Data yang tersedia untuk ekspresi: request, response, this

Anda menggunakan data untuk:

  • Evaluasi dengan ekspresi CEL dalam direktif @auth(expr:) dan @check(expr:)
  • Penetapan menggunakan ekspresi server, <field>_expr.

Ekspresi CEL @auth(expr:) dan @check(expr:) dapat mengevaluasi hal berikut:

  • request.operationName
  • vars (alias untuk request.variables)
  • auth (alias untuk request.auth)

Dalam mutasi, Anda dapat mengakses dan menetapkan konten:

  • response (untuk memeriksa hasil parsial dalam logika multi-langkah)

Selain itu, ekspresi @check(expr:) dapat mengevaluasi:

  • this (nilai kolom saat ini)
  • response (untuk memeriksa hasil parsial dalam logika multi-langkah)

Binding request.operationName

Binding request.operarationName menyimpan jenis operasi, baik kueri atau mutasi.

Binding vars (request.vars)

Binding vars memungkinkan ekspresi Anda mengakses semua variabel yang diteruskan dalam kueri atau mutasi Anda.

Anda dapat menggunakan vars.<variablename> dalam ekspresi sebagai alias untuk request.variables.<variablename> yang sepenuhnya memenuhi syarat:

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

Binding auth (request.auth)

Authentication mengidentifikasi pengguna yang meminta akses ke data Anda dan memberikan informasi tersebut sebagai binding yang dapat Anda bangun dalam ekspresi Anda.

Dalam filter dan ekspresi, Anda dapat menggunakan auth sebagai alias untuk request.auth.

Binding auth berisi informasi berikut:

  • uid: ID pengguna unik, yang ditetapkan untuk pengguna yang meminta.
  • token: Peta nilai yang dikumpulkan oleh Authentication.

Untuk mengetahui detail selengkapnya tentang konten auth.token, lihat Data dalam token auth

Binding response

Binding response berisi data yang disusun oleh server sebagai respons terhadap kueri atau mutasi saat data tersebut disusun.

Saat operasi berlanjut, dan setiap langkah berhasil diselesaikan, response berisi data respons dari langkah-langkah yang berhasil diselesaikan.

Binding response disusun sesuai dengan bentuk operasi terkaitnya, termasuk kolom bertingkat (ganda) dan (jika berlaku) kueri sematan.

Perhatikan bahwa saat Anda mengakses data respons kueri sematan, kolom dapat berisi jenis data apa pun, bergantung pada data yang diminta dalam kueri sematan; saat Anda mengakses data yang ditampilkan oleh kolom mutasi seperti _insert dan _delete, data tersebut dapat berisi kunci UUID, jumlah penghapusan, nilai null (lihat referensi mutasi).

Contoh:

  • Dalam mutasi yang berisi kueri sematan, pengikatan response berisi data pencarian di response.query.<fieldName>.<fieldName>...., dalam hal ini, response.query.todoList dan response.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
    }
  }
}
  • Dalam mutasi multi-langkah, misalnya dengan beberapa kolom _insert, binding response berisi data parsial di response.<fieldName>.<fieldName>...., dalam hal ini, 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,
  })
}

Binding this

.

Binding this dievaluasi ke kolom yang dilampiri direktif @check. Dalam kasus dasar, Anda dapat mengevaluasi hasil kueri bernilai tunggal.

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

Jika kolom yang ditampilkan muncul beberapa kali karena ada ancestor yang berupa daftar, setiap kemunculan akan diuji dengan this yang terikat ke setiap nilai.

Untuk jalur tertentu, jika ancestor adalah null atau [], kolom tidak akan dijangkau dan evaluasi CEL akan dilewati untuk jalur tersebut. Dengan kata lain, evaluasi hanya terjadi saat this adalah null atau non-null, tetapi tidak pernah undefined.

Jika kolom itu sendiri adalah daftar atau objek, this mengikuti struktur yang sama (termasuk semua turunan yang dipilih jika berupa objek), seperti yang diilustrasikan dalam contoh berikut.

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

Sintaksis ekspresi kompleks

Anda dapat menulis ekspresi yang lebih kompleks dengan menggabungkan operator && dan ||.

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

Bagian berikut menjelaskan semua operator yang tersedia.

Operator dan prioritas operator

Gunakan tabel berikut sebagai referensi untuk operator dan prioritasnya yang sesuai.

Ekspresi arbitrer tertentu a dan b, kolom f, dan indeks i.

Operator Deskripsi Asosiativitas
a[i] a() a.f Akses indeks, panggilan, kolom kiri ke kanan
!a -a Negasi unary kanan ke kiri
a/b a%b a*b Operator multiplikatif kiri ke kanan
a+b a-b Operator aditif kiri ke kanan
a>b a>=b a<b a<=b Operator relasional kiri ke kanan
a in b Keberadaan dalam daftar atau peta kiri ke kanan
type(a) == t Perbandingan jenis, dengan t dapat berupa bool, int, float, angka, string, daftar, peta, stempel waktu, atau durasi kiri ke kanan
a==b a!=b Operator perbandingan kiri ke kanan
a && b AND kondisional kiri ke kanan
a || b OR kondisional kiri ke kanan
a ? true_value : false_value Ekspresi ternary kiri ke kanan

Data dalam token autentikasi

Objek auth.token dapat berisi nilai berikut:

Kolom Deskripsi
email Alamat email yang terhubung dengan akun, jika ada.
email_verified true jika pengguna telah memverifikasi bahwa mereka memiliki akses ke alamat email. Beberapa penyedia secara otomatis memverifikasi alamat email yang mereka miliki.
phone_number Nomor telepon yang terkait dengan akun, jika ada.
name Nama tampilan pengguna, jika ditetapkan.
sub UID Firebase pengguna. UID ini bersifat unik dalam sebuah project.
firebase.identities Kamus yang memuat semua identitas terkait akun pengguna ini. Kunci kamus dapat berupa salah satu dari berikut ini: email, phone, google.com, facebook.com, github.com, twitter.com. Nilai kamus adalah array ID unik untuk setiap penyedia identitas yang terkait dengan akun. Misalnya, auth.token.firebase.identities["google.com"][0] berisi ID pengguna Google pertama yang dikaitkan dengan akun.
firebase.sign_in_provider Penyedia login yang digunakan untuk mendapatkan token ini. Dapat berupa salah satu string berikut: custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com.
firebase.tenant tenantId yang terkait dengan akun, jika ada. Contoh, tenant2-m6tyz

Kolom tambahan di token ID JWT

Anda juga dapat mengakses kolom auth.token berikut:

Klaim Token Kustom
alg Algoritme "RS256"
iss Penerbit Alamat email akun layanan project Anda
sub Subjek Alamat email akun layanan project Anda
aud Audience "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Waktu penerbitan Waktu saat ini, dalam satuan detik sejak epoch UNIX
exp Waktu habis masa berlaku Waktu saat token sudah tidak berlaku lagi, dalam satuan detik sejak epoch UNIX. Waktu ini bisa mencapai maksimum 3600 detik lebih lama daripada iat.
Catatan: ini hanya mengontrol kapan token kustom berhenti berlaku. Namun, setelah Anda memproses login menggunakan signInWithCustomToken(), pengguna akan tetap login di perangkatnya hingga validitas sesi berakhir atau pengguna tersebut logout.
<claims> (opsional) Klaim kustom opsional yang akan disertakan dalam token, yang dapat diakses melalui auth.token (atau request.auth.token) dalam ekspresi. Misalnya, jika Anda membuat klaim kustom adminClaim, Anda dapat mengaksesnya dengan auth.token.adminClaim.

Apa langkah selanjutnya?