Check out what’s new from Firebase@ Google I/O 2021, and join our alpha program for early access to the new Remote Config personalization feature. Learn more

נהל מושבי משתמשים

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

  • המשתמש נמחק
  • המשתמש מושבת
  • מתגלה שינוי חשוב בחשבון עבור המשתמש. זה כולל אירועים כמו עדכוני סיסמה או כתובת דוא"ל.

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

בטל אסימוני רענון

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

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

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

Node.js

// Revoke all refresh tokens for a specified user for whatever reason.
// Retrieve the timestamp of the revocation, in seconds since the epoch.
admin
  .auth()
  .revokeRefreshTokens(uid)
  .then(() => {
    return admin.auth().getUser(uid);
  })
  .then((userRecord) => {
    return new Date(userRecord.tokensValidAfterTime).getTime() / 1000;
  })
  .then((timestamp) => {
    console.log(`Tokens revoked at: ${timestamp}`);
  });

ג'אווה

FirebaseAuth.getInstance().revokeRefreshTokens(uid);
UserRecord user = FirebaseAuth.getInstance().getUser(uid);
// Convert to seconds as the auth_time in the token claims is in seconds too.
long revocationSecond = user.getTokensValidAfterTimestamp() / 1000;
System.out.println("Tokens revoked at: " + revocationSecond);

פִּיתוֹן

# Revoke tokens on the backend.
auth.revoke_refresh_tokens(uid)
user = auth.get_user(uid)
# Convert to seconds as the auth_time in the token claims is in seconds.
revocation_second = user.tokens_valid_after_timestamp / 1000
print('Tokens revoked at: {0}'.format(revocation_second))

ללכת

client, err := app.Auth(ctx)
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}
if err := client.RevokeRefreshTokens(ctx, uid); err != nil {
	log.Fatalf("error revoking tokens for user: %v, %v\n", uid, err)
}
// accessing the user's TokenValidAfter
u, err := client.GetUser(ctx, uid)
if err != nil {
	log.Fatalf("error getting user %s: %v\n", uid, err)
}
timestamp := u.TokensValidAfterMillis / 1000
log.Printf("the refresh tokens were revoked at: %d (UTC seconds) ", timestamp)

C #

await FirebaseAuth.DefaultInstance.RevokeRefreshTokensAsync(uid);
var user = await FirebaseAuth.DefaultInstance.GetUserAsync(uid);
Console.WriteLine("Tokens revoked at: " + user.TokensValidAfterTimestamp);

זיהוי ביטול אסימון מזהה

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

זיהוי ביטול אסימון מזהה בכללי מסד הנתונים

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

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

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

Node.js

const metadataRef = admin.database().ref('metadata/' + uid);
metadataRef.set({ revokeTime: utcRevocationTimeSecs }).then(() => {
  console.log('Database updated successfully.');
});

ג'אווה

DatabaseReference ref = FirebaseDatabase.getInstance().getReference("metadata/" + uid);
Map<String, Object> userData = new HashMap<>();
userData.put("revokeTime", revocationSecond);
ref.setValueAsync(userData);

פִּיתוֹן

metadata_ref = firebase_admin.db.reference("metadata/" + uid)
metadata_ref.set({'revokeTime': revocation_second})

הוסף צ'ק לתקנון מסד הנתונים

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

{
  "rules": {
    "metadata": {
      "$user_id": {
        // this could be false as it is only accessed from backend or rules.
        ".read": "$user_id === auth.uid",
        ".write": "false",
      }
    }
  }
}

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

{
  "rules": {
    "users": {
      "$user_id": {
        ".read": "auth != null && $user_id === auth.uid && (
            !root.child('metadata').child(auth.uid).child('revokeTime').exists()
          || auth.token.auth_time > root.child('metadata').child(auth.uid).child('revokeTime').val()
        )",
        ".write": "auth != null && $user_id === auth.uid && (
            !root.child('metadata').child(auth.uid).child('revokeTime').exists()
          || auth.token.auth_time > root.child('metadata').child(auth.uid).child('revokeTime').val()
        )",
      }
    }
  }
}

זיהוי ביטול אסימון מזהה ב- SDK.

בשרת שלך, הטמיע את ההיגיון הבא לצורך ביטול אסימון אסימון ואימות אסימון מזהה:

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

כדי לאתחל את ה- Admin SDK לפלטפורמה שלך, פעל לפי ההוראות בדף ההתקנה . דוגמאות לאחזור אסימון המזהה הן בקטע verifyIdToken .

Node.js

// Verify the ID token while checking if the token is revoked by passing
// checkRevoked true.
let checkRevoked = true;
admin
  .auth()
  .verifyIdToken(idToken, checkRevoked)
  .then((payload) => {
    // Token is valid.
  })
  .catch((error) => {
    if (error.code == 'auth/id-token-revoked') {
      // Token has been revoked. Inform the user to reauthenticate or signOut() the user.
    } else {
      // Token is invalid.
    }
  });

ג'אווה

try {
  // Verify the ID token while checking if the token is revoked by passing checkRevoked
  // as true.
  boolean checkRevoked = true;
  FirebaseToken decodedToken = FirebaseAuth.getInstance()
      .verifyIdToken(idToken, checkRevoked);
  // Token is valid and not revoked.
  String uid = decodedToken.getUid();
} catch (FirebaseAuthException e) {
  if (e.getAuthErrorCode() == AuthErrorCode.REVOKED_ID_TOKEN) {
    // Token has been revoked. Inform the user to re-authenticate or signOut() the user.
  } else {
    // Token is invalid.
  }
}

פִּיתוֹן

try:
    # Verify the ID token while checking if the token is revoked by
    # passing check_revoked=True.
    decoded_token = auth.verify_id_token(id_token, check_revoked=True)
    # Token is valid and not revoked.
    uid = decoded_token['uid']
except auth.RevokedIdTokenError:
    # Token revoked, inform the user to reauthenticate or signOut().
    pass
except auth.InvalidIdTokenError:
    # Token is invalid
    pass

ללכת

client, err := app.Auth(ctx)
if err != nil {
	log.Fatalf("error getting Auth client: %v\n", err)
}
token, err := client.VerifyIDTokenAndCheckRevoked(ctx, idToken)
if err != nil {
	if err.Error() == "ID token has been revoked" {
		// Token is revoked. Inform the user to reauthenticate or signOut() the user.
	} else {
		// Token is invalid
	}
}
log.Printf("Verified ID token: %v\n", token)

C #

try
{
    // Verify the ID token while checking if the token is revoked by passing checkRevoked
    // as true.
    bool checkRevoked = true;
    var decodedToken = await FirebaseAuth.DefaultInstance.VerifyIdTokenAsync(
        idToken, checkRevoked);
    // Token is valid and not revoked.
    string uid = decodedToken.Uid;
}
catch (FirebaseAuthException ex)
{
    if (ex.AuthErrorCode == AuthErrorCode.RevokedIdToken)
    {
        // Token has been revoked. Inform the user to re-authenticate or signOut() the user.
    }
    else
    {
        // Token is invalid.
    }
}

הגב לביטול סמלי על הלקוח

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

function onIdTokenRevocation() {
  // For an email/password user. Prompt the user for the password again.
  let password = prompt('Please provide your password for reauthentication');
  let credential = firebase.auth.EmailAuthProvider.credential(
      firebase.auth().currentUser.email, password);
  firebase.auth().currentUser.reauthenticateWithCredential(credential)
    .then(result => {
      // User successfully reauthenticated. New ID tokens should be valid.
    })
    .catch(error => {
      // An error occurred.
    });
}

אבטחה מתקדמת: אכוף הגבלות על כתובות IP

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

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

app.post('/getRestrictedData', (req, res) => {
  // Get the ID token passed.
  const idToken = req.body.idToken;
  // Verify the ID token, check if revoked and decode its payload.
  admin.auth().verifyIdToken(idToken, true).then((claims) => {
    // Get the user's previous IP addresses, previously saved.
    return getPreviousUserIpAddresses(claims.sub);
  }).then(previousIpAddresses => {
    // Get the request IP address.
    const requestIpAddress = req.connection.remoteAddress;
    // Check if the request IP address origin is suspicious relative to previous
    // IP addresses. The current request timestamp and the auth_time of the ID
    // token can provide additional signals of abuse especially if the IP address
    // suddenly changed. If there was a sudden location change in a
    // short period of time, then it will give stronger signals of possible abuse.
    if (!isValidIpAddress(previousIpAddresses, requestIpAddress)) {
      // Invalid IP address, take action quickly and revoke all user's refresh tokens.
      revokeUserTokens(claims.uid).then(() => {
        res.status(401).send({error: 'Unauthorized access. Please login again!'});
      }, error => {
        res.status(401).send({error: 'Unauthorized access. Please login again!'});
      });
    } else {
      // Access is valid. Try to return data.
      getData(claims).then(data => {
        res.end(JSON.stringify(data);
      }, error => {
        res.status(500).send({ error: 'Server error!' })
      });
    }
  });
});