Firebase מאפשרת לכם לאמת משתמשים או מכשירים באמצעות אסימוני JWT (JSON Web Tokens) מאובטחים, וכך נותנת לכם שליטה מלאה על תהליך האימות. אתם יוצרים את האסימונים האלה בשרת, מעבירים אותם חזרה למכשיר הלקוח ומשתמשים בהם כדי לבצע אימות באמצעות השיטה signInWithCustomToken()
.
כדי לעשות זאת, צריך ליצור נקודת קצה של שרת שמקבלת פרטי כניסה – כמו שם משתמש וסיסמה – ואם פרטי הכניסה תקינים, מחזירה JWT בהתאמה אישית. לאחר מכן, מכשיר לקוח יכול להשתמש ב-JWT המותאם אישית שהוחזר מהשרת כדי לבצע אימות מול Firebase (iOS+, Android, אינטרנט). אחרי האימות, הזהות הזו תשמש לגישה לשירותים אחרים של Firebase, כמו Firebase Realtime Database ו-Cloud Storage. בנוסף, תוכן ה-JWT יהיה זמין באובייקט auth
ב-Realtime Database Security Rules ובאובייקט request.auth
ב-Cloud Storage Security Rules.
אפשר ליצור אסימון מותאם אישית באמצעות Firebase Admin SDK, או להשתמש בספריית JWT של צד שלישי אם השרת נכתב בשפה שאין לה תמיכה מובנית ב-Firebase.
לפני שמתחילים
אסימונים בהתאמה אישית הם אסימוני JWT חתומים, שבהם המפתח הפרטי שמשמש לחתימה שייך לחשבון שירות ב-Google. יש כמה דרכים לציין את חשבון השירות של Google שבו ה-SDK של Firebase Admin ישתמש כדי לחתום על אסימונים מותאמים אישית:
- שימוש בקובץ JSON של חשבון שירות – אפשר להשתמש בשיטה הזו בכל סביבה, אבל צריך לארוז קובץ JSON של חשבון שירות יחד עם הקוד. חשוב מאוד לוודא שקובץ ה-JSON של חשבון השירות לא נחשף לגורמים חיצוניים.
- איך מאפשרים ל-Admin SDK לגלות חשבון שירות – אפשר להשתמש בשיטה הזו בסביבות שמנוהלות על ידי Google, כמו Google Cloud Functions ו-App Engine. יכול להיות שתצטרכו להגדיר הרשאות נוספות דרך מסוף Google Cloud.
- שימוש במזהה של חשבון שירות – כשמשתמשים בשיטה הזו בסביבה בניהול Google, האסימונים ייחתמו באמצעות המפתח של חשבון השירות שצוין. עם זאת, הוא משתמש בשירות אינטרנט מרוחק, ויכול להיות שתצטרכו להגדיר הרשאות נוספות לחשבון השירות הזה דרך מסוף Google Cloud.
באמצעות קובץ JSON של חשבון שירות
קובצי JSON של חשבונות שירות מכילים את כל המידע שתואם לחשבונות השירות (כולל המפתח הפרטי של RSA). אפשר להוריד אותם מהמסוף Firebase. בהוראות ההגדרה של Admin SDK מוסבר איך לאתחל את Admin SDK באמצעות קובץ JSON של חשבון שירות.
שיטת האיניציאציה הזו מתאימה למגוון רחב של פריסות של Admin SDK. בנוסף, הוא מאפשר ל-Admin SDK ליצור אסימונים בהתאמה אישית ולחתום עליהם באופן מקומי, בלי לבצע קריאות מרחוק ל-API. החיסרון העיקרי של הגישה הזו הוא שצריך לארוז קובץ JSON של חשבון שירות יחד עם הקוד. חשוב לזכור שהמפתח הפרטי בקובץ ה-JSON של חשבון השירות הוא מידע רגיש, ויש להקפיד במיוחד על סודיותו. באופן ספציפי, מומלץ להימנע מהוספת קובצי JSON של חשבונות שירות למערכת בקרת גרסאות ציבורית.
איך מאפשרים ל-Admin SDK לזהות חשבון שירות
אם הקוד שלכם פרוס בסביבה שמנוהלת על ידי Google, ה-Admin SDK יכול לנסות לגלות באופן אוטומטי דרך לחתום על אסימונים מותאמים אישית:
אם הקוד שלכם פרוס בסביבה הרגילה App Engine ל-Java, ל-Python או ל-Go, ה-Admin SDK יכול להשתמש בשירות אימות הזהות של האפליקציה שנמצא בסביבה הזו כדי לחתום על אסימונים מותאמים אישית. שירות App Identity חותם על נתונים באמצעות חשבון שירות שהוקצה לאפליקציה על ידי Google App Engine.
אם הקוד שלכם נפרס בסביבה מנוהלת אחרת (למשל Google Cloud Functions או Google Compute Engine), Firebase Admin SDK יכול לזהות באופן אוטומטי מחרוזת מזהה של חשבון שירות משרת המטא-נתונים המקומי. לאחר מכן, מזהה חשבון השירות שנתגלה משמש בשילוב עם שירות IAM כדי לחתום על אסימונים מרחוק.
כדי להשתמש בשיטות החתימה האלה, צריך לאתחל את ה-SDK באמצעות פרטי הכניסה של Application Default של 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 Admin צריך לגלות מחרוזת מזהה של חשבון שירות, הוא עושה זאת כשהקוד יוצר אסימון מותאם אישית בפעם הראשונה. התוצאה מאוחסנת במטמון ומשמשת לפעולות חתימה על אסימונים עתידיות. מזהה חשבון השירות שזוהה באופן אוטומטי הוא בדרך כלל אחד מחשבונות השירות שמוגדרים כברירת מחדל ב-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",
});
מזהי חשבונות השירות הם לא מידע רגיש, ולכן חשיפת המזהים לא משמעותית. עם זאת, כדי לחתום על אסימונים בהתאמה אישית באמצעות חשבון השירות שצוין, Firebase Admin SDK צריך להפעיל שירות מרוחק.
בנוסף, צריך לוודא שלחשבון השירות שבו Admin SDK משתמש כדי לבצע את הקריאה הזו – בדרך כלל {project-name}@appspot.gserviceaccount.com
– יש את ההרשאה iam.serviceAccounts.signBlob
.
פרטים נוספים זמינים בקטע 'פתרון בעיות' שבהמשך.
יצירת אסימונים מותאמים אישית באמצעות Firebase Admin SDK
ל-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 של צד שלישי
אם הקצה העורפי שלכם נכתב בשפה שאין לה SDK רשמי של Firebase Admin, עדיין תוכלו ליצור אסימונים מותאמים אישית באופן ידני. בשלב הראשון, מחפשים ספריית 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 של כללי האבטחה
|
ריכזנו כאן כמה דוגמאות להטמעה של יצירת אסימונים מותאמים אישית במגוון שפות שלא נתמכות ב-Firebase Admin SDK:
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 שלכם. פותחים את הקישור שמופיע בהודעת השגיאה בדפדפן אינטרנט ולוחצים על הלחצן 'הפעלת ה-API' כדי להפעיל אותו בפרויקט.
לחשבון השירות אין את ההרשאות הנדרשות
אם לחשבון השירות שבו פועל Firebase Admin SDK אין את ההרשאה iam.serviceAccounts.signBlob
, עשויה להופיע הודעת שגיאה כמו זו:
Permission iam.serviceAccounts.signBlob is required to perform this operation on service account projects/-/serviceAccounts/{your-service-account-id}.
הדרך הקלה ביותר לפתור את הבעיה היא להקצות לחשבון השירות הרלוונטי את התפקיד 'יצירת אסימונים בחשבון שירות' ב-IAM, בדרך כלל {project-name}@appspot.gserviceaccount.com
:
- פותחים את הדף IAM and admin במסוף Google Cloud.
- בוחרים את הפרויקט ולוחצים על 'המשך'.
- לוחצים על סמל העריכה של חשבון השירות שרוצים לעדכן.
- לוחצים על 'הוספת תפקיד נוסף'.
- מקלידים 'יצירת אסימונים בחשבון שירות' במסנן החיפוש ובוחרים אותו בתוצאות.
- לוחצים על 'שמירה' כדי לאשר את הקצאת התפקיד.
פרטים נוספים על התהליך הזה זמינים במסמכי העזרה של IAM. אפשר גם לקרוא איך מעדכנים תפקידים באמצעות הכלים של שורת הפקודה של gcloud.
לא ניתן היה לקבוע את חשבון השירות
אם מופיעה הודעת שגיאה דומה להודעה הבאה, סימן ש-Firebase Admin SDK לא הופעל כמו שצריך.
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.