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.
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ürrequest.variables
)auth
(Alias fürrequest.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 unterresponse.query.<fieldName>.<fieldName>....
, in diesem Fallresponse.query.todoList
undresponse.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 dieresponse
-Bindung Teildaten unterresponse.<fieldName>.<fieldName>....
, in diesem Fallresponse.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
- Firebase Data Connect bietet ein Admin SDK, mit dem Sie Abfragen und Mutationen aus privilegierten Umgebungen ausführen können.
- Weitere Informationen zur IAM-Sicherheit finden Sie im Leitfaden zum Verwalten von Diensten und Datenbanken.