ב-Firebase אתם יכולים לאמת משתמשים או מכשירים באמצעות אסימוני אינטרנט מאובטחים מסוג JSON (JWT), וכך אתם מקבלים שליטה מלאה על האימות. אתם יוצרים את האסימונים האלה בשרת, מעבירים אותם בחזרה למכשיר הלקוח ואז משתמשים בהם כדי לאמת באמצעות השיטה signInWithCustomToken().
כדי לעשות את זה, צריך ליצור נקודת קצה בשרת שמקבלת פרטי כניסה, כמו שם משתמש וסיסמה, ואם פרטי הכניסה תקינים, מחזירה אסימון JWT בהתאמה אישית. לאחר מכן, מכשיר לקוח יכול להשתמש ב-JWT המותאם אישית שהוחזר מהשרת כדי לבצע אימות באמצעות Firebase (iOS+, Android, אינטרנט). אחרי האימות, הזהות הזו תשמש לגישה לשירותים אחרים של Firebase, כמו Firebase Realtime Database ו-Cloud Storage. בנוסף, התוכן של ה-JWT יהיה זמין באובייקט auth ב-Realtime Database Security Rules ובאובייקט request.auth ב-Cloud Storage Security Rules.
אתם יכולים ליצור אסימון בהתאמה אישית באמצעות SDK של Firebase לאדמינים, או להשתמש בספריית JWT של צד שלישי אם השרת שלכם כתוב בשפה ש-Firebase לא תומכת בה באופן מקורי.
לפני שמתחילים
אסימונים בהתאמה אישית הם אסימוני JWT חתומים, שבהם המפתח הפרטי שמשמש לחתימה שייך לחשבון שירות של Google. יש כמה דרכים לציין את חשבון השירות של Google שבו ה-SDK של Firebase לאדמינים צריך להשתמש כדי לחתום על טוקנים בהתאמה אישית:
- שימוש בקובץ JSON של חשבון שירות – אפשר להשתמש בשיטה הזו בכל סביבה, אבל צריך לארוז קובץ JSON של חשבון שירות יחד עם הקוד. חשוב לוודא שקובץ ה-JSON של חשבון השירות לא נחשף לגורמים חיצוניים.
- זיהוי של חשבון שירות באמצעות SDK לאדמינים – אפשר להשתמש בשיטה הזו בסביבות שמנוהלות על ידי Google, כמו Cloud Functions ו-App Engine. יכול להיות שתצטרכו להגדיר הרשאות נוספות במסוף Google Cloud.
- שימוש במזהה של חשבון שירות – כשמשתמשים בשיטה הזו בסביבה שמנוהלת על ידי Google, היא חותמת על טוקנים באמצעות המפתח של חשבון השירות שצוין. עם זאת, הוא משתמש בשירות אינטרנט מרוחק, ויכול להיות שתצטרכו להגדיר הרשאות נוספות לחשבון השירות הזה דרך מסוף Google Cloud.
שימוש בקובץ JSON של חשבון שירות
קובצי JSON של חשבונות שירות מכילים את כל המידע שקשור לחשבונות שירות (כולל המפתח הפרטי של RSA). אפשר להוריד אותם ממסוף Firebase. כדי לקבל מידע נוסף על האופן שבו מאתחלים את SDK לאדמינים באמצעות קובץ JSON של חשבון שירות, פועלים לפי ההוראות להגדרת SDK לאדמינים.
שיטת האיניציאליזציה הזו מתאימה למגוון רחב של פריסות של SDK לאדמינים. הוא גם מאפשר ל-SDK לאדמינים ליצור ולחתום על אסימונים בהתאמה אישית באופן מקומי, בלי לבצע קריאות ל-API מרחוק. החיסרון העיקרי בגישה הזו הוא שצריך לארוז קובץ JSON של חשבון שירות יחד עם הקוד. חשוב גם לזכור שהמפתח הפרטי בקובץ ה-JSON של חשבון השירות הוא מידע רגיש, וצריך להקפיד לשמור על הסודיות שלו. באופן ספציפי, אל תוסיפו קובצי JSON של חשבונות שירות לניהול גרסאות ציבורי.
איך מאפשרים ל-SDK לאדמינים לגלות חשבון שירות
אם הקוד שלכם פרוס בסביבה שמנוהלת על ידי Google, ה-SDK לאדמינים יכול לנסות לגלות באופן אוטומטי אמצעי לחתימה על אסימונים בהתאמה אישית:
אם הקוד שלכם פרוס בסביבה הרגילה של App Engine ל-Java, ל-Python או ל-Go, ה-SDK לאדמינים יכול להשתמש בשירות App Identity שקיים בסביבה הזו כדי לחתום על טוקנים בהתאמה אישית. שירות App Identity חותם על נתונים באמצעות חשבון שירות שהוקצה לאפליקציה על ידי Google App Engine.
אם הקוד שלכם פרוס בסביבה מנוהלת אחרת (למשל, Google Cloud Functions או Google Compute Engine), ה-SDK של Firebase לאדמינים יכול לגלות באופן אוטומטי מחרוזת של מזהה חשבון שירות משרת המטא-נתונים המקומי. מזהה חשבון השירות שנמצא משמש בשילוב עם שירות IAM כדי לחתום על אסימונים מרחוק.
כדי להשתמש בשיטות החתימה האלה, צריך להפעיל את ה-SDK עם פרטי הכניסה שמוגדרים כברירת מחדל לאפליקציה (ADC) של Google, ולא לציין מחרוזת של מזהה חשבון שירות:
Node.js
initializeApp();
Java
FirebaseApp.initializeApp();
Python
default_app = firebase_admin.initialize_app()
Go
app, err := firebase.NewApp(context.Background(), nil)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
C#
FirebaseApp.Create();
כדי לבדוק את אותו קוד באופן מקומי, צריך להוריד קובץ JSON של חשבון שירות ולהגדיר את משתנה הסביבה GOOGLE_APPLICATION_CREDENTIALS כך שיצביע עליו.
אם ה-SDK של Firebase לאדמינים צריך לגלות מחרוזת של מזהה חשבון שירות, הוא עושה את זה כשהקוד יוצר טוקן בהתאמה אישית בפעם הראשונה. התוצאה נשמרת במטמון ומשמשת לפעולות הבאות של חתימת טוקנים. מזהה חשבון השירות שמתגלה באופן אוטומטי הוא בדרך כלל אחד מחשבונות השירות שמוגדרים כברירת מחדל ומסופקים על ידי Google Cloud:
כמו במקרה של מזהי חשבונות שירות שצוינו במפורש, גם למזהי חשבונות שירות שזוהו באופן אוטומטי צריכה להיות ההרשאה iam.serviceAccounts.signBlob כדי שיצירת הטוקן בהתאמה אישית תפעל. יכול להיות שתצטרכו להשתמש בקטע IAM and admin במסוף Google Cloud כדי לתת לחשבונות השירות שמוגדרים כברירת מחדל את ההרשאות הנדרשות. מידע נוסף זמין בסעיף הבא, שעוסק בפתרון בעיות.
שימוש במזהה חשבון שירות
כדי לשמור על עקביות בין החלקים השונים של האפליקציה, אתם יכולים לציין מזהה של חשבון שירות שהמפתחות שלו ישמשו לחתימה על אסימונים בזמן הפעלה בסביבה שמנוהלת על ידי Google. כך אפשר לפשט את מדיניות ה-IAM ולהפוך אותה למאובטחת יותר, וגם להימנע מהצורך לכלול בקוד את קובץ ה-JSON של חשבון השירות.
מזהה חשבון השירות מופיע במסוף Google Cloud או בשדה client_email בקובץ ה-JSON של חשבון השירות שהורדתם.
המזהים של חשבונות השירות הם כתובות אימייל בפורמט הבא: <client-id>@<project-id>.iam.gserviceaccount.com. הם מזהים באופן ייחודי חשבונות שירות ב-Firebase ובפרויקטים ב-Google Cloud.
כדי ליצור אסימונים בהתאמה אישית באמצעות מזהה נפרד של חשבון שירות, צריך להפעיל את ה-SDK כמו שמוצג בהמשך:
Node.js
initializeApp({
serviceAccountId: 'my-client-id@my-project-id.iam.gserviceaccount.com',
});
Java
FirebaseOptions options = FirebaseOptions.builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setServiceAccountId("my-client-id@my-project-id.iam.gserviceaccount.com")
.build();
FirebaseApp.initializeApp(options);
Python
options = {
'serviceAccountId': 'my-client-id@my-project-id.iam.gserviceaccount.com',
}
firebase_admin.initialize_app(options=options)
Go
conf := &firebase.Config{
ServiceAccountID: "my-client-id@my-project-id.iam.gserviceaccount.com",
}
app, err := firebase.NewApp(context.Background(), conf)
if err != nil {
log.Fatalf("error initializing app: %v\n", err)
}
C#
FirebaseApp.Create(new AppOptions()
{
Credential = GoogleCredential.GetApplicationDefault(),
ServiceAccountId = "my-client-id@my-project-id.iam.gserviceaccount.com",
});
מזהי חשבונות שירות הם לא מידע רגיש, ולכן אין משמעות לחשיפה שלהם. עם זאת, כדי לחתום על טוקנים בהתאמה אישית באמצעות חשבון השירות שצוין, ה-SDK של Firebase לאדמינים צריך להפעיל שירות מרוחק.
בנוסף, צריך לוודא שלחשבון השירות שבו משתמש ה-SDK לאדמינים כדי לבצע את הקריאה הזו (בדרך כלל {project-name}@appspot.gserviceaccount.com) יש את ההרשאה iam.serviceAccounts.signBlob.
מידע נוסף זמין בסעיף הבא, שעוסק בפתרון בעיות.
יצירת טוקנים בהתאמה אישית באמצעות SDK של Firebase לאדמינים
ל-SDK של Firebase לאדמינים יש שיטה מובנית ליצירת טוקנים בהתאמה אישית. לכל הפחות, צריך לספק uid, שיכול להיות כל מחרוזת אבל צריך לזהות באופן ייחודי את המשתמש או המכשיר שאתם מאמתים. התוקף של האסימונים האלה פג אחרי שעה אחת.
Node.js
const uid = 'some-uid';
getAuth()
.createCustomToken(uid)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
Java
String uid = "some-uid";
String customToken = FirebaseAuth.getInstance().createCustomToken(uid);
// Send token back to client
Python
uid = 'some-uid'
custom_token = auth.create_custom_token(uid)
Go
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.CustomToken(ctx, "some-uid")
if err != nil {
log.Fatalf("error minting custom token: %v\n", err)
}
log.Printf("Got custom token: %v\n", token)
C#
var uid = "some-uid";
string customToken = await FirebaseAuth.DefaultInstance.CreateCustomTokenAsync(uid);
// Send token back to client
אפשר גם לציין הצהרות נוספות שייכללו בטוקן בהתאמה אישית. לדוגמה, בדוגמה הבאה, השדה premiumAccount נוסף לאסימון בהתאמה אישית, שיהיה זמין באובייקטים auth / request.auth בכללי האבטחה:
Node.js
const userId = 'some-uid';
const additionalClaims = {
premiumAccount: true,
};
getAuth()
.createCustomToken(userId, additionalClaims)
.then((customToken) => {
// Send token back to client
})
.catch((error) => {
console.log('Error creating custom token:', error);
});
Java
String uid = "some-uid";
Map<String, Object> additionalClaims = new HashMap<String, Object>();
additionalClaims.put("premiumAccount", true);
String customToken = FirebaseAuth.getInstance()
.createCustomToken(uid, additionalClaims);
// Send token back to client
Python
uid = 'some-uid'
additional_claims = {
'premiumAccount': True
}
custom_token = auth.create_custom_token(uid, additional_claims)
Go
client, err := app.Auth(context.Background())
if err != nil {
log.Fatalf("error getting Auth client: %v\n", err)
}
claims := map[string]interface{}{
"premiumAccount": true,
}
token, err := client.CustomTokenWithClaims(ctx, "some-uid", claims)
if err != nil {
log.Fatalf("error minting custom token: %v\n", err)
}
log.Printf("Got custom token: %v\n", token)
C#
var uid = "some-uid";
var additionalClaims = new Dictionary<string, object>()
{
{ "premiumAccount", true },
};
string customToken = await FirebaseAuth.DefaultInstance
.CreateCustomTokenAsync(uid, additionalClaims);
// Send token back to client
שמות שמורים של אסימונים מותאמים אישית
כניסה באמצעות אסימונים בהתאמה אישית בלקוחות
אחרי שיוצרים טוקן בהתאמה אישית, צריך לשלוח אותו לאפליקציית הלקוח. אפליקציית הלקוח מבצעת אימות באמצעות הטוקן בהתאמה אישית על ידי קריאה ל-signInWithCustomToken():
iOS+
Objective-C
[[FIRAuth auth] signInWithCustomToken:customToken
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
// ...
}];
Swift
Auth.auth().signIn(withCustomToken: customToken ?? "") { user, error in
// ...
}
Android
mAuth.signInWithCustomToken(mCustomToken)
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCustomToken:success");
FirebaseUser user = mAuth.getCurrentUser();
updateUI(user);
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCustomToken:failure", task.getException());
Toast.makeText(CustomAuthActivity.this, "Authentication failed.",
Toast.LENGTH_SHORT).show();
updateUI(null);
}
}
});
Unity
auth.SignInWithCustomTokenAsync(custom_token).ContinueWith(task => {
if (task.IsCanceled) {
Debug.LogError("SignInWithCustomTokenAsync was canceled.");
return;
}
if (task.IsFaulted) {
Debug.LogError("SignInWithCustomTokenAsync encountered an error: " + task.Exception);
return;
}
Firebase.Auth.AuthResult result = task.Result;
Debug.LogFormat("User signed in successfully: {0} ({1})",
result.User.DisplayName, result.User.UserId);
});
C++
firebase::Future<firebase::auth::AuthResult> result =
auth->SignInWithCustomToken(custom_token);
Web
firebase.auth().signInWithCustomToken(token)
.then((userCredential) => {
// Signed in
var user = userCredential.user;
// ...
})
.catch((error) => {
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
Web
import { getAuth, signInWithCustomToken } from "firebase/auth";
const auth = getAuth();
signInWithCustomToken(auth, token)
.then((userCredential) => {
// Signed in
const user = userCredential.user;
// ...
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
// ...
});
אם האימות יצליח, המשתמש ייכנס לאפליקציית הלקוח באמצעות החשבון שצוין ב-uid שכלול בטוקן בהתאמה אישית. אם החשבון הזה לא היה קיים קודם, תיווצר רשומה של המשתמש הזה.
בדומה לשיטות כניסה אחרות (כמו signInWithEmailAndPassword() ו-signInWithCredential()), האובייקט auth ב-Realtime Database Security Rules והאובייקט request.auth ב-Cloud Storage Security Rules יאוכלסו ב-uid של המשתמש. במקרה כזה, הערך של uid יהיה הערך שציינתם כשיצרתם את הטוקן בהתאמה אישית.
כללי מסד נתונים
{
"rules": {
"adminContent": {
".read": "auth.uid === 'some-uid'"
}
}
}
כללי אחסון
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /adminContent/{filename} {
allow read, write: if request.auth != null && request.auth.uid == "some-uid";
}
}
}
אם הטוקן המותאם אישית מכיל הצהרות נוספות, אפשר להפנות אליהן מהאובייקט auth.token (Firebase Realtime Database) או request.auth.token (Cloud Storage) בכללים:
כללי מסד נתונים
{
"rules": {
"premiumContent": {
".read": "auth.token.premiumAccount === true"
}
}
}
כללי אחסון
service firebase.storage {
match /b/<your-firebase-storage-bucket>/o {
match /premiumContent/{filename} {
allow read, write: if request.auth.token.premiumAccount == true;
}
}
}
יצירת אסימונים בהתאמה אישית באמצעות ספריית JWT של צד שלישי
אם ה-backend שלכם כתוב בשפה שאין לה SDK רשמי של Firebase לאדמינים, אתם עדיין יכולים ליצור אסימונים בהתאמה אישית באופן ידני. קודם כול, מוצאים ספריית JWT של צד שלישי בשפה שלכם. לאחר מכן, משתמשים בספריית ה-JWT הזו כדי ליצור JWT שכולל את ההצהרות הבאות:
| הצהרות טוקן בהתאמה אישית | ||
|---|---|---|
alg |
אלגוריתם | "RS256" |
iss |
המוסד המנפיק | כתובת האימייל של חשבון השירות של הפרויקט |
sub |
נושא | כתובת האימייל של חשבון השירות של הפרויקט |
aud |
קהל | "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit" |
iat |
שעת ההנפקה | השעה הנוכחית, בשניות מאז ראשית זמן יוניקס (Unix epoch) |
exp |
מועד תפוגה |
הזמן, בשניות מאז ראשית זמן יוניקס (Unix epoch), שבו פג תוקף האסימון. הוא יכול להיות עד 3,600 שניות אחרי iat.
הערה: ההגדרה הזו קובעת רק את הזמן שבו יפוג התוקף של האסימון בהתאמה אישית עצמו. אבל אחרי שמשתמש נכנס באמצעות signInWithCustomToken(), הוא נשאר מחובר למכשיר עד שהסשן שלו מבוטל או עד שהוא יוצא מהחשבון.
|
uid |
המזהה הייחודי של המשתמש המחובר חייב להיות מחרוזת באורך של 1-128 תווים, כולל. ככל ש-uid קצר יותר,
הביצועים טובים יותר.
|
|
ֶclaims (אופציונלי) |
הצהרות אופציונליות בהתאמה אישית שייכללו במשתנים auth / request.auth של כללי האבטחה
|
|
הנה כמה דוגמאות להטמעות של יצירת אסימונים בהתאמה אישית במגוון שפות שלא נתמכות ב-SDK של Firebase לאדמינים:
PHP
באמצעות php-jwt:
// Requires: composer require firebase/php-jwt
use Firebase\JWT\JWT;
// Get your service account's email address and private key from the JSON key file
$service_account_email = "abc-123@a-b-c-123.iam.gserviceaccount.com";
$private_key = "-----BEGIN PRIVATE KEY-----...";
function create_custom_token($uid, $is_premium_account) {
global $service_account_email, $private_key;
$now_seconds = time();
$payload = array(
"iss" => $service_account_email,
"sub" => $service_account_email,
"aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iat" => $now_seconds,
"exp" => $now_seconds+(60*60), // Maximum expiration time is one hour
"uid" => $uid,
"claims" => array(
"premium_account" => $is_premium_account
)
);
return JWT::encode($payload, $private_key, "RS256");
}
Ruby
באמצעות ruby-jwt:
require "jwt"
# Get your service account's email address and private key from the JSON key file
$service_account_email = "service-account@my-project-abc123.iam.gserviceaccount.com"
$private_key = OpenSSL::PKey::RSA.new "-----BEGIN PRIVATE KEY-----\n..."
def create_custom_token(uid, is_premium_account)
now_seconds = Time.now.to_i
payload = {:iss => $service_account_email,
:sub => $service_account_email,
:aud => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
:iat => now_seconds,
:exp => now_seconds+(60*60), # Maximum expiration time is one hour
:uid => uid,
:claims => {:premium_account => is_premium_account}}
JWT.encode payload, $private_key, "RS256"
end
אחרי שיוצרים את הטוקן בהתאמה אישית, שולחים אותו לאפליקציית הלקוח כדי להשתמש בו לאימות באמצעות Firebase. אפשר לראות איך עושים את זה בדוגמאות הקוד שלמעלה.
פתרון בעיות
בקטע הזה מתוארות כמה בעיות נפוצות שמפתחים עלולים להיתקל בהן כשהם יוצרים אסימונים בהתאמה אישית, ואיך לפתור אותן.
IAM API לא מופעל
אם אתם מציינים מזהה של חשבון שירות לחתימה על אסימונים, יכול להיות שתקבלו שגיאה דומה לזו:
Identity and Access Management (IAM) API has not been used in project 1234567890 before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/iam.googleapis.com/overview?project=1234567890 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.
ה-SDK של Firebase לאדמינים משתמש ב-IAM API כדי לחתום על טוקנים. השגיאה הזו מציינת ש-IAM API לא מופעל כרגע בפרויקט ב-Firebase. פותחים את הקישור שבהודעת השגיאה בדפדפן אינטרנט ולוחצים על הכפתור Enable API כדי להפעיל אותו בפרויקט.
לחשבון השירות אין את ההרשאות הנדרשות
אם לחשבון השירות שבו פועל ה-SDK של Firebase לאדמינים אין את ההרשאה iam.serviceAccounts.signBlob, יכול להיות שתופיע הודעת שגיאה כמו זו:
Permission iam.serviceAccounts.signBlob is required to perform this operation
on service account projects/-/serviceAccounts/{your-service-account-id}.
כדי לפתור את הבעיה, צריך להקצות את התפקיד יצירת אסימונים בחשבון שירות ב-IAM לחשבון השירות המתאים. חשבון השירות שמוגדר כברירת מחדל משתנה בהתאם לסביבה ולגרסה של Cloud Functions:
- Cloud Functions (דור ראשון): משתמש בחשבון השירות שמוגדר כברירת מחדל ב-App Engine.
- Cloud Functions (דור שני): משתמש בחשבון השירות שמוגדר כברירת מחדל ב-Compute Engine.
במסוף Google Cloud, נכנסים לדף IAM and Admin.
לוחצים על סמל העריכה של חשבון השירות שרוצים לעדכן.
לוחצים על הוספת תפקיד.
מקלידים
"Service Account Token Creator"במסנן החיפוש ובוחרים את האפשרות הזו מהתוצאות.לוחצים על Save כדי לאשר את הקצאת התפקיד.
לפרטים נוספים על התהליך הזה, אפשר לעיין במסמכי התיעוד של IAM או ללמוד איך לעדכן תפקידים באמצעות כלי שורת הפקודה של Google Cloud.
לא הצלחנו לזהות את חשבון השירות
אם מופיעה הודעת שגיאה דומה לזו, סימן שה-SDK של Firebase לאדמינים לא אותחל כמו שצריך.
Failed to determine service account ID. Initialize the SDK with service account credentials or specify a service account ID with iam.serviceAccounts.signBlob permission.
אם אתם מסתמכים על ה-SDK כדי לגלות באופן אוטומטי מזהה של חשבון שירות, ודאו שהקוד פרוס בסביבה מנוהלת של Google עם שרת מטא-נתונים. אחרת, צריך לציין את קובץ ה-JSON של חשבון השירות או את מזהה חשבון השירות במהלך האתחול של ה-SDK.