Secure Data Connect mit Autorisierung und Attestierung

Firebase Data Connect bietet robuste clientseitige Sicherheit mit:

  • Autorisierung von Mobil- und Webclients
  • Individuelle Autorisierungskontrollen auf Abfrage- und Mutationsebene
  • App-Attestierung mit Firebase App Check.

Data Connect bietet folgende zusätzliche Sicherheitsfunktionen:

  • Serverseitige Autorisierung
  • Sicherheit von Firebase-Projekten und Cloud SQL-Nutzern mit IAM

Clientabfragen und ‑mutationen autorisieren

Data Connect ist vollständig in Firebase Authentication integriert. Sie können also umfangreiche Daten zu Nutzern, die auf Ihre Daten zugreifen (Authentifizierung), in Ihr Design für die Daten einbeziehen, auf die diese Nutzer zugreifen können (Autorisierung).

Data Connect bietet eine @auth-Anweisung für Abfragen und Mutationen, mit der Sie die Authentifizierungsebene festlegen können, die zum Autorisieren des Vorgangs erforderlich ist. In diesem Leitfaden wird die @auth-Anweisung mit Beispielen vorgestellt.

Außerdem unterstützt Data Connect die Ausführung von Abfragen, die in Mutationen eingebettet sind. So können Sie zusätzliche Autorisierungskriterien abrufen, die Sie in Ihrer Datenbank gespeichert haben, und diese Kriterien in @check-Direktiven verwenden, um zu entscheiden, ob die einschließenden Mutationen autorisiert sind. In diesem Autorisierungsfall können Sie mit der @redact-Anweisung steuern, ob Abfrageergebnisse an Clients im Wire-Protokoll zurückgegeben werden und ob die eingebettete Abfrage in generierten SDKs ausgelassen wird. Einführung in diese Direktiven mit Beispielen

Die @auth-Anweisung

Sie können die @auth-Anweisung parametrisieren, um eine von mehreren voreingestellten Zugriffsebenen zu verwenden, die viele gängige Zugriffsszenarien abdecken. Diese Ebenen reichen von PUBLIC (die Abfragen und Mutationen von allen Clients ohne jegliche Authentifizierung zulässt) bis NO_ACCESS (die Abfragen und Mutationen außerhalb von privilegierten Serverumgebungen mit dem Firebase Admin SDK nicht zulässt). Jede dieser Stufen ist mit den von Firebase Authentication bereitgestellten Authentifizierungsabläufen verknüpft.

Level Definition
PUBLIC Der Vorgang kann von jedem mit oder ohne Authentifizierung ausgeführt werden.
PUBLIC Der Vorgang kann von jedem mit oder ohne Authentifizierung ausgeführt werden.
USER_ANON Jeder identifizierte Nutzer, auch Nutzer, die sich anonym mit Firebase Authentication angemeldet haben, ist berechtigt, die Abfrage oder Mutation auszuführen.
USER Jeder Nutzer, der sich mit Firebase Authentication angemeldet hat, ist berechtigt, die Abfrage oder Mutation auszuführen, mit Ausnahme von Nutzern, die sich anonym angemeldet haben.
USER_EMAIL_VERIFIED Jeder Nutzer, der sich mit Firebase Authentication mit einer bestätigten E-Mail-Adresse angemeldet hat, ist berechtigt, die Abfrage oder Mutation auszuführen.
NO_ACCESS Dieser Vorgang kann nur im Rahmen des Admin SDK ausgeführt werden.

Anhand dieser voreingestellten Zugriffsebenen können Sie komplexe und robuste Autorisierungsprüfungen in der @auth-Anweisung mit where-Filtern und CEL-Ausdrücken (Common Expression Language) definieren, die auf dem Server ausgewertet werden.

@auth-Anweisung zum Implementieren gängiger Autorisierungsszenarien verwenden

Die voreingestellten Zugriffsebenen sind der Ausgangspunkt für die Autorisierung.

Die Zugriffsebene USER ist die nützlichste grundlegende Ebene für den Anfang.

Der vollständig sichere Zugriff basiert auf der USER-Ebene sowie auf Filtern und Ausdrücken, mit denen Nutzerattribute, Ressourcenattribute, Rollen und andere Prüfungen geprüft werden. Die Ebenen USER_ANON und USER_EMAIL_VERIFIED sind Variationen des Falls USER.

Mit der Ausdruckssyntax können Sie Daten mithilfe eines auth-Objekts auswerten, das Authentifizierungsdaten darstellt, die mit Vorgängen übergeben werden. Das sind sowohl Standarddaten in Autorisierungstokens als auch benutzerdefinierte Daten in Tokens. Eine Liste der im auth-Objekt verfügbaren Felder finden Sie im Referenzabschnitt.

Es gibt natürlich Anwendungsfälle, in denen PUBLIC die richtige Zugriffsebene für den Anfang ist. Auch hier gilt: Eine Zugriffsebene ist immer nur ein Ausgangspunkt. Für eine robuste Sicherheit sind zusätzliche Filter und Ausdrücke erforderlich.

In diesem Leitfaden finden Sie jetzt Beispiele für die Entwicklung auf USER und PUBLIC.

Motivierendes Beispiel

Die folgenden Best-Practice-Beispiele beziehen sich auf das folgende Schema für eine Blogging-Plattform, bei der bestimmte Inhalte nur mit einem kostenpflichtigen Abo zugänglich sind.

Auf einer solchen Plattform würden wahrscheinlich Users und Posts modelliert.

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

Ressourcen im Besitz des Nutzers

Firebase empfiehlt, Filter und Ausdrücke zu schreiben, mit denen die Inhaberschaft von Nutzern an einer Ressource getestet wird. In den folgenden Fällen wird die Inhaberschaft von Posts getestet.

In den folgenden Beispielen werden Daten aus Autorisierungstokens mithilfe von Ausdrücken gelesen und verglichen. Normalerweise werden Ausdrücke wie where: {authorUid: {eq_expr: "auth.uid"}} verwendet, um eine gespeicherte authorUid mit der auth.uid (User-ID) zu vergleichen, die im Authentifizierungstoken übergeben wird.

Erstellen

Bei dieser Autorisierungsmethode wird zuerst die auth.uid aus dem Autorisierungstoken jedem neuen Post als authorUid-Feld hinzugefügt, damit sie in nachfolgenden Autorisierungstests verglichen werden kann.

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

Wenn ein Client versucht, eine Post zu aktualisieren, können Sie die übergebene auth.uid mit der gespeicherten authorUid vergleichen.

# 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"
    }
  )
}
Löschen

Dieselbe Methode wird verwendet, um Löschvorgänge zu autorisieren.

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

Daten filtern

Das Autorisierungssystem von Data Connect ermöglicht es Ihnen, komplexe Filter in Kombination mit voreingestellten Zugriffsebenen wie PUBLIC zu schreiben und Daten aus Autorisierungstokens zu verwenden.

Das Autorisierungssystem ermöglicht es Ihnen auch, nur Ausdrücke ohne Basiszugriffsebene zu verwenden, wie in einigen der folgenden Beispiele gezeigt.

Nach Ressourcenattributen filtern

Die Autorisierung basiert hier nicht auf Autorisierungstokens, da die grundlegende Sicherheitsstufe auf PUBLIC festgelegt ist. Wir können jedoch Datensätze in unserer Datenbank explizit als für den öffentlichen Zugriff geeignet festlegen. Angenommen, wir haben Post Datensätze in unserer Datenbank, bei denen visibility auf „öffentlich“ gesetzt ist.

# 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
  }
}
Nach Nutzerbehauptungen filtern

Angenommen, Sie haben benutzerdefinierte Nutzeransprüche eingerichtet, die in Auth-Tokens übergeben werden, um Nutzer mit einem „Pro“-Abo für Ihre App zu identifizieren. Diese Nutzer sind mit einem auth.token.plan-Feld im Auth-Token gekennzeichnet. Ihre Ausdrücke können anhand dieses Felds getestet werden.

# 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
  }
}
Nach Reihenfolge und Limit filtern

Möglicherweise haben Sie visibility in Post-Einträgen festgelegt, um anzugeben, dass es sich um Inhalte handelt, die für „Pro“-Nutzer verfügbar sind. Für eine Vorschau oder einen Teaser von Daten sollten Sie die Anzahl der zurückgegebenen Einträge jedoch weiter einschränken.

# 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
  }
}
Nach Rolle filtern

Wenn in Ihrer benutzerdefinierten Anforderung eine admin-Rolle definiert ist, können Sie Vorgänge entsprechend testen und autorisieren.

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

Fügen Sie die Direktiven @check und @redact hinzu, um Autorisierungsdaten abzurufen.

Ein häufiger Anwendungsfall für die Autorisierung ist das Speichern benutzerdefinierter Autorisierungsrollen in Ihrer Datenbank, z. B. in einer speziellen Berechtigungstabelle, und die Verwendung dieser Rollen zum Autorisieren von Mutationen zum Erstellen, Aktualisieren oder Löschen von Daten.

Mit Autorisierungsdaten-Lookups können Sie Rollen anhand einer Nutzer-ID abfragen und mit CEL-Ausdrücken entscheiden, ob die Mutation autorisiert ist. Sie möchten beispielsweise eine UpdateMovieTitle-Mutation schreiben, mit der ein autorisierter Client Filmtitel aktualisieren kann.

Im weiteren Verlauf dieser Diskussion wird davon ausgegangen, dass in der Datenbank der Filmrezensions-App eine Autorisierungsrolle in einer MoviePermission-Tabelle gespeichert ist.

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

In Mutationen verwenden

In der folgenden Beispielimplementierung enthält die Mutation UpdateMovieTitle das Feld query, um Daten aus MoviePermission abzurufen, sowie die folgenden Direktiven, um sicherzustellen, dass der Vorgang sicher und robust ist:

  • Eine @transaction-Anweisung, um sicherzustellen, dass alle Autorisierungsanfragen und ‑prüfungen atomar abgeschlossen werden oder fehlschlagen.
  • Die @redact-Anweisung, um Abfrageergebnisse aus der Antwort auszuschließen. Das bedeutet, dass unsere Autorisierungsprüfung auf dem Data Connect-Server durchgeführt wird, sensible Daten aber nicht für den Client verfügbar gemacht werden.
  • Ein Paar @check-Anweisungen zum Auswerten der Autorisierungslogik für Abfrageergebnisse, z. B. zum Testen, ob eine bestimmte Nutzer-ID die entsprechende Rolle hat, um Änderungen vorzunehmen.

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

In Abfragen verwenden

Autorisierungsdaten-Lookups sind auch nützlich, um Abfragen basierend auf Rollen oder anderen Einschränkungen einzuschränken.

Im folgenden Beispiel, in dem auch das MoviePermission-Schema verwendet wird, wird geprüft, ob ein Anfragender eine entsprechende Administratorrolle hat, um Nutzer aufzurufen, die einen Film bearbeiten können.

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

Zu vermeidende Antipatterns bei der Autorisierung

Im vorherigen Abschnitt wurden Muster für die Verwendung der @auth-Anweisung behandelt.

Außerdem sollten Sie wichtige Antipatterns kennen, die Sie vermeiden sollten.

Keine Nutzerattribut-IDs und Autorisierungstoken-Parameter in Abfrage- und Mutationsargumenten übergeben

Firebase Authentication ist ein leistungsstarkes Tool zum Präsentieren von Authentifizierungsabläufen und zum sicheren Erfassen von Authentifizierungsdaten wie registrierten Nutzer-IDs und zahlreichen Feldern, die in Authentifizierungstokens gespeichert sind.

Es wird nicht empfohlen, Nutzer-IDs und Auth-Token-Daten in Abfrage- und Mutationsargumenten zu übergeben.

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

Die Zugriffsebene USER nicht ohne Filter verwenden

Wie im Leitfaden mehrfach erwähnt, sind die grundlegenden Zugriffsebenen wie USER, USER_ANON und USER_EMAIL_VERIFIED die Baselines und Ausgangspunkte für Autorisierungsprüfungen, die mit Filtern und Ausdrücken erweitert werden können. Die Verwendung dieser Ebenen ohne entsprechenden Filter oder Ausdruck, der prüft, welcher Nutzer die Anfrage ausführt, entspricht im Wesentlichen der Verwendung der Ebene PUBLIC.

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

PUBLIC- oder USER-Zugriffsebene für Prototypen vermeiden

Um die Entwicklung zu beschleunigen, kann es verlockend sein, alle Vorgänge auf die Zugriffsebene PUBLIC oder USER festzulegen, ohne weitere Verbesserungen vorzunehmen, um alle Vorgänge zu autorisieren und Ihren Code schnell zu testen.

Wenn Sie auf diese Weise erste Prototypen erstellt haben, sollten Sie von NO_ACCESS auf die produktionsreife Autorisierung mit den Ebenen PUBLIC und USER umstellen. Sie sollten sie jedoch nicht als PUBLIC oder USER bereitstellen, ohne zusätzliche Logik hinzuzufügen, wie in dieser Anleitung beschrieben.

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

Autorisierung nicht auf nicht bestätigten E-Mail-Adressen basieren

Wenn Sie Nutzern in einer bestimmten Domain Zugriff gewähren, können Sie den Zugriff einschränken. Bei der Anmeldung kann jedoch jeder behaupten, Inhaber einer E‑Mail-Adresse zu sein. Achten Sie darauf, dass Sie nur Zugriff auf E-Mail-Adressen gewähren, die über Firebase Authentication bestätigt wurden.

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

Autorisierung mit der Firebase CLI prüfen

Wie bereits erwähnt, sind voreingestellte Zugriffsebenen wie PUBLIC und USER der Ausgangspunkt für eine robuste Autorisierung und sollten mit zusätzlichen filter- und ausdrucksbasierten Autorisierungsprüfungen verwendet werden. Sie sollten nicht ohne sorgfältige Prüfung des Anwendungsfalls verwendet werden.

Mit Data Connect können Sie Ihre Autorisierungsstrategie prüfen, indem Sie Ihren Connector-Code analysieren, wenn Sie ihn mit firebase deploy über die Firebase CLI auf dem Server bereitstellen. Mit diesem Audit können Sie Ihren Code prüfen.

Wenn Sie Ihre Connectors bereitstellen, gibt die CLI Bewertungen für vorhandenen, geänderten und neuen Vorgangscode in Ihrem Connector aus.

Bei geänderten und neuen Vorgängen gibt die CLI Warnungen aus und fordert Sie zur Bestätigung auf, wenn Sie bestimmte Zugriffsebenen in Ihren neuen Vorgängen verwenden oder wenn Sie vorhandene Vorgänge so ändern, dass diese Zugriffsebenen verwendet werden.

Warnungen und Aufforderungen werden immer in den folgenden Fällen angezeigt:

  • PUBLIC

Bei den folgenden Zugriffsebenen werden Warnungen und Aufforderungen angezeigt, wenn Sie sie nicht mit Filtern über auth.uid erweitern:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

Warnungen vor unsicheren Vorgängen mit dem Argument @auth(insecureReason:) unterdrücken

In vielen Fällen werden Sie zu dem Schluss kommen, dass die Verwendung der Zugriffsebenen PUBLIC und USER* durchaus angemessen ist.

Wenn Ihr Connector viele Vorgänge enthält, möchten Sie möglicherweise eine übersichtlichere, relevantere Ausgabe des Sicherheitsaudits, in der Vorgänge ausgelassen werden, die normalerweise eine Warnung auslösen würden, bei denen Sie aber wissen, dass die richtige Zugriffsebene vorhanden ist.

Mit @auth(insecureReason:) können Sie Warnungen für solche Vorgänge unterdrücken. Beispiel:

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

Firebase App Check für die App-Attestierung verwenden

Authentifizierung und Autorisierung sind wichtige Komponenten der Data Connect-Sicherheit. Die Kombination aus Authentifizierung und Autorisierung mit der Gerätebestätigung ergibt eine sehr robuste Sicherheitslösung.

Bei der Bestätigung über Firebase App Check verwenden Geräte, auf denen Ihre App ausgeführt wird, einen App- oder Gerätebestätigungsanbieter, der bestätigt, dass Data Connect-Vorgänge von Ihrer authentischen App stammen und Anfragen von einem authentischen, nicht manipulierten Gerät stammen. Diese Bestätigung wird an jede Anfrage angehängt, die Ihre App an Data Connect sendet.

Weitere Informationen zum Aktivieren von App Check für Data Connect und zum Einbinden des Client-SDK in Ihre AppApp Check

Authentifizierungsebenen für die @auth(level)-Anweisung

In der folgenden Tabelle sind alle Standardzugriffsebenen und ihre CEL-Entsprechungen aufgeführt. Die Authentifizierungsebenen sind von breit bis eng aufgeführt. Jede Ebene umfasst alle Nutzer, die den folgenden Ebenen entsprechen.

Level Definition
PUBLIC Der Vorgang kann von jedem mit oder ohne Authentifizierung ausgeführt werden.

Hinweise:Daten können von jedem Nutzer gelesen oder geändert werden. Firebase empfiehlt diese Autorisierungsstufe für öffentlich zugängliche Daten wie Produkt- oder Media-Einträge. Beispiele für Best Practices und Alternativen

Entspricht @auth(expr: "true")

@auth-Filter und ‑Ausdrücke können nicht in Kombination mit dieser Zugriffsebene verwendet werden. Solche Ausdrücke führen zu einem 400 Bad Request-Fehler.
USER_ANON Jeder identifizierte Nutzer, auch Nutzer, die sich anonym mit Firebase Authentication angemeldet haben, ist berechtigt, die Abfrage oder Mutation auszuführen.

Hinweis: USER_ANON ist eine Obermenge von USER.

Wichtige Hinweise:Sie müssen Ihre Abfragen und Mutationen sorgfältig für diese Autorisierungsebene entwerfen. Auf dieser Ebene kann sich der Nutzer anonym anmelden (automatische Anmeldung nur an ein Nutzergerät gebunden) und es werden nicht automatisch andere Prüfungen durchgeführt, z. B. ob Daten dem Nutzer gehören.Authentication Beispiele für Best Practices und Alternativen

Da bei anonymen Anmeldevorgängen für Authentication ein uid ausgegeben wird, entspricht die USER_ANON-Ebene
@auth(expr: "auth.uid != nil")
USER Jeder Nutzer, der sich mit Firebase Authentication angemeldet hat, ist berechtigt, die Abfrage oder Mutation auszuführen, mit Ausnahme von Nutzern, die sich anonym angemeldet haben.

Wichtige Hinweise:Sie müssen Ihre Abfragen und Mutationen sorgfältig für diese Autorisierungsebene entwerfen. Auf dieser Ebene wird nur geprüft, ob der Nutzer mit Authentication angemeldet ist. Es werden keine weiteren Prüfungen durchgeführt, z. B. ob Daten dem Nutzer gehören. Beispiele für Best Practices und Alternativen

Entspricht @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED Jeder Nutzer, der sich mit Firebase Authentication mit einer bestätigten E-Mail-Adresse angemeldet hat, ist berechtigt, die Abfrage oder Mutation auszuführen.

Hinweise:Da die E-Mail-Bestätigung mit Authentication erfolgt, basiert sie auf einer robusteren Authentication-Methode. Daher bietet diese Stufe im Vergleich zu USER oder USER_ANON zusätzliche Sicherheit. Auf dieser Stufe wird nur geprüft, ob der Nutzer mit Authentication mit einer bestätigten E-Mail-Adresse angemeldet ist. Es werden keine weiteren Prüfungen durchgeführt, z. B. ob die Daten dem Nutzer gehören. Beispiele für Best Practices und Alternativen

Entspricht @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Dieser Vorgang kann nicht außerhalb eines Admin SDK-Kontexts ausgeführt werden.

Entspricht @auth(expr: "false")

CEL-Referenz für @auth(expr)

Wie in Beispielen an anderer Stelle in diesem Leitfaden gezeigt, können und sollten Sie Ausdrücke verwenden, die in der Common Expression Language (CEL) definiert sind, um die Autorisierung für Data Connect mit den Direktiven @auth(expr:) und @check zu steuern.

In diesem Abschnitt wird die CEL-Syntax behandelt, die für das Erstellen von Ausdrücken für diese Direktiven relevant ist.

Vollständige Referenzinformationen zu CEL finden Sie in der CEL-Spezifikation.

Testvariablen, die in Abfragen und Mutationen übergeben werden

Mit der @auth(expr)-Syntax können Sie auf Variablen aus Abfragen und Mutationen zugreifen und sie testen.

Sie können beispielsweise eine Vorgangsvariable wie $status mit vars.status einfügen.

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

Für Ausdrücke verfügbare Daten: request, response, this

Sie verwenden Daten für:

  • Auswertung mit CEL-Ausdrücken in @auth(expr:)- und @check(expr:)-Direktiven
  • Zuweisung mit Serverausdrücken, <field>_expr.

Sowohl @auth(expr:)- als auch @check(expr:)-CEL-Ausdrücke können Folgendes auswerten:

  • request.operationName
  • vars (Alias für request.variables)
  • auth (Alias für request.auth)

In Mutationen können Sie auf die Inhalte von Folgendem zugreifen und sie zuweisen:

  • response (zum Prüfen von Teilergebnissen in der mehrstufigen Logik)

Außerdem können @check(expr:)-Ausdrücke Folgendes auswerten:

  • this (der Wert des aktuellen Felds)
  • response (zum Prüfen von Teilergebnissen in der mehrstufigen Logik)

Die Bindung „request.operationName“

Die request.operarationName-Bindung speichert den Vorgangstyp, entweder „query“ (Abfrage) oder „mutation“ (Mutation).

Die vars-Bindung (request.vars)

Mit der vars-Bindung können Ihre Ausdrücke auf alle Variablen zugreifen, die in Ihrer Abfrage oder Mutation übergeben werden.

Sie können vars.<variablename> in einem Ausdruck als Alias für den vollständig qualifizierten request.variables.<variablename> verwenden:

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

Die auth-Bindung (request.auth)

Mit Authentication werden Nutzer identifiziert, die Zugriff auf Ihre Daten anfordern. Diese Informationen werden als Bindung bereitgestellt, die Sie in Ihren Ausdrücken verwenden können.

In Ihren Filtern und Ausdrücken können Sie auth als Alias für request.auth verwenden.

Die Authentifizierungsbindung enthält die folgenden Informationen:

  • uid: Eine eindeutige Nutzer-ID, die dem anfragenden Nutzer zugewiesen ist.
  • token: Eine Zuordnung von Werten, die von Authentication erfasst wurden.

Weitere Informationen zum Inhalt von auth.token findest du unter Daten in Autorisierungstokens.

Die response-Bindung

Die response-Bindung enthält die Daten, die vom Server als Reaktion auf eine Abfrage oder Mutation während der Zusammenstellung zusammengestellt werden.

Im Laufe des Vorgangs enthält response nach jedem erfolgreich abgeschlossenen Schritt Antwortdaten.

Die response-Bindung ist entsprechend der Form des zugehörigen Vorgangs strukturiert, einschließlich (mehrerer) verschachtelter Felder und (falls zutreffend) eingebetteter Abfragen.

Wenn Sie auf eingebettete Abfrageantwortdaten zugreifen, können Felder je nach den in der eingebetteten Abfrage angeforderten Daten einen beliebigen Datentyp enthalten. Wenn Sie auf Daten zugreifen, die von Mutationsfeldern wie _insert und _delete zurückgegeben werden, können diese UUID-Schlüssel, die Anzahl der Löschvorgänge und Nullwerte enthalten (siehe Mutationsreferenz).

Beispiel:

  • In einer Mutation, die eine eingebettete Abfrage enthält, enthält die response-Bindung Lookup-Daten unter response.query.<fieldName>.<fieldName>...., in diesem Fall response.query.todoList und 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
    }
  }
}
  • Bei einer mehrstufigen Mutation, z. B. mit mehreren _insert-Feldern, enthält die response-Bindung Teildaten unter response.<fieldName>.<fieldName>...., in diesem Fall 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,
  })
}

Die this-Bindung

Die Bindung this wird zum Feld ausgewertet, an das die @check-Anweisung angehängt ist. Im einfachsten Fall werten Sie Ergebnisse von Anfragen mit einem einzelnen Wert aus.

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

Wenn das zurückgegebene Feld mehrmals vorkommt, weil ein übergeordnetes Element eine Liste ist, wird jedes Vorkommen mit this getestet, wobei this an jeden Wert gebunden ist.

Wenn ein Vorfahre für einen bestimmten Pfad null oder [] ist, wird das Feld nicht erreicht und die CEL-Auswertung für diesen Pfad wird übersprungen. Die Auswertung erfolgt also nur, wenn this null oder nicht null ist, aber nie undefined.

Wenn das Feld selbst eine Liste oder ein Objekt ist, folgt this derselben Struktur (einschließlich aller ausgewählten untergeordneten Elemente im Fall von Objekten), wie im folgenden Beispiel veranschaulicht.

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

Syntax für komplexe Ausdrücke

Sie können komplexere Ausdrücke schreiben, indem Sie sie mit den Operatoren && und || kombinieren.

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

Im folgenden Abschnitt werden alle verfügbaren Operatoren beschrieben.

Operatoren und Operator-Vorrang

In der folgenden Tabelle finden Sie eine Übersicht über die Operatoren und ihre entsprechende Priorität.

Angenommen, es gibt beliebige Ausdrücke a und b, ein Feld f und einen Index i.

Operator Beschreibung Assoziativität
a[i] a() a.f Index, Aufruf, Feldzugriff von links nach rechts
!a -a Unäre Negation von rechts nach links
a/b a%b a*b Multiplikative Operatoren von links nach rechts
a+b a-b Additive Operatoren von links nach rechts
a>b a>=b a<b a<=b Relationale Operatoren von links nach rechts
a in b Vorhandensein in der Liste oder auf der Karte von links nach rechts
type(a) == t Typvergleich, wobei t bool, int, float, number, string, list, map, timestamp oder duration sein kann von links nach rechts
a==b a!=b Vergleichsoperatoren von links nach rechts
a && b Bedingtes AND von links nach rechts
a || b Bedingtes ODER von links nach rechts
a ? true_value : false_value Ternärer Ausdruck von links nach rechts

Daten in Auth-Tokens

Das auth.token-Objekt kann die folgenden Werte enthalten:

Feld Beschreibung
email Die mit dem Konto verknüpfte E-Mail-Adresse, falls vorhanden.
email_verified true, wenn der Nutzer bestätigt hat, dass er Zugriff auf die Adresse email hat. Einige Anbieter bestätigen automatisch E-Mail-Adressen, die ihnen gehören.
phone_number Die mit dem Konto verknüpfte Telefonnummer, falls vorhanden.
name Der Anzeigename des Nutzers, falls festgelegt.
sub Die Firebase-UID des Nutzers. Sie ist innerhalb eines Projekts eindeutig.
firebase.identities Verzeichnis aller Identitäten, die mit dem Konto dieses Nutzers verknüpft sind. Die Schlüssel des Dictionarys können einer der folgenden Werte sein: email, phone, google.com, facebook.com, github.com, twitter.com. Die Werte des Dictionary sind Arrays mit eindeutigen Kennungen für jeden Identitätsanbieter, der mit dem Konto verknüpft ist. auth.token.firebase.identities["google.com"][0] enthält beispielsweise die erste Google-Nutzer-ID, die mit dem Konto verknüpft ist.
firebase.sign_in_provider Der Anmeldeanbieter, der zum Abrufen dieses Tokens verwendet wurde. Kann einer der folgenden Strings sein: custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com.
firebase.tenant Die dem Konto zugeordnete „tenantId“, falls vorhanden. Beispiel: tenant2-m6tyz

Zusätzliche Felder in JWT-ID-Tokens

Sie können auch auf die folgenden auth.token-Felder zugreifen:

Benutzerdefinierte Token-Anforderungen
alg Algorithmus "RS256"
iss Aussteller Die E-Mail-Adresse des Dienstkontos Ihres Projekts
sub Betreff Die E-Mail-Adresse des Dienstkontos Ihres Projekts
aud Zielgruppe "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Ausstellungszeit Die aktuelle Zeit in Sekunden seit der UNIX-Epoche.
exp Ablaufzeit Die Zeit in Sekunden seit der UNIX-Epoche, zu der das Token abläuft. Dies kann maximal 3.600 Sekunden später als iat sein.
Beachten Sie, dass damit nur der Zeitpunkt gesteuert wird, zu dem das benutzerdefinierte Token selbst abläuft. Wenn Sie jedoch einen Nutzer mit signInWithCustomToken() anmelden, bleiben er so lange auf dem Gerät angemeldet, bis die Sitzung ungültig wird oder der Nutzer sich abmeldet.
<claims> (optional) Optionale benutzerdefinierte Anforderungen, die in das Token aufgenommen werden sollen und über auth.token (oder request.auth.token) in Ausdrücken aufgerufen werden können. Wenn Sie beispielsweise einen benutzerdefinierten Anspruch adminClaim erstellen, können Sie mit auth.token.adminClaim darauf zugreifen.

Nächste Schritte