認可と構成証明による Secure Data Connect

Firebase Data Connect は、次の機能により堅牢なクライアントサイド セキュリティを提供します。

  • モバイル クライアントとウェブ クライアントの認可
  • 個々のクエリレベルとミューテーション レベルの認可制御
  • Firebase App Check を使用したアプリの証明書。

Data Connect は、次の機能でこのセキュリティを拡張します。

  • サーバーサイドの認可
  • IAM を使用した Firebase プロジェクトと Cloud SQL ユーザーのセキュリティ。

クライアントのクエリとミューテーションを承認する

Data ConnectFirebase Authentication と完全に統合されているため、データにアクセスしているユーザーに関する豊富なデータ(認証)を、ユーザーがアクセスできるデータ(認可)の設計で使用できます。

Data Connect は、クエリとミューテーション用の @auth ディレクティブを提供します。このディレクティブを使用すると、オペレーションの承認に必要な認証レベルを設定できます。このガイドでは、@auth ディレクティブについて、例を挙げて説明します

また、Data Connect はミューテーションに埋め込まれたクエリの実行をサポートしているため、データベースに保存した追加の認可条件を取得し、@check ディレクティブでその条件を使用して、囲まれたミューテーションが認可されているかどうかを判断できます。この認可の場合、@redact ディレクティブを使用すると、クエリ結果がワイヤ プロトコルでクライアントに返されるかどうか、生成された SDK で埋め込みクエリが省略されるかどうかを制御できます。これらのディレクティブの概要と例をご覧ください。

@auth ディレクティブについて

@auth ディレクティブをパラメータ化して、一般的なアクセス シナリオをカバーする複数のプリセット アクセスレベルのいずれかに従うことができます。これらのレベルは、PUBLIC(あらゆる種類の認証なしですべてのクライアントからのクエリとミューテーションを許可)から NO_ACCESSFirebase 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 が適切なアクセスレベルであるユースケースもあります。アクセスレベルは常に開始点であり、堅牢なセキュリティを確保するには追加のフィルタと式が必要です。

このガイドでは、USERPUBLIC をベースに構築する方法の例を示しています。

動機付けの例

次のベスト プラクティスの例は、特定のコンテンツが有料プランでロックされているブログ プラットフォームの次のスキーマを参照しています。

このようなプラットフォームでは、UsersPosts がモデル化される可能性があります。

type User @table(key: "uid") {
  uid: String!
  name: String
  birthday: Date
  createdAt: Timestamp! @default(expr: "request.time")
}

type Post @table {
  author: User!
  text: String!
  # "one of 'draft', 'public', or 'pro'"
  visibility: String! @default(value: "draft")
  # "the time at which the post should be considered published. defaults to
  # immediately"
  publishedAt: Timestamp! @default(expr: "request.time")
  createdAt: Timestamp! @default(expr: "request.time")
  updatedAt: Timestamp! @default(expr: "request.time")
}

ユーザー所有のリソース

Firebase では、次のケースで、リソースのユーザー所有権(Posts の所有権など)をテストするフィルタと式を作成することをおすすめします。

次の例では、式を使用して認証トークンのデータが読み取られ、比較されます。一般的なパターンは、where: {authorUid: {eq_expr: "auth.uid"}} などの式を使用して、保存されている authorUid と認証トークンで渡された auth.uid(ユーザー ID)を比較することです。

作成

この認可プラクティスは、認証トークンから auth.uid を各新しい PostauthorUid フィールドとして追加し、後続の認可テストで比較できるようにすることから始まります。

# 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
# 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 に設定されているため、認証トークンに基づく認可は行われません。ただし、データベース内のレコードを一般公開に適しているとして明示的に設定できます。データベースに visibility が「public」に設定されている Post レコードがあるとします。

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
ユーザー クレームでフィルタする

ここでは、アプリの「プロ」プランのユーザーを識別するために、認証トークンで渡されるカスタム ユーザー クレームを設定し、認証トークンの auth.token.plan フィールドでフラグを設定しているとします。式でこのフィールドに対してテストできます。

# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
  posts(where: {
    # display both public posts and "pro" posts
    visibility: {in: ['public', 'pro']},
    # only display articles that are already published
    publishedAt: {lt_expr: "request.time"},
  }) {
    # see the fragment above
    ...DisplayPost
    # show visibility so pro users can see which posts are pro\
    visibility
  }
}
注文と上限でフィルタ

また、Post レコードで visibility を設定して「プロ」ユーザーが利用できるコンテンツであることを識別し、データのプレビューまたはティーザー リストでは、返されるレコードの数をさらに制限することもできます。

# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
  posts(
    where: {
      # show only pro posts
      visibility: {eq: "pro"}
      # that have already been published more than 30 days ago
      publishedAt: {lt_time: {now: true, sub: {days: 30}}}
    },
    # order by publish time
    orderBy: [{publishedAt: DESC}],
    # only return two posts
    limit: 2
  ) {
    # See the fragment above
    ...DisplayPost
  }
}
ロールでフィルタ

カスタム クレームで admin ロールを定義している場合は、それに応じてオペレーションをテストして承認できます。

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

@check ディレクティブと @redact ディレクティブを追加して、承認データをルックアップする

一般的な認可のユースケースでは、カスタム認可ロールをデータベース(特別な権限テーブルなど)に保存し、それらのロールを使用して、データの作成、更新、削除を行うミューテーションを認可します。

認証データ ルックアップを使用すると、userID に基づいてロールをクエリし、CEL 式を使用してミューテーションが承認されているかどうかを判断できます。たとえば、承認済みのクライアントが映画のタイトルを更新できる UpdateMovieTitle ミューテーションを作成できます。

以降の説明では、映画レビュー アプリのデータベースが MoviePermission テーブルに認可ロールを保存していることを前提とします。

# MoviePermission
# Suppose a user has an authorization role with respect to records in the Movie table
type MoviePermission @table(key: ["movie", "user"]) {
  movie: Movie! # implies another field: movieId: UUID!
  user: User!
  role: String!
}

ミューテーションで使用する

次の実装例では、UpdateMovieTitle ミューテーションに MoviePermission からデータを取得する query フィールドが含まれており、オペレーションの安全性と堅牢性を確保するための次のディレクティブが含まれています。

  • すべての認可クエリとチェックが自動的に完了または失敗することを保証する @transaction ディレクティブ。
  • レスポンスからクエリ結果を省略する @redact ディレクティブ。つまり、認証チェックは Data Connect サーバーで実行されますが、機密データはクライアントに公開されません。
  • クエリ結果の認可ロジックを評価する @check ディレクティブのペア。たとえば、特定の userID に変更を行う適切なロールがあることをテストします。

mutation UpdateMovieTitle($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    # Step 1a: Use @check to test if the user has any role associated with the movie
    # Here the `this` binding refers the lookup result, i.e. a MoviePermission object or null
    # The `this != null` expression could be omitted since rejecting on null is default behavior
    ) @check(expr: "this != null", message: "You do not have access to this movie") {
      # Step 1b: Check if the user has the editor role for the movie
      # Next we execute another @check; now `this` refers to the contents of the `role` field
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

クエリで使用する

認可データのルックアップは、ロールやその他の制限に基づいてクエリを制限する場合にも役立ちます。

次の例では、MoviePermission スキーマも使用して、リクエスタに映画を編集できるユーザーを表示するための適切な「管理者」ロールがあるかどうかをクエリで確認します。

query GetMovieEditors($movieId: UUID!) @auth(level: PUBLIC) {
  moviePermission(key: { movieId: $movieId, userId_expr: "auth.uid" }) @redact {
    role @check(expr: "this == 'admin'", message: "You must be an admin to view all editors of a movie.")
  }
  moviePermissions(where: { movieId: { eq: $movieId }, role: { eq: "editor" } }) {
    user {
      id
      username
    }
  }
}

認可で避けるべきアンチパターン

前のセクションでは、@auth ディレクティブを使用する際に従うパターンについて説明しました。

避けるべき重要なアンチパターンも把握しておく必要があります。

クエリ引数とミューテーション引数でユーザー属性 ID と認証トークン パラメータを渡さない

Firebase Authentication は、認証フローを提示し、登録済みユーザー ID や認証トークンに保存されている多数のフィールドなどの認証データを安全にキャプチャするための強力なツールです。

クエリとミューテーションの引数でユーザー ID と認証トークンデータを渡すことはおすすめしません。

# Antipattern!
# This incorrectly allows any user to view any other user's posts
query AllMyPosts($userId: String!) @auth(level: USER) {
  posts(where: {authorUid: {eq: $userId}}) {
    id, text, createdAt
  }
}

フィルタなしで USER アクセスレベルを使用しない

ガイドで何度か説明したように、USERUSER_ANONUSER_EMAIL_VERIFIED などのコア アクセスレベルは、認可チェックのベースラインと開始点であり、フィルタと式で強化されます。リクエストを実行しているユーザーをチェックする対応するフィルタや式を使用せずにこれらのレベルを使用することは、基本的に PUBLIC レベルを使用することと同じです。

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

プロトタイピングに 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 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
  })
}

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

Firebase CLI で認可を監査する

前述のように、PUBLICUSER などのプリセット アクセスレベルは、堅牢な認可の出発点であり、追加のフィルタと式ベースの認可チェックとともに使用する必要があります。ユースケースを慎重に検討せずに単独で使用しないでください。

Data Connect は、Firebase CLI の firebase deploy を使用してサーバーにデプロイするときに、コネクタコードを分析して認可戦略を監査します。この監査を使用して、コードベースを確認できます。

コネクタをデプロイすると、CLI はコネクタ内の既存のオペレーション コード、変更されたオペレーション コード、新しいオペレーション コードの評価を出力します。

変更されたオペレーションと新しいオペレーションの場合、新しいオペレーションで特定のアクセスレベルを使用する場合や、既存のオペレーションを変更してこれらのアクセスレベルを使用する場合、CLI は警告を発行し、確認を求めます。

警告とプロンプトは常に次の状況で表示されます。

  • PUBLIC

また、auth.uid を使用してフィルタで拡張しない場合、次のアクセスレベルで警告とプロンプトが表示されます。

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

@auth(insecureReason:) 引数を使用して安全でないオペレーションの警告を抑制する

多くの場合、PUBLICUSER* のアクセスレベルを使用するのが適切であると判断できます。

コネクタに多くのオペレーションが含まれている場合は、通常は警告をトリガーするが、正しいアクセスレベルがわかっているオペレーションを除外した、より明確で関連性の高いセキュリティ監査出力を取得したい場合があります。

@auth(insecureReason:) を使用すると、このようなオペレーションの警告を抑制できます。例:

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

アプリの証明書には Firebase App Check を使用する

認証と認可は、Data Connect セキュリティの重要なコンポーネントです。認証と認可をアプリ証明書と組み合わせることで、非常に堅牢なセキュリティ ソリューションが実現します。

Firebase App Check による証明書を使用すると、アプリを実行しているデバイスは、Data Connect オペレーションが正規のアプリから発信され、リクエストが改ざんされていない正規のデバイスから発信されていることを証明するアプリまたはデバイスの証明書プロバイダを使用します。この証明書は、アプリが Data Connect に送信するすべてのリクエストに添付されます。

Data ConnectApp Check を有効にして、そのクライアント SDK をアプリに組み込む方法については、App Check の概要をご覧ください。

@auth(level) ディレクティブの認証レベル

次の表に、すべての標準アクセスレベルとその CEL の同等のレベルを示します。認証レベルは、広範囲から狭範囲の順に並んでいます。各レベルには、次のレベルに一致するすべてのユーザーが含まれます。

レベル 定義
PUBLIC このオペレーションは、認証の有無にかかわらず、誰でも実行できます。

考慮事項: データはすべてのユーザーが読み取りまたは変更できます。Firebase は、商品やメディアのリスティングなど、一般公開されているデータに対してこのレベルの認可を推奨しています。ベスト プラクティスの例と代替案をご覧ください。

@auth(expr: "true") と同等

@auth フィルタと式は、このアクセスレベルと組み合わせて使用できません。このような式は、400 Bad Request エラーで失敗します。
USER_ANON Firebase Authentication を使用して匿名でログインしたユーザーを含む、特定されたすべてのユーザーは、クエリまたはミューテーションを実行する権限が付与されます。

注: USER_ANONUSER のスーパーセットです。

考慮事項: このレベルの認可では、クエリとミューテーションを慎重に設計する必要があります。このレベルでは、ユーザーは Authentication を使用して匿名でログインできます(ユーザー デバイスにのみ関連付けられた自動ログイン)。また、データがユーザーに属するかどうかなどの他のチェックは単独では実行されません。ベスト プラクティスの例と代替案をご覧ください。

Authentication 匿名ログインフローは uid を発行するため、USER_ANON レベルは
@auth(expr: "auth.uid != nil") と同等です。
USER Firebase Authentication でログインしたユーザーは、匿名ログイン ユーザーを除き、クエリまたはミューテーションを実行する権限が付与されます。

考慮事項: このレベルの認可では、クエリとミューテーションを慎重に設計する必要があります。このレベルでは、ユーザーが Authentication でログインしていることのみが確認され、データがユーザーに属しているかどうかなどの他のチェックは単独では実行されません。ベスト プラクティスの例と代替案をご覧ください。

@auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")" と同等
USER_EMAIL_VERIFIED 確認済みのメールアドレスで Firebase Authentication を使用してログインしたユーザーは、クエリまたはミューテーションを実行する権限が付与されます。

考慮事項: メール確認は Authentication を使用して行われるため、より堅牢な Authentication メソッドに基づいています。したがって、このレベルでは USERUSER_ANON と比較してセキュリティが強化されています。このレベルでは、ユーザーが確認済みのメールアドレスで Authentication にログインしていることのみが確認されます。データがユーザーに属しているかどうかなどの他のチェックは、このレベルでは行われません。ベスト プラクティスの例と代替案をご覧ください。

@auth(expr: "auth.uid != nil && auth.token.email_verified")" と同等
NO_ACCESS このオペレーションは、Admin SDK のコンテキスト外では実行できません。

@auth(expr: "false") と同等

@auth(expr) の CEL リファレンス

このガイドの他の例に示すように、Common Expression Language(CEL)で定義された式を使用して、@auth(expr:) ディレクティブと @check ディレクティブを使用して Data Connect の認可を制御できます。

このセクションでは、これらのディレクティブの式を作成する際に使用する CEL 構文について説明します。

CEL の完全なリファレンス情報は、CEL 仕様で提供されています。

クエリとミューテーションで渡されるテスト変数

@auth(expr) 構文を使用すると、クエリとミューテーションから変数にアクセスしてテストできます。

たとえば、vars.status を使用して、$status などのオペレーション変数を含めることができます。

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

式で使用できるデータ: request、response、this

データは次の目的で使用されます。

  • @auth(expr:) ディレクティブと @check(expr:) ディレクティブでの CEL 式による評価
  • サーバー式 <field>_expr を使用した割り当て。

@auth(expr:)@check(expr:) の両方の CEL 式で、次の値を評価できます。

  • request.operationName
  • varsrequest.variables のエイリアス)
  • authrequest.auth のエイリアス)

ミューテーションでは、次の内容にアクセスして割り当てることができます。

  • response(複数ステップのロジックで部分的な結果を確認するため)

また、@check(expr:) 式では次の値を評価できます。

  • this(現在のフィールドの値)
  • response(複数ステップのロジックで部分的な結果を確認するため)

request.operationName バインディング

request.operarationName バインディングには、オペレーションのタイプ(クエリまたはミューテーション)が保存されます。

vars バインディング(request.vars)

vars バインディングを使用すると、式でクエリまたはミューテーションで渡されたすべての変数にアクセスできます。

式で vars.<variablename> を完全修飾 request.variables.<variablename> のエイリアスとして使用できます。

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

auth バインディング(request.auth)

Authentication は、データへのアクセスをリクエストしているユーザーを識別し、その情報を、式で利用できるバインディングとして提供します。

フィルタと式では、authrequest.auth のエイリアスとして使用できます。

認証バインディングには次の情報が含まれます。

  • uid: リクエストしているユーザーに割り当てられた一意のユーザー ID。
  • token: Authentication によって収集された値のマップ。

auth.token の内容について詳しくは、認証トークンのデータをご覧ください。

response バインディング

response バインディングには、クエリまたはミューテーションに対するレスポンスとしてサーバーによって組み立てられるデータが、そのデータが組み立てられるにつれて含まれます。

オペレーションが進行し、各ステップが正常に完了すると、response には正常に完了したステップからのレスポンス データが含まれます。

response バインディングは、関連付けられたオペレーションのシェイプに従って構造化されます。これには、ネストされた(複数の)フィールドと(該当する場合)埋め込みクエリが含まれます。

埋め込みクエリのレスポンス データにアクセスする場合、フィールドには埋め込みクエリでリクエストされたデータに応じて任意のデータ型を含めることができます。_insert_delete などのミューテーション フィールドから返されたデータにアクセスする場合、UUID キー、削除数、null を含めることができます(ミューテーションのリファレンスをご覧ください)。

例:

  • 埋め込みクエリを含むミューテーションでは、response バインディングに response.query.<fieldName>.<fieldName>.... のルックアップ データ(この場合は 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
    }
  }
}
  • たとえば、複数の _insert フィールドを含むマルチステップ ミューテーションでは、response バインディングに response.<fieldName>.<fieldName>.... の部分データ(この場合は response.todoList_insert.id)が含まれます。
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

this バインディング

バインディング this は、@check ディレクティブがアタッチされているフィールドに評価されます。基本的なケースでは、単一値のクエリ結果を評価します。

mutation UpdateMovieTitle (
  $movieId: UUID!,
  $newTitle: String!)
  @auth(level: USER)
  @transaction {
  # Step 1: Query and check
  query @redact {
    moviePermission( # Look up a join table called MoviePermission with a compound key.
      key: {movieId: $movieId, userId_expr: "auth.uid"}
    ) {
      # Check if the user has the editor role for the movie. `this` is the string value of `role`.
      # If the parent moviePermission is null, the @check will also fail automatically.
      role @check(expr: "this == 'editor'", message: "You must be an editor of this movie to update title")
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

祖先がリストであるため、返されたフィールドが複数回発生する場合、各発生は、各値にバインドされた this でテストされます。

特定のパスで、祖先が null または [] の場合、フィールドに到達せず、そのパスの CEL 評価はスキップされます。つまり、評価は thisnull または非 null の場合にのみ行われ、undefined の場合は行われません。

フィールド自体がリストまたはオブジェクトの場合、this は次の例に示すように、同じ構造(オブジェクトの場合は選択されたすべての子孫を含む)に従います。

mutation UpdateMovieTitle2($movieId: UUID!, $newTitle: String!) @auth(level: USER) @transaction {
  # Step 1: Query and check
  query {
    moviePermissions( # Now we query for a list of all matching MoviePermissions.
      where: {movieId: {eq: $movieId}, userId: {eq_expr: "auth.uid"}}
    # This time we execute the @check on the list, so `this` is the list of objects.
    # We can use the `.exists` macro to check if there is at least one matching entry.
    ) @check(expr: "this.exists(p, p.role == 'editor')", message: "You must be an editor of this movie to update title") {
      role
    }
  }
  # Step 2: Act
  movie_update(id: $movieId, data: {
    title: $newTitle
  })
}

複雑な式の構文

&& 演算子と || 演算子を組み合わせることで、より複雑な式を記述できます。

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

次のセクションでは、使用可能なすべての演算子について説明します。

演算子と演算子の優先順位

演算子とそれぞれの優先順位については、次の表を参考にしてください。

次の表では、任意の式 ab、フィールド f、インデックス i を前提としています。

演算子 説明 関連性
a[i] a() a.f インデックス、呼び出し、フィールド アクセス 左から右
!a -a 単項否定 右から左
a/b a%b a*b 乗法演算子 左から右
a+b a-b 加法演算子 左から右
a>b a>=b a<b a<=b 関係演算子 左から右
a in b リストまたはマップ内の存在 左から右
type(a) == t 型の比較。t は、bool、int、float、number、string、list、map、timestamp、duration です。 左から右
a==b a!=b 比較演算子 左から右
a && b 条件付き AND 左から右
a || b 条件付き OR 左から右
a ? true_value : false_value 3 項式 左から右

認証トークンのデータ

auth.token オブジェクトには次の値が含まれることがあります。

フィールド 説明
email アカウントに関連付けられているメールアドレス(存在する場合)。
email_verified ユーザーに email アドレスへのアクセス権があることが確認された場合は true。一部のプロバイダは、そのプロバイダが所有するメールアドレスを自動的に確認します。
phone_number アカウントに関連付けられている電話番号(存在する場合)。
name ユーザーの表示名(設定されている場合)。
sub ユーザーの Firebase UID。これはプロジェクト内で一意です。
firebase.identities このユーザーのアカウントに関連付けられているすべての ID の辞書。辞書のキーは、emailphonegoogle.comfacebook.comgithub.comtwitter.com のいずれかです。辞書の値は、アカウントに関連付けられている各 ID プロバイダの一意の識別子からなる配列です。たとえば、auth.token.firebase.identities["google.com"][0] にはアカウントに関連付けられている最初の Google ユーザー ID が含まれます。
firebase.sign_in_provider このトークンを取得するために使用されたログイン プロバイダ。custompasswordphoneanonymousgoogle.comfacebook.comgithub.comtwitter.com のいずれかの文字列です。
firebase.tenant 存在する場合、アカウントに関連付けられている tenantId。例: tenant2-m6tyz

JWT ID トークンの追加フィールド

次の auth.token フィールドにもアクセスできます。

カスタム トークンのクレーム
alg アルゴリズム "RS256"
iss 発行元 プロジェクトのサービス アカウントのメールアドレス。
sub 件名 プロジェクトのサービス アカウントのメールアドレス。
aud 対象 "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat 発行時 現在の時間(UNIX エポック時刻からの秒数)
exp 有効期限 トークンの有効期限が切れる時間(UNIX エポック時刻からの秒数)。iat から最大 3,600 秒後の時間を設定できます。
注: これは、カスタム トークン自体の有効期限が切れる時間のみを制御できます。ただし、signInWithCustomToken() を使用してユーザーにログインさせた後は、セッションが無効になるかユーザーがログアウトするまで、デバイスにログインしたままになります。
<claims>(オプション) トークンに含めるオプションのカスタム クレーム。式内の auth.token(または request.auth.token)を介してアクセスできます。たとえば、カスタム クレーム adminClaim を作成した場合、auth.token.adminClaim でアクセスできます。

次のステップ