חיבור מאובטח של נתונים באמצעות הרשאה ואימות

Firebase Data Connect מספקת אבטחה חזקה בצד הלקוח באמצעות:

  • הרשאה באפליקציות לנייד ובדפדפני אינטרנט
  • אמצעי בקרה של הרשאות ברמת השאילתה והמוטציה
  • אימות אפליקציות באמצעות Firebase App Check.

Data Connect מרחיב את האבטחה הזו באמצעות:

  • הרשאה בצד השרת
  • אבטחת משתמשי Cloud SQL ופרויקט Firebase באמצעות IAM.

אישור של שאילתות ומוטציות של לקוחות

Data Connect משולב באופן מלא עם Firebase Authentication, כך שתוכלו להשתמש בנתונים מפורטים על משתמשים שניגשים לנתונים שלכם (אימות) בתכנון שלכם לגבי הנתונים שהמשתמשים האלה יכולים לגשת אליהם (הרשאה).

Data Connect מספק הנחיית @auth לשאילתות ולשינויים, שמאפשרת להגדיר את רמת האימות שנדרשת כדי לאשר את הפעולה. במדריך הזה מוצגת ההנחיה @auth, עם דוגמאות.

בנוסף, Data Connect תומך בהרצת שאילתות שמוטמעות במוטציות, כך שאפשר לאחזר קריטריונים נוספים להרשאה ששמרתם במסד הנתונים, ולהשתמש בקריטריונים האלה בהוראות של @check כדי להחליט אם המוטציות המצורפות מורשות. במקרה הזה של הרשאה, ההנחיה @redact מאפשרת לכם לקבוע אם תוצאות השאילתה יוחזרו ללקוחות בפרוטוקול התקשורת, והאם השאילתה המוטמעת תושמט בערכות ה-SDK שנוצרו. כאן אפשר לקרוא מבוא להנחיות האלה, עם דוגמאות.

הסבר על ההנחיה @auth

אפשר להגדיר פרמטרים להוראה @auth כדי לפעול לפי אחת מכמה רמות גישה מוגדרות מראש שמתאימות לתרחישי גישה נפוצים רבים. הרמות האלה נעות בין PUBLIC (שמאפשרת שאילתות ושינויים מכל הלקוחות ללא אימות מכל סוג) לבין NO_ACCESS (שלא מאפשרת שאילתות ושינויים מחוץ לסביבות שרתים עם הרשאות מיוחדות באמצעות Firebase Admin SDK). כל אחת מהרמות האלה קשורה לתהליכי אימות שמסופקים על ידי Firebase Authentication.

רמה הגדרה
PUBLIC כל אחד יכול לבצע את הפעולה, עם או בלי אימות.
PUBLIC כל אחד יכול לבצע את הפעולה, עם או בלי אימות.
USER_ANON כל משתמש מזוהה, כולל משתמשים שהתחברו באופן אנונימי באמצעות Firebase Authentication, מורשה לבצע את השאילתה או את השינוי.
USER כל משתמש שהתחבר באמצעות Firebase Authentication מורשה לבצע את השאילתה או את השינוי, למעט משתמשים שהתחברו באופן אנונימי.
USER_EMAIL_VERIFIED כל משתמש שהתחבר באמצעות Firebase Authentication עם כתובת אימייל מאומתת מורשה לבצע את השאילתה או את השינוי.
NO_ACCESS אי אפשר לבצע את הפעולה הזו מחוץ להקשר של Admin SDK.

אפשר להשתמש ברמות הגישה המוגדרות מראש האלה כנקודת התחלה להגדרת בדיקות הרשאה מורכבות וחזקות בהנחיה @auth באמצעות מסנני where וביטויי Common Expression Language‏ (CEL) שמוערכים בשרת.

שימוש בהנחיה @auth להטמעה של תרחישי הרשאה נפוצים

רמות הגישה המוגדרות מראש הן נקודת ההתחלה להרשאה.

רמת הגישה USER היא רמת הגישה הבסיסית הכי שימושית להתחלה.

גישה מאובטחת לחלוטין תתבסס על רמה USER בתוספת מסננים וביטויים שיבדקו מאפייני משתמשים, מאפייני משאבים, תפקידים ובדיקות אחרות. הרמות USER_ANON ו-USER_EMAIL_VERIFIED הן וריאציות של המקרה USER.

תחביר הביטוי מאפשר להעריך נתונים באמצעות אובייקט auth שמייצג נתוני אימות שמועברים עם פעולות, גם נתונים רגילים באסימוני אימות וגם נתונים מותאמים אישית באסימונים. רשימת השדות שזמינים באובייקט auth מופיעה בקטע העזר.

כמובן שיש תרחישי שימוש שבהם PUBLIC היא רמת הגישה הנכונה להתחלה. שוב, רמת גישה היא תמיד נקודת התחלה, ונדרשים מסננים וביטויים נוספים כדי להשיג אבטחה חזקה.

במדריך הזה מובאות דוגמאות לאופן השימוש ב-USER וב-PUBLIC.

דוגמה מעוררת השראה

הדוגמאות הבאות לשיטות מומלצות מתייחסות לסכימה הבאה של פלטפורמת בלוגים עם תוכן מסוים שנעול מאחורי תוכנית תשלום.

פלטפורמה כזו כנראה תדמה את Users ואת Posts.

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

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

משאבים בבעלות המשתמש

במקרים הבאים, מומלץ ב-Firebase לכתוב מסננים וביטויים שבודקים את הבעלות של המשתמש על משאב, הבעלות על Posts.

בדוגמאות הבאות, נתונים מאסימוני אימות נקראים ומושווים באמצעות ביטויים. התבנית האופיינית היא להשתמש בביטויים כמו where: {authorUid: {eq_expr: "auth.uid"}} כדי להשוות בין authorUid מאוחסן לבין auth.uid (מזהה משתמש) שמועבר באסימון האימות.

יצירה

השיטה הזו לאימות מתחילה בהוספת auth.uid מאסימון האימות לכל Post חדש כשדה authorUid, כדי לאפשר השוואה בבדיקות אימות עוקבות.

# Create a new post as the current user
mutation CreatePost($text: String!, $visibility: String) @auth(level: USER) {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}
עדכון

כשלקוח מנסה לעדכן Post, אפשר לבדוק את auth.uid שעבר מול authorUid שמאוחסן.

# Update one of the current user's posts
mutation UpdatePost($id: UUID!, $text: String, $visibility: String) @auth(level:USER) {
  post_update(
    # only update posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
    data: {
      text: $text
      visibility: $visibility
      # insert the current server time for updatedAt
      updatedAt_expr: "request.time"
    }
  )
}
מחיקה

אותה טכניקה משמשת לאישור פעולות מחיקה.

# Delete one of the current user's posts
mutation DeletePost($id: UUID!) @auth(level: USER) {
  post_delete(
    # only delete posts whose author is the current user
    first: { where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}
    }}
  )
}
# Common display information for a post
fragment DisplayPost on Post {
  id, text, createdAt, updatedAt
  author { uid, name }
}
רשימה
# List all posts belonging to the current user
query ListMyPosts @auth(level: USER) {
  posts(where: {
    userUid: {eq_expr: "auth.uid"}
  }) {
    # See the fragment above
    ...DisplayPost
    # also show visibility since it is user-controlled
    visibility
  }
}
קבל
# Get a post only if it belongs to the current user
query GetMyPost($id: UUID!) @auth(level: USER) {
  post(key: {id: $id},
    first: {where: {
      id: {eq: $id}
      authorUid: {eq_expr: "auth.uid"}}
      }}, {
      # See the fragment above
      ...DisplayPost
      # also show visibility since it is user-controlled
      visibility
  }
}

סינון הנתונים

מערכת ההרשאות של Data Connect מאפשרת לכתוב מסננים מתוחכמים בשילוב עם רמות גישה מוגדרות מראש כמו PUBLIC, וגם באמצעות נתונים מאסימוני אימות.

מערכת ההרשאות מאפשרת גם להשתמש רק בביטויים, בלי רמת גישה בסיסית, כמו שמוצג בחלק מהדוגמאות הבאות.

סינון לפי מאפייני משאבים

במקרה הזה, ההרשאה לא מבוססת על טוקנים של אימות, כי רמת האבטחה הבסיסית מוגדרת כ-PUBLIC. אבל אנחנו יכולים להגדיר רשומות במסד הנתונים שלנו כמתאימות לגישה ציבורית. נניח שיש לנו Post רשומות במסד הנתונים עם visibility שמוגדר כ'ציבורי'.

# List all posts marked as 'public' visibility
query ListPublicPosts @auth(level: PUBLIC) {
  posts(where: {
    # Test that visibility is "public"
    visibility: {eq: "public"}
    # Only display articles that are already published
    publishedAt: {lt_expr: "request.time"}
  }) {
    # see the fragment above
    ...DisplayPost
  }
}
סינון לפי תלונות של משתמשים

נניח שהגדרתם הצהרות מותאמות אישית של משתמשים שמועברות באסימוני אימות כדי לזהות משתמשים בתוכנית 'מקצועית' באפליקציה שלכם, שמסומנים בשדה auth.token.plan באסימון האימות. אפשר לבדוק את הביטויים שלכם מול השדה הזה.

# List all public or pro posts, only permitted if user has "pro" plan claim
query ProListPosts @auth(expr: "auth.token.plan == 'pro'") {
  posts(where: {
    # display both public posts and "pro" posts
    visibility: {in: ['public', 'pro']},
    # only display articles that are already published
    publishedAt: {lt_expr: "request.time"},
  }) {
    # see the fragment above
    ...DisplayPost
    # show visibility so pro users can see which posts are pro\
    visibility
  }
}
סינון לפי הזמנה + הגבלה

לחלופין, יכול להיות שהגדרתם visibility ברשומות Post כדי לציין שהתוכן זמין למשתמשי 'פרו', אבל כדי להציג תצוגה מקדימה או טיזר של רשימת נתונים, אתם רוצים להגביל עוד יותר את מספר הרשומות שמוחזרות.

# Show 2 oldest Pro post as a preview
query ProTeaser @auth(level: USER) {
  posts(
    where: {
      # show only pro posts
      visibility: {eq: "pro"}
      # that have already been published more than 30 days ago
      publishedAt: {lt_time: {now: true, sub: {days: 30}}}
    },
    # order by publish time
    orderBy: [{publishedAt: DESC}],
    # only return two posts
    limit: 2
  ) {
    # See the fragment above
    ...DisplayPost
  }
}
סינון לפי תפקיד

אם התפקיד מוגדר בהצהרה בהתאמה אישית admin, אפשר לבדוק ולאשר פעולות בהתאם.

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

הוספת ההוראות @check ו-@redact כדי לחפש נתוני הרשאה

תרחיש נפוץ לשימוש בהרשאות כולל אחסון של תפקידי הרשאה בהתאמה אישית במסד הנתונים, למשל בטבלת הרשאות מיוחדת, ושימוש בתפקידים האלה כדי לאשר שינויים ליצירה, לעדכון או למחיקה של נתונים.

באמצעות חיפושים של נתוני הרשאות, אפשר לשלוח שאילתות לגבי תפקידים על סמך מזהה משתמש ולהשתמש בביטויי CEL כדי להחליט אם השינוי מורשה. לדוגמה, יכול להיות שתרצו לכתוב UpdateMovieTitle מוטציה שתאפשר ללקוח מורשה לעדכן את שמות הסרטים.

בהמשך הדיון הזה, נניח שמסד הנתונים של אפליקציית הביקורות על סרטים מאחסן תפקיד הרשאה בטבלה MoviePermission.

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

שימוש במוטציות

בדוגמה הבאה להטמעה, המוטציה UpdateMovieTitle כוללת את השדה query לאחזור נתונים מ-MoviePermission, ואת ההוראות הבאות כדי להבטיח שהפעולה תהיה מאובטחת ואמינה:

  • הוראה @transaction שמבטיחה שכל השאילתות והבדיקות של ההרשאות יושלמו או ייכשלו באופן אטומי.
  • ההנחיה @redact להשמטת תוצאות השאילתה מהתגובה, כלומר בדיקת ההרשאה שלנו מתבצעת בשרת Data Connect אבל נתונים רגישים לא נחשפים ללקוח.
  • זוג הנחיות @check להערכת לוגיקת ההרשאה בתוצאות של שאילתה, למשל בדיקה שלתעודת משתמש מסוימת יש תפקיד מתאים לביצוע שינויים.

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

שימוש בשאילתות

חיפושים של נתוני הרשאה שימושיים גם להגבלת שאילתות על סמך תפקידים או הגבלות אחרות.

בדוגמה הבאה, שגם משתמשת בסכימה MoviePermission, השאילתה בודקת אם למבקש יש תפקיד מתאים של 'אדמין' כדי להציג משתמשים שיכולים לערוך סרט.

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

דפוסי התנהגות שצריך להימנע מהם בהרשאות

בקטע הקודם מוסבר על דפוסים שכדאי לפעול לפיהם כשמשתמשים בהנחיית @auth.

חשוב גם להכיר אנטי-תבניות חשובות שכדאי להימנע מהן.

הימנעות מהעברת מזהים של מאפייני משתמשים ופרמטרים של טוקן אימות בארגומנטים של שאילתות ומוטציות

Firebase Authentication הוא כלי רב עוצמה להצגת תהליכי אימות וללכידה מאובטחת של נתוני אימות, כמו מזהי משתמשים רשומים ושדות רבים שמאוחסנים באסימוני אימות.

לא מומלץ להעביר מזהי משתמשים ונתוני טוקן אימות בארגומנטים של שאילתות ומוטציות.

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

מומלץ להימנע משימוש ברמת הגישה USER ללא מסננים

כפי שצוין כמה פעמים במדריך, רמות הגישה הבסיסיות כמו USER,‏ USER_ANON ו-USER_EMAIL_VERIFIED הן נקודות התחלה לבדיקות הרשאות, שאפשר לשפר באמצעות מסננים וביטויים. שימוש ברמות האלה ללא מסנן או ביטוי תואם שבודק איזה משתמש מבצע את הבקשה שקול לשימוש ברמה PUBLIC.

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

אל תשתמשו ברמת הגישה PUBLIC או USER ליצירת אב טיפוס

כדי לזרז את הפיתוח, יכול להיות שתתפתו להגדיר את כל הפעולות לרמת הגישה PUBLIC או לרמת הגישה USER בלי לבצע שיפורים נוספים, כדי לאשר את כל הפעולות ולאפשר לכם לבדוק את הקוד במהירות.

אחרי שיוצרים אב טיפוס ראשוני בדרך הזו, מתחילים לעבור מ-NO_ACCESS לאישור מוכן לייצור עם רמות PUBLIC ו-USER. עם זאת, אל תפרסו אותם כ-PUBLIC או כ-USER בלי להוסיף לוגיקה נוספת כמו שמוצג במדריך הזה.

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

לא כדאי לבסס הרשאה על כתובות אימייל לא מאומתות

הענקת גישה למשתמשים בדומיין מסוים היא דרך מצוינת להגביל את הגישה. עם זאת, כל אחד יכול לטעון שהוא הבעלים של כתובת אימייל במהלך הכניסה לחשבון. חשוב לוודא שאתם מעניקים גישה רק לכתובות אימייל שאומתו באמצעות Firebase Authentication.

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

מומלץ גם לבדוק את auth.token.email_verified

mutation CreatePost($text: String!, $visibility: String) @auth(expr: "auth.token.email_verified && auth.token.email.endsWith('@example.com')") {
  post_insert(data: {
    # set the author's uid to the current user uid
    authorUid_expr: "auth.uid"
    text: $text
    visibility: $visibility
  })
}

ביקורת על הרשאות באמצעות Firebase CLI

כמו שצוין קודם, רמות גישה מוגדרות מראש כמו PUBLIC ו-USER הן נקודת ההתחלה להרשאות חזקות, וצריך להשתמש בהן עם בדיקות הרשאה נוספות שמבוססות על מסננים וביטויים. אין להשתמש בהם לבד בלי לבחון בקפידה את תרחיש השימוש.

Data Connect עוזר לכם לבדוק את אסטרטגיית ההרשאות שלכם על ידי ניתוח קוד המחבר כשאתם מבצעים פריסה לשרת באמצעות firebase deploy מ-Firebase CLI. אתם יכולים להשתמש בביקורת הזו כדי לבדוק את בסיס הקוד.

כשפורסים את המחברים, ה-CLI יציג הערכות של קוד הפעולה הקיים, ששונה וחדש במחבר.

בפעולות חדשות ופעולות ששונו, ה-CLI מציג אזהרות ומבקש אישור כשמשתמשים ברמות גישה מסוימות בפעולות החדשות, או כשמשנים פעולות קיימות כדי להשתמש ברמות הגישה האלה.

אזהרות והנחיות תמיד יופיעו במקרים הבאים:

  • PUBLIC

בנוסף, אם לא מוסיפים מסננים לרמות הגישה הבאות באמצעות auth.uid, יוצגו אזהרות והנחיות:

  • USER
  • USER_ANON
  • USER_EMAIL_VERIFIED

הסתרת אזהרות לגבי פעולות לא מאובטחות באמצעות הארגומנט @auth(insecureReason:)

במקרים רבים, תגיעו למסקנה ששימוש ברמות הגישה PUBLIC ו-USER* מתאים מאוד.

אם המחבר מכיל הרבה פעולות, יכול להיות שתרצו פלט ברור יותר ורלוונטי יותר של ביקורת אבטחה, שבו לא נכללות פעולות שבדרך כלל מפעילות אזהרה, אבל אתם יודעים שיש להן את רמת הגישה הנכונה.

אפשר להשבית את האזהרות לפעולות כאלה באמצעות @auth(insecureReason:). לדוגמה:

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

שימוש ב-Firebase App Check לאימות אפליקציות

אימות והרשאה הם רכיבים קריטיים באבטחת Data Connect. השילוב של אימות והרשאה עם אימות אפליקציות יוצר פתרון אבטחה חזק מאוד.

בעזרת אימות באמצעות Firebase App Check, מכשירים שמופעלת בהם האפליקציה שלכם ישתמשו בספק אימות של אפליקציות או מכשירים, כדי לאמת שפעולות Data Connect מקורן באפליקציה המקורית שלכם ובקשות מקורן במכשיר מקורי שלא בוצעו בו שינויים. האישור הזה מצורף לכל בקשה שהאפליקציה שולחת אל Data Connect.

כדי ללמוד איך להפעיל את App Check עבור Data Connect ולכלול את ה-SDK של הלקוח באפליקציה, אפשר לעיין בסקירה הכללית של App Check.

רמות האימות של ההנחיה @auth(level)

בטבלה הבאה מפורטות כל רמות הגישה הרגילות והמקבילות שלהן ב-CEL. רמות האימות מפורטות מהרחבה ביותר למצומצמת ביותר – כל רמה כוללת את כל המשתמשים שתואמים לרמות הבאות.

רמה הגדרה
PUBLIC כל אחד יכול לבצע את הפעולה, עם או בלי אימות.

שיקולים: כל משתמש יכול לקרוא או לשנות את הנתונים. ב-Firebase ממליצים על רמת ההרשאה הזו לנתונים שגלויים לציבור, כמו כרטיסי מוצר או כרטיסי מדיה. אפשר לעיין בדוגמאות לשיטות מומלצות ולאפשרויות חלופיות.

שווה ל-@auth(expr: "true")

אי אפשר להשתמש במסננים ובביטויים של @auth בשילוב עם רמת הגישה הזו. כל הביטויים האלה ייכשלו עם שגיאת בקשה שגויה (400).
USER_ANON כל משתמש מזוהה, כולל משתמשים שהתחברו באופן אנונימי באמצעות Firebase Authentication, מורשה לבצע את השאילתה או את השינוי.

הערה: USER_ANON היא קבוצה שמכילה את USER.

שיקולים: חשוב לתכנן בקפידה את השאילתות והמוטציות שלכם לרמת ההרשאה הזו. ברמה הזו, המשתמש יכול להיכנס באופן אנונימי (כניסה אוטומטית שקשורה רק למכשיר של המשתמש) באמצעות Authentication, ולא מתבצעות בדיקות אחרות, למשל, אם הנתונים שייכים למשתמש. אפשר לעיין בדוגמאות ובחלופות לשיטות מומלצות.

מכיוון שבתהליכי התחברות אנונימיים מונפק uid, הרמה USER_ANON שווה ל-

@auth(expr: "auth.uid != nil")Authentication
USER כל משתמש שהתחבר באמצעות Firebase Authentication מורשה לבצע את השאילתה או את השינוי, למעט משתמשים שהתחברו באופן אנונימי.

שיקולים: חשוב לתכנן בקפידה את השאילתות והמוטציות שלכם לרמת ההרשאה הזו. ברמה הזו מתבצעת רק בדיקה שהמשתמש מחובר באמצעות Authentication, ולא מתבצעות בדיקות אחרות, למשל בדיקה אם הנתונים שייכים למשתמש. דוגמאות לשיטות מומלצות ואפשרויות חלופיות

שווה ערך ל-@auth(expr: "auth.uid != nil && auth.token.firebase.sign_in_provider != 'anonymous'")"
USER_EMAIL_VERIFIED כל משתמש שהתחבר באמצעות Firebase Authentication עם כתובת אימייל מאומתת מורשה לבצע את השאילתה או את השינוי.

נקודות למחשבה: אימות האימייל מתבצע באמצעות Authentication, ולכן הוא מבוסס על שיטה חזקה יותר של Authentication. לכן, הרמה הזו מספקת אבטחה נוספת בהשוואה ל-USER או ל-USER_ANON. ברמה הזו נבדק רק אם המשתמש מחובר ל-Authentication עם כתובת אימייל מאומתת, ולא מתבצעות בדיקות נוספות, למשל אם הנתונים שייכים למשתמש. אפשר לעיין בדוגמאות לשיטות מומלצות ולאפשרויות חלופיות.

שווה ערך ל-@auth(expr: "auth.uid != nil && auth.token.email_verified")"
NO_ACCESS אי אפשר לבצע את הפעולה הזו מחוץ להקשר של Admin SDK.

שווה ערך ל-@auth(expr: "false")

הפניה ל-CEL עבור @auth(expr)

כפי שמוצג בדוגמאות במקומות אחרים במדריך הזה, אפשר ומומלץ להשתמש בביטויים שמוגדרים ב-Common Expression Language ‏ (CEL) כדי לשלוט בהרשאה ל-Data Connect באמצעות ההוראות @auth(expr:) ו-@check.

בקטע הזה מוסבר על תחביר CEL שרלוונטי ליצירת ביטויים להוראות האלה.

מידע מלא על CEL זמין במפרט של CEL.

בדיקת משתנים שמועברים בשאילתות ובמוטציות

התחביר של @auth(expr) מאפשר לכם לגשת למשתנים משאילתות ומוטציות ולבדוק אותם.

לדוגמה, אפשר לכלול משתנה של פעולה, כמו $status, באמצעות vars.status.

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

נתונים שזמינים לביטויים: בקשה, תגובה, this

אתם משתמשים בנתונים כדי:

  • הערכה באמצעות ביטויי CEL בהנחיות @auth(expr:) ו-@check(expr:)
  • הקצאה באמצעות ביטויי שרת, <field>_expr.

שני ביטויי ה-CEL‏, @auth(expr:) ו-@check(expr:), יכולים להעריך את הדברים הבאים:

  • request.operationName
  • vars (כתובת אימייל חלופית של request.variables)
  • auth (כתובת אימייל חלופית של request.auth)

במוטציות, אפשר לגשת לתוכן של:

  • response (כדי לבדוק תוצאות חלקיות בלוגיקה מרובת שלבים)

בנוסף, ביטויים של @check(expr:) יכולים להעריך:

  • this (הערך של השדה הנוכחי)
  • response (כדי לבדוק תוצאות חלקיות בלוגיקה מרובת שלבים)

הקישור request.operationName

הקישור request.operarationName מאחסן את סוג הפעולה, שאלה או שינוי.

הקישור vars (request.vars)

הקישור vars מאפשר לביטויים שלכם לגשת לכל המשתנים שמועברים בשאילתה או במוטציה.

אפשר להשתמש ב-vars.<variablename> בביטוי בתור כינוי ל-request.variables.<variablename> המלא:

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

הקישור auth (request.auth)

Authentication מזהה משתמשים שמבקשים גישה לנתונים שלכם ומספק את המידע הזה כקישור שאפשר להשתמש בו בביטויים.

במסננים ובביטויים, אפשר להשתמש ב-auth ככינוי ל-request.auth.

הקישור של ההרשאה מכיל את הפרטים הבאים:

  • uid: מזהה משתמש ייחודי שהוקצה למשתמש ששלח את הבקשה.
  • token: מפה של ערכים שנאספו על ידי Authentication.

לפרטים נוספים על התוכן של auth.token אפשר לעיין במאמר נתונים באסימוני אימות

הקישור response

הקישור response מכיל את הנתונים שהשרת מרכיב בתגובה לשאילתה או לשינוי בזמן שהנתונים האלה מורכבים.

במהלך הפעולה, אחרי שכל שלב מסתיים בהצלחה, response מכיל נתוני תגובה משלבים שהושלמו בהצלחה.

הקישור response בנוי בהתאם לצורה של הפעולה המשויכת, כולל שדות מוטמעים (מרובים) ושאילתות מוטמעות (אם רלוונטי).

שימו לב: כשניגשים לנתוני תגובה של שאילתות מוטמעות, השדות יכולים להכיל כל סוג נתונים, בהתאם לנתונים שהתבקשו בשאילתה המוטמעת. כשניגשים לנתונים שמוחזרים על ידי שדות מוטציות כמו _inserts ו-_deletes, הם עשויים להכיל מפתחות UUID, מספר מחיקות, ערכי null (ראו את ההפניה למוטציות).

לדוגמה:

  • במוטציה שמכילה שאילתה מוטמעת, הכריכה response מכילה נתוני חיפוש ב-response.query.<fieldName>.<fieldName>...., ובמקרה הזה, ב-response.query.todoList וב-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
    }
  }
}
  • במוטציה מרובת שלבים, למשל עם כמה שדות _insert, הכריכה response מכילה נתונים חלקיים ב-response.<fieldName>.<fieldName>...., ובמקרה הזה, response.todoList_insert.id.
mutation CreateTodoListWithFirstItem(
  $listName: String!,
  $itemContent: String!
) @transaction {
  # Step 1
  todoList_insert(data: {
    id_expr: "uuidV4()",
    name: $listName,
  })
  # Step 2:
  todo_insert(data: {
    listId_expr: "response.todoList_insert.id" # <-- Grab the newly generated ID from the partial response so far.
    content: $itemContent,
  })
}

הקישור this

הקישור this מוערך לשדה שאליו מצורף ה-directive‏ @check. במקרה בסיסי, יכול להיות שתעריכו תוצאות של שאילתות עם ערך יחיד.

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

אם השדה שמוחזר מופיע כמה פעמים כי כל אחד מהשדות ברמת האב הוא רשימה, כל מופע נבדק עם this שקשור לכל ערך.

בכל נתיב נתון, אם רכיב אב הוא null או [], לא תהיה גישה לשדה והערכת ה-CEL תדלג על הנתיב הזה. במילים אחרות, ההערכה מתבצעת רק אם this הוא null או לא null, אבל אף פעם לא undefined.

אם השדה עצמו הוא רשימה או אובייקט, this פועל לפי אותו מבנה (כולל כל צאצא שנבחר במקרה של אובייקטים), כמו בדוגמה הבאה.

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

תחביר של ביטויים מורכבים

אפשר לכתוב ביטויים מורכבים יותר על ידי שילוב עם האופרטורים && ו-||.

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

בקטע הבא מתוארים כל האופרטורים הזמינים.

אופרטורים וקדימות אופרטורים

הטבלה הבאה משמשת כהפניה לאופרטורים ולסדר העדיפויות התואם שלהם.

בהינתן ביטויים שרירותיים a ו-b, שדה f ואינדקס i.

אופרטור תיאור אסוציאטיביות
a[i] a() a.f אינדקס, גישה לשיחות ולשדות משמאל לימין
!a -a שלילה אונרית מימין לשמאל
a/b a%b a*b אופרטורים של כפל משמאל לימין
a+b a-b אופרטורים של חיבור משמאל לימין
a>b a>=b a<b a<=b אופרטורים יחסיים משמאל לימין
a in b האם המקום מופיע ברשימה או במפה משמאל לימין
type(a) == t השוואת סוגים, כאשר t יכול להיות bool,‏ int,‏ float,‏ number,‏ string,‏ list,‏ map,‏ timestamp או duration משמאל לימין
a==b a!=b אופרטורים להשוואה משמאל לימין
a && b AND מותנה משמאל לימין
a || b OR מותנה משמאל לימין
a ? true_value : false_value ביטוי טרנרי משמאל לימין

נתונים בטוקנים של אימות

האובייקט auth.token יכול להכיל את הערכים הבאים:

שדה תיאור
email כתובת האימייל שמשויכת לחשבון, אם יש כזו.
email_verified true אם המשתמש אימת שיש לו גישה לכתובת email. חלק מהספקים מאמתים אוטומטית כתובות אימייל שנמצאות בבעלותם.
phone_number מספר הטלפון שמשויך לחשבון, אם יש כזה.
name השם המוצג של המשתמש, אם הוא מוגדר.
sub מזהה המשתמש ב-Firebase. הערך הזה ייחודי במסגרת פרויקט.
firebase.identities מילון של כל הזהויות שמשויכות לחשבון של המשתמש הזה. המפתחות של המילון יכולים להיות כל אחד מהערכים הבאים: email, ‏phone, ‏google.com, ‏facebook.com, ‏github.com, ‏twitter.com. הערכים במילון הם מערכים של מזהים ייחודיים לכל ספק זהויות שמשויך לחשבון. לדוגמה, auth.token.firebase.identities["google.com"][0] מכיל את מזהה המשתמש הראשון ב-Google שמשויך לחשבון.
firebase.sign_in_provider ספק הכניסה ששימש לקבלת האסימון הזה. יכול להיות אחת מהמחרוזות הבאות: custom, ‏ password, ‏ phone, ‏ anonymous, ‏ google.com, ‏ facebook.com, ‏ github.com, ‏ twitter.com.
firebase.tenant מזהה הדייר שמשויך לחשבון, אם קיים. לדוגמה, tenant2-m6tyz

שדות נוספים באסימוני JWT מזהים

אפשר גם לגשת לשדות הבאים של auth.token:

הצהרות טוקן בהתאמה אישית
alg אלגוריתם "RS256"
iss הונפק על ידי: כתובת האימייל של חשבון השירות בפרויקט
sub נושא כתובת האימייל של חשבון השירות בפרויקט
aud קהל "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit"
iat הזמן שבו הונפק האסימון הזמן הנוכחי, בשניות מאז ראשית זמן יוניקס (Unix epoch)
exp מועד תפוגה הזמן שבו יפוג תוקף האסימון, בשניות מאז ראשית זמן יוניקס (Unix epoch). התאריך יכול להיות עד 3,600 שניות אחרי התאריך iat.
הערה: ההגדרה הזו קובעת רק את הזמן שבו יפוג התוקף של האסימון המותאם אישית עצמו. אבל אחרי שמתחברים לחשבון של משתמש באמצעות signInWithCustomToken(), הוא יישאר מחובר למכשיר עד שהסשן שלו יבוטל או עד שהמשתמש יתנתק.
ֶ<claims> (אופציונלי) טענות אופציונליות בהתאמה אישית שייכללו בטוקן, שאפשר לגשת אליהן דרך auth.token (או request.auth.token) בביטויים. לדוגמה, אם יוצרים טענה מותאמת אישית adminClaim, אפשר לגשת אליה באמצעות auth.token.adminClaim.

מה השלב הבא?