Bezpieczne łączenie danych z autoryzacją i potwierdzeniem

Firebase Data Connect zapewnia solidne zabezpieczenia po stronie klienta dzięki:

  • Autoryzacja klienta mobilnego i internetowego
  • Indywidualne opcje kontroli autoryzacji na poziomie zapytań i mutacji
  • Atestowanie aplikacji za pomocą Firebase App Check.

Data Connect rozszerza to bezpieczeństwo o:

  • Autoryzacja po stronie serwera
  • Zabezpieczanie projektu Firebase i użytkownika Cloud SQL za pomocą IAM.

Autoryzowanie zapytań i mutacji klienta

Data Connect jest w pełni zintegrowana z usługą Firebase Authentication, dzięki czemu możesz wykorzystywać w projekcie bogate dane o użytkownikach, którzy uzyskują dostęp do Twoich danych (uwierzytelnianie), aby określać, do jakich danych mogą oni mieć dostęp (autoryzacja).

Data Connect udostępnia dyrektywę @auth dla zapytań i mutacji, która umożliwia ustawienie poziomu uwierzytelniania wymaganego do autoryzacji operacji. W tym przewodniku przedstawiamy dyrektywę @auth wraz z przykładami.

Dodatkowo Data Connect obsługuje wykonywanie zapytań osadzonych w mutacjach, dzięki czemu możesz pobierać dodatkowe kryteria autoryzacji przechowywane w bazie danych i używać ich w dyrektywach @check, aby określić, czy otaczające mutacje są autoryzowane. W tym przypadku autoryzacji dyrektywa @redact umożliwia określenie, czy wyniki zapytania mają być zwracane do klientów w protokole przewodowym, a osadzone zapytanie ma być pomijane w wygenerowanych pakietach SDK. Zapoznaj się z wprowadzeniem do tych dyrektyw wraz z przykładami.

Informacje o dyrektywie @auth

Możesz sparametryzować dyrektywę @auth, aby była zgodna z jednym z kilku gotowych poziomów dostępu, które obejmują wiele typowych scenariuszy dostępu. Poziomy te wahają się od PUBLIC (który zezwala na zapytania i mutacje ze wszystkich klientów bez uwierzytelniania jakiegokolwiek rodzaju) do NO_ACCESS (który zabrania zapytań i mutacji poza uprzywilejowanymi środowiskami serwerowymi korzystającymi z pakietu Firebase Admin SDK). Każdy z tych poziomów jest powiązany z procesami uwierzytelniania udostępnianymi przez Firebase Authentication.

Poziom Definicja
PUBLIC Operację może wykonać każdy, z uwierzytelnianiem lub bez niego.
PUBLIC Operację może wykonać każdy, z uwierzytelnianiem lub bez niego.
USER_ANON Każdy zidentyfikowany użytkownik, w tym użytkownicy, którzy zalogowali się anonimowo za pomocą Firebase Authentication, ma uprawnienia do wykonania zapytania lub mutacji.
USER Każdy użytkownik, który zalogował się za pomocą Firebase Authentication, ma uprawnienia do wykonania zapytania lub mutacji, z wyjątkiem użytkowników, którzy zalogowali się anonimowo.
USER_EMAIL_VERIFIED Każdy użytkownik, który zalogował się za pomocą Firebase Authentication i ma zweryfikowany adres e-mail, może wykonać zapytanie lub mutację.
NO_ACCESS Tej operacji nie można wykonać poza kontekstem pakietu Admin SDK.

Korzystając z tych gotowych poziomów dostępu jako punktu wyjścia, możesz zdefiniować złożone i niezawodne kontrole autoryzacji w dyrektywie @auth za pomocą filtrów where i wyrażeń w języku CEL (Common Expression Language) ocenianych na serwerze.

Używanie dyrektywy @auth do implementowania typowych scenariuszy autoryzacji

Wstępnie ustawione poziomy dostępu są punktem wyjścia autoryzacji.

USER Poziom dostępu to najbardziej przydatny podstawowy poziom, od którego warto zacząć.

Pełne zabezpieczenie dostępu będzie oparte na poziomie USER oraz filtrach i wyrażeniach, które sprawdzają atrybuty użytkownika, atrybuty zasobu, role i inne elementy. Poziomy USER_ANONUSER_EMAIL_VERIFIED to odmiany poziomu USER.

Składnia wyrażeń umożliwia ocenę danych za pomocą obiektu auth reprezentującego dane uwierzytelniające przekazywane z operacjami, zarówno standardowe dane w tokenach uwierzytelniających, jak i dane niestandardowe w tokenach. Listę pól dostępnych w obiekcie auth znajdziesz w sekcji referencyjnej.

Oczywiście są przypadki użycia, w których PUBLIC jest odpowiednim poziomem dostępu na początek. Poziom dostępu jest zawsze punktem wyjścia, a do zapewnienia solidnych zabezpieczeń potrzebne są dodatkowe filtry i wyrażenia.

W tym przewodniku znajdziesz teraz przykłady rozszerzania funkcji USERPUBLIC.

Przykład motywujący

Poniższe przykłady sprawdzonych metod odnoszą się do tego schematu platformy blogowej, na której niektóre treści są dostępne tylko w ramach płatnego abonamentu.

Taka platforma prawdopodobnie modeluje 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")
}

Zasoby należące do użytkownika

W tych przypadkach Firebase zaleca pisanie filtrów i wyrażeń, które sprawdzają, czy użytkownik jest właścicielem zasobu, np. Posts:

W przykładach poniżej dane z tokenów uwierzytelniających są odczytywane i porównywane za pomocą wyrażeń. Zazwyczaj używa się wyrażeń takich jak where: {authorUid: {eq_expr: "auth.uid"}}, aby porównać przechowywany authorUidauth.uid (identyfikatorem użytkownika) przekazywanym w tokenie uwierzytelniającym.

Utwórz

Ta praktyka autoryzacji rozpoczyna się od dodania auth.uid z tokena autoryzacji do każdego nowego Post jako pola authorUid, aby umożliwić porównanie w kolejnych testach autoryzacji.

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

Gdy klient próbuje zaktualizować Post, możesz przetestować przekazany auth.uid w porównaniu z zapisanym 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"
    }
  )
}
Usuń

Ta sama technika jest używana do autoryzowania operacji usuwania.

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

Filtrowanie danych

Data ConnectUmożliwia pisanie zaawansowanych filtrów w połączeniu z gotowymi poziomami dostępu, takimi jak PUBLIC, a także przy użyciu danych z tokenów autoryzacji.

System autoryzacji umożliwia też używanie tylko wyrażeń bez podstawowego poziomu dostępu, jak pokazano w niektórych z tych przykładów.

Filtrowanie według atrybutów zasobu

Autoryzacja nie jest tu oparta na tokenach autoryzacji, ponieważ podstawowy poziom bezpieczeństwa jest ustawiony na PUBLIC. Możemy jednak wyraźnie oznaczyć rekordy w naszej bazie danych jako odpowiednie do publicznego dostępu. Załóżmy, że w naszej bazie danych mamy Post rekordów, w których pole visibility ma wartość „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
  }
}
Filtrowanie według roszczeń użytkownika

Załóżmy, że masz skonfigurowane niestandardowe roszczenia użytkowników, które przekazują tokeny uwierzytelniania w celu identyfikowania użytkowników korzystających z wersji „pro” aplikacji. Są oni oznaczeni polem auth.token.plan w tokenie uwierzytelniania. Wyrażenia mogą być testowane względem tego pola.

# 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
  }
}
Filtruj według kolejności i limitu

Możesz też ustawić wartość visibility w rekordach Post, aby wskazać, że są to treści dostępne dla użytkowników „pro”, ale w przypadku podglądu lub listy danych z zajawką dodatkowo ograniczyć liczbę zwracanych rekordów.

# 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
  }
}
Filtrowanie według roli

Jeśli roszczenie niestandardowe definiuje admin rolę, możesz odpowiednio testować i autoryzować operacje.

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

Dodaj dyrektywy @check@redact, aby wyszukać dane autoryzacji.

Typowy przypadek użycia autoryzacji polega na przechowywaniu niestandardowych ról autoryzacji w bazie danych, na przykład w specjalnej tabeli uprawnień, i używaniu tych ról do autoryzowania zmian w celu tworzenia, aktualizowania lub usuwania danych.

Korzystając z wyszukiwania danych autoryzacji, możesz wysyłać zapytania o role na podstawie identyfikatora użytkownika i używać wyrażeń CEL, aby określić, czy mutacja jest autoryzowana. Możesz na przykład napisać UpdateMovieTitle mutację, która umożliwi autoryzowanemu klientowi aktualizowanie tytułów filmów.

W dalszej części tego omówienia załóżmy, że baza danych aplikacji do recenzowania filmów przechowuje rolę autoryzacji w tabeli 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!
}

Używanie w mutacjach

W tym przykładzie implementacji mutacja UpdateMovieTitle zawiera pole query do pobierania danych z MoviePermission oraz te dyrektywy, które zapewniają bezpieczeństwo i niezawodność operacji:

  • Dyrektywa @transaction, która zapewnia, że wszystkie zapytania i sprawdzanie autoryzacji są wykonywane lub kończą się niepowodzeniem w sposób niepodzielny.
  • Dyrektywa @redact, która nakazuje pominięcie wyników zapytania w odpowiedzi, co oznacza, że sprawdzanie autoryzacji jest przeprowadzane na serwerze Data Connect, ale dane wrażliwe nie są udostępniane klientowi.
  • Para dyrektyw @check do oceny logiki autoryzacji w odniesieniu do wyników zapytania, np. sprawdzenia, czy dany identyfikator użytkownika ma odpowiednią rolę do wprowadzania zmian.

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

Używanie w zapytaniach

Wyszukiwanie danych autoryzacji jest też przydatne do ograniczania zapytań na podstawie ról lub innych ograniczeń.

W poniższym przykładzie, który również korzysta ze schematu MoviePermission, zapytanie sprawdza, czy osoba wysyłająca żądanie ma odpowiednią rolę „administratora”, aby wyświetlić użytkowników, którzy mogą edytować 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
    }
  }
}

Wzorce, których należy unikać w autoryzacji

W poprzedniej sekcji omówiliśmy wzorce, których należy przestrzegać podczas korzystania z dyrektywy @auth.

Warto też znać ważne antywzorce, których należy unikać.

Unikaj przekazywania identyfikatorów atrybutów użytkownika i parametrów tokena autoryzacji w argumentach zapytań i mutacji

Firebase Authentication to zaawansowane narzędzie do prezentowania przepływów uwierzytelniania i bezpiecznego rejestrowania danych uwierzytelniających, takich jak zarejestrowane identyfikatory użytkowników i liczne pola przechowywane w tokenach uwierzytelniających.

Przekazywanie identyfikatorów użytkowników i danych tokena uwierzytelniania w argumentach zapytań i mutacji nie jest zalecane.

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

Unikaj używania poziomu dostępu USER bez filtrów

Jak wspomnieliśmy w tym przewodniku kilka razy, podstawowe poziomy dostępu, takie jak USER, USER_ANONUSER_EMAIL_VERIFIED, są punktami wyjścia do sprawdzania autoryzacji, które można rozszerzyć za pomocą filtrów i wyrażeń. Używanie tych poziomów bez odpowiedniego filtra lub wyrażenia, które sprawdza, który użytkownik wysyła żądanie, jest w zasadzie równoznaczne z używaniem poziomu PUBLIC.

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

Unikaj używania poziomu dostępu PUBLIC lub USER do tworzenia prototypów

Aby przyspieszyć proces tworzenia, możesz ustawić wszystkie operacje na poziomie dostępu PUBLIC lub USER bez dalszych ulepszeń, aby autoryzować wszystkie operacje i szybko przetestować kod.

Gdy w ten sposób stworzysz wstępny prototyp, zacznij przechodzić z NO_ACCESS na autoryzację gotową do wdrożenia na poziomach PUBLICUSER. Nie wdrażaj ich jednak jako PUBLIC ani USER bez dodawania dodatkowej logiki, jak pokazano w tym przewodniku.

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

Unikaj autoryzacji na podstawie niezweryfikowanych adresów e-mail

Przyznanie dostępu użytkownikom w określonej domenie to świetny sposób na ograniczenie dostępu. Podczas logowania każdy może jednak twierdzić, że jest właścicielem danego adresu e-mail. Upewnij się, że przyznajesz dostęp tylko do adresów e-mail, które zostały zweryfikowane za pomocą usługi Uwierzytelnianie Firebase.

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

Sprawdź też 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
  })
}

Sprawdzanie autoryzacji za pomocą wiersza poleceń Firebase

Jak wspomnieliśmy wcześniej, gotowe poziomy dostępu, takie jak PUBLICUSER, są punktem wyjścia dla solidnej autoryzacji i powinny być używane z dodatkowymi filtrami i sprawdzaniem autoryzacji na podstawie wyrażeń. Nie należy ich używać samodzielnie bez dokładnego rozważenia przypadku użycia.

Data Connect pomaga w audycie strategii autoryzacji przez analizowanie kodu łącznika podczas wdrażania na serwerze za pomocą firebase deployFirebase interfejsu CLI. Możesz użyć tego audytu, aby sprawdzić bazę kodu.

Po wdrożeniu łączników interfejs CLI wyświetli oceny istniejącego, zmodyfikowanego i nowego kodu operacji w łączniku.

W przypadku zmodyfikowanych i nowych operacji interfejs wiersza poleceń wyświetla ostrzeżenia i prosi o potwierdzenie, gdy w nowych operacjach używasz określonych poziomów dostępu lub gdy modyfikujesz istniejące operacje, aby używać tych poziomów dostępu.

Ostrzeżenia i prośby o potwierdzenie zawsze pojawiają się w przypadku:

  • PUBLIC

Ostrzeżenia i prośby pojawiają się na tych poziomach dostępu, gdy nie rozszerzysz ich za pomocą filtrów z użyciem auth.uid:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

Blokowanie ostrzeżeń o niezabezpieczonych operacjach za pomocą argumentu @auth(insecureReason:)

W wielu przypadkach dojdziesz do wniosku, że używanie poziomów dostępu PUBLICUSER* jest w pełni odpowiednie.

Jeśli Twój łącznik zawiera wiele operacji, możesz potrzebować bardziej przejrzystych i trafnych wyników audytu bezpieczeństwa, które pomijają operacje, które zwykle wywołują ostrzeżenie, ale wiesz, że mają odpowiedni poziom dostępu.

Ostrzeżenia dotyczące takich operacji możesz wyłączyć za pomocą @auth(insecureReason:). Przykład:

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

Używanie Firebase App Check do potwierdzania aplikacji

Uwierzytelnianie i autoryzacja to kluczowe elementy zabezpieczeńData Connect. Uwierzytelnianie i autoryzacja w połączeniu z atestem aplikacji stanowią bardzo solidne rozwiązanie w zakresie bezpieczeństwa.

Dzięki atestowaniu za pomocą Firebase App Check urządzenia, na których działa Twoja aplikacja, będą korzystać z dostawcy atestowania aplikacji lub urządzenia, który potwierdzi, że Data Connect operacje pochodzą z autentycznej aplikacji, a żądania – z autentycznego, niezmodyfikowanego urządzenia. To zaświadczenie jest dołączane do każdego żądania wysyłanego przez Twoją aplikację do Data Connect.

Aby dowiedzieć się, jak włączyć App Check w przypadku Data Connect i uwzględnić w aplikacji pakiet SDK klienta, zapoznaj się z tym App Check artykułem.

Poziomy uwierzytelniania w przypadku dyrektywy @auth(level)

W tabeli poniżej znajdziesz wszystkie standardowe poziomy dostępu i ich odpowiedniki w CEL. Poziomy uwierzytelniania są wymienione od najszerszego do najwęższego – każdy poziom obejmuje wszystkich użytkowników, którzy spełniają wymagania kolejnych poziomów.

Poziom Definicja
PUBLIC Operację może wykonać każdy, z uwierzytelnianiem lub bez niego.

Ważne informacje: dane mogą być odczytywane lub modyfikowane przez każdego użytkownika. Firebase zaleca ten poziom autoryzacji w przypadku danych dostępnych publicznie, takich jak listy produktów lub multimediów. Zobacz przykłady sprawdzonych metod i alternatywne rozwiązania.

Odpowiednik @auth(expr: "true")

@auth Filtrów i wyrażeń nie można używać w połączeniu z tym poziomem dostępu. Wszelkie takie wyrażenia będą kończyć się niepowodzeniem i zwracać błąd 400 (Nieprawidłowe żądanie).
USER_ANON Każdy zidentyfikowany użytkownik, w tym użytkownicy, którzy zalogowali się anonimowo za pomocą Firebase Authentication, ma uprawnienia do wykonania zapytania lub mutacji.

Uwaga: USER_ANON to nadzbiór USER.

Ważne informacje: pamiętaj, że musisz starannie zaprojektować zapytania i mutacje na potrzeby tego poziomu autoryzacji. Ten poziom umożliwia użytkownikowi anonimowe logowanie się (automatyczne logowanie powiązane tylko z urządzeniem użytkownika) za pomocą Authentication i sam w sobie nie wykonuje innych sprawdzeń, np. czy dane należą do użytkownika. Zobacz przykłady sprawdzonych metod i alternatywne rozwiązania.

Ponieważ przepływy logowania anonimowego Authentication wydają uid, poziom USER_ANON jest równoważny poziomowi
. @auth(expr: "auth.uid != nil")
USER Każdy użytkownik, który zalogował się za pomocą Firebase Authentication, ma uprawnienia do wykonania zapytania lub mutacji, z wyjątkiem użytkowników, którzy zalogowali się anonimowo.

Ważne informacje: pamiętaj, że musisz starannie zaprojektować zapytania i mutacje na potrzeby tego poziomu autoryzacji. Ten poziom sprawdza tylko, czy użytkownik jest zalogowany za pomocą Authentication, i nie wykonuje samodzielnie innych sprawdzeń, np. czy dane należą do użytkownika. Zobacz przykłady sprawdzonych metod i alternatywne rozwiązania.

Odpowiednik: @auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED Każdy użytkownik, który zalogował się za pomocą Firebase Authentication i ma zweryfikowany adres e-mail, może wykonać zapytanie lub mutację.

Ważne informacje: weryfikacja adresu e-mail jest przeprowadzana za pomocą Authentication, więc opiera się na bardziej niezawodnej metodzie Authentication. Zapewnia to dodatkowe bezpieczeństwo w porównaniu z USER lub USER_ANON. Ten poziom sprawdza tylko, czy użytkownik jest zalogowany za pomocą Authentication z zweryfikowanym adresem e-mail, i nie wykonuje samodzielnie innych sprawdzeń, np. czy dane należą do użytkownika. Zobacz przykłady sprawdzonych metod i alternatywne rozwiązania.

Odpowiednik: @auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS Tej operacji nie można wykonać poza kontekstem pakietu Admin SDK.

Odpowiednik: @auth(expr: "false")

Dokumentacja CEL dla @auth(expr)

Jak pokazano w przykładach w innych częściach tego przewodnika, możesz i powinieneś(-aś) używać wyrażeń zdefiniowanych w języku Common Expression Language (CEL) do kontrolowania autoryzacji w przypadku Data Connect za pomocą dyrektyw @auth(expr:)@check.

W tej sekcji omówimy składnię CEL, która jest istotna przy tworzeniu wyrażeń dla tych dyrektyw.

Pełne informacje referencyjne dotyczące CEL znajdziesz w specyfikacji CEL.

Testowanie zmiennych przekazywanych w zapytaniach i mutacjach

Składnia @auth(expr) umożliwia dostęp do zmiennych z zapytań i mutacji oraz ich testowanie.

Możesz na przykład uwzględnić zmienną operacji, np. $status, używając vars.status.

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

Dane dostępne w wyrażeniach: request, response, this

Dane są wykorzystywane do:

  • Ocena za pomocą wyrażeń CEL w dyrektywach @auth(expr:) i @check(expr:)
  • Przypisanie za pomocą wyrażeń serwera, <field>_expr.

Wyrażenia CEL @auth(expr:)@check(expr:) mogą oceniać te elementy:

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

W mutacjach możesz uzyskać dostęp do zawartości tych elementów i przypisać ją do nich:

  • response (aby sprawdzić wyniki częściowe w logice wieloetapowej)

Dodatkowo wyrażenia @check(expr:) mogą sprawdzać:

  • this (wartość bieżącego pola)
  • response (aby sprawdzić wyniki częściowe w logice wieloetapowej)

Powiązanie request.operationName

Wiązanie request.operarationName przechowuje typ operacji, czyli zapytanie lub zmianę.

Powiązanie vars (request.vars)

Wiązanie vars umożliwia wyrażeniom dostęp do wszystkich zmiennych przekazywanych w zapytaniu lub mutacji.

W wyrażeniu możesz użyć atrybutu vars.<variablename> jako aliasu dla w pełni kwalifikowanego atrybutu 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'")

Powiązanie auth (request.auth)

Authentication identyfikuje użytkowników, którzy proszą o dostęp do Twoich danych, i udostępnia te informacje jako powiązanie, na którym możesz budować wyrażenia.

W filtrach i wyrażeniach możesz używać znaku auth jako aliasu dla request.auth.

Powiązanie autoryzacji zawiera te informacje:

  • uid: unikalny identyfikator użytkownika przypisany do użytkownika wysyłającego żądanie.
  • token: mapa wartości zebranych przez Authentication.

Więcej informacji o zawartości auth.token znajdziesz w sekcji Dane w tokenach uwierzytelniających.

Powiązanie response

response powiązanie zawiera dane, które serwer gromadzi w odpowiedzi na zapytanie lub mutację w trakcie ich gromadzenia.

W miarę postępu operacji, po pomyślnym zakończeniu każdego kroku, pole response zawiera dane odpowiedzi z pomyślnie zakończonych kroków.

Powiązanie response jest skonstruowane zgodnie z kształtem powiązanej operacji, w tym (wieloma) zagnieżdżonymi polami i (w stosownych przypadkach) wbudowanymi zapytaniami.

Pamiętaj, że gdy uzyskujesz dostęp do danych odpowiedzi na zapytanie osadzone, pola mogą zawierać dowolny typ danych w zależności od danych, o które prosisz w zapytaniu osadzonym. Gdy uzyskujesz dostęp do danych zwracanych przez pola mutacji, takie jak _inserts i _deletes, mogą one zawierać klucze UUID, liczbę usunięć i wartości null (patrz dokumentacja mutacji).

Przykład:

  • W mutacji zawierającej zapytanie zagnieżdżone powiązanie response zawiera dane wyszukiwania w response.query.<fieldName>.<fieldName>...., w tym przypadku 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
    }
  }
}
  • W przypadku mutacji wieloetapowej, na przykład z wieloma polami _insert, powiązanie response zawiera częściowe dane w polu response.<fieldName>.<fieldName>...., w tym przypadku 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,
  })
}

Powiązanie this

Powiązanie this jest obliczane na podstawie pola, do którego jest dołączona dyrektywa @check. W podstawowym przypadku możesz oceniać wyniki zapytań jednowartościowych.

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

Jeśli zwrócone pole występuje wiele razy, ponieważ dowolny element nadrzędny jest listą, każde wystąpienie jest testowane z parametrem this powiązanym z każdą wartością.

Jeśli w przypadku danej ścieżki element nadrzędny ma wartość null lub [], pole nie zostanie osiągnięte, a ocena CEL zostanie pominięta w przypadku tej ścieżki. Innymi słowy, ocena jest przeprowadzana tylko wtedy, gdy wartość this to null lub wartość niebędąca null, ale nigdy undefined.

Jeśli pole jest listą lub obiektem, this ma taką samą strukturę (w przypadku obiektów obejmuje wszystkie wybrane elementy podrzędne), jak pokazano w przykładzie poniżej.

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

Składnia złożonych wyrażeń

Możesz pisać bardziej złożone wyrażenia, łącząc je z operatorami &&||.

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

W sekcji poniżej znajdziesz opis wszystkich dostępnych operatorów.

Operatory i ich kolejność

W poniższej tabeli znajdziesz operatory i ich priorytety.

Dane są wyrażeniami ab, polem f oraz indeksem i.

Operator Opis Łączność
a[i] a() a.f Indeksowanie, wywoływanie, dostęp do pól od lewej do prawej,
!a -a Negacja jednoargumentowa od prawej do lewej,
a/b a%b a*b Operatory mnożenia od lewej do prawej,
a+b a-b Operatory dodawania od lewej do prawej,
a>b a>=b a<b a<=b Operatory relacji od lewej do prawej,
a in b Obecność na liście lub mapie od lewej do prawej,
type(a) == t porównanie typów, gdzie t może być wartością logiczną, liczbą całkowitą, liczbą zmiennoprzecinkową, liczbą, ciągiem znaków, listą, mapą, sygnaturą czasową lub czasem trwania; od lewej do prawej,
a==b a!=b Operatory porównania od lewej do prawej,
a && b Warunkowe ORAZ od lewej do prawej,
a || b Warunkowe LUB od lewej do prawej,
a ? true_value : false_value Wyrażenie trójargumentowe od lewej do prawej,

Dane w tokenach uwierzytelniania

Obiekt auth.token może zawierać te wartości:

Pole Opis
email adres e-mail powiązany z kontem (jeśli jest dostępny);
email_verified true, jeśli użytkownik potwierdził, że ma dostęp do adresu email. Niektórzy dostawcy automatycznie weryfikują adresy e-mail, których są właścicielami.
phone_number numer telefonu powiązany z kontem (jeśli jest dostępny);
name Wyświetlana nazwa użytkownika, jeśli została ustawiona.
sub Identyfikator UID Firebase użytkownika. Jest on unikalny w obrębie projektu.
firebase.identities Słownik wszystkich tożsamości powiązanych z kontem tego użytkownika. Klucze słownika mogą być dowolne z tych wartości: email, phone, google.com, facebook.com, github.com, twitter.com. Wartości słownika to tablice unikalnych identyfikatorów każdego dostawcy tożsamości powiązanego z kontem. Na przykład auth.token.firebase.identities["google.com"][0] zawiera pierwszy identyfikator użytkownika Google powiązany z kontem.
firebase.sign_in_provider Dostawca logowania użyty do uzyskania tego tokena. Może być jednym z tych ciągów: custom, password, phone, anonymous, google.com, facebook.com, github.com, twitter.com.
firebase.tenant Identyfikator tenantId powiązany z kontem (jeśli występuje). Na przykład tenant2-m6tyz

Dodatkowe pola w tokenach identyfikatorów JWT

Możesz też uzyskać dostęp do tych pól auth.token:

Deklaracje tokenów niestandardowych
alg Algorytm "RS256"
iss Wystawca Adres e-mail konta usługi w projekcie
sub Temat Adres e-mail konta usługi w projekcie
aud Odbiorcy "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat Godzina wydania Bieżący czas w sekundach od początku epoki systemu UNIX.
exp Okres ważności Czas wygaśnięcia tokena podany w sekundach od początku epoki systemu UNIX. Może być maksymalnie o 3600 sekund późniejsza niż wartość iat.
Uwaga: to ustawienie kontroluje tylko czas wygaśnięcia samego tokena niestandardowego. Gdy jednak zalogujesz użytkownika za pomocą signInWithCustomToken(), pozostanie on zalogowany na urządzeniu, dopóki jego sesja nie straci ważności lub użytkownik się nie wyloguje.
<claims> (opcjonalnie) Opcjonalne roszczenia niestandardowe do uwzględnienia w tokenie, do których można uzyskać dostęp za pomocą symbolu auth.token (lub request.auth.token) w wyrażeniach. Jeśli na przykład utworzysz roszczenie niestandardowe adminClaim, możesz uzyskać do niego dostęp za pomocą auth.token.adminClaim.

Co dalej?