Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

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

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

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

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

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

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

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

להלן יישום לדוגמה המשתמש ב- SDK Admin כדי לבטל את אסימון הרענון של משתמש נתון. כדי לאתחל את 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.
getAuth()
  .revokeRefreshTokens(uid)
  .then(() => {
    return getAuth().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 = getDatabase().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})

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

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

{
  "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 . אם אסימון המשתמש מבוטל, יש לצאת מהמשתמש מהלקוח או לבקש ממנו לאמת מחדש באמצעות ממשקי API של אימות מחדש המסופקים על ידי SDKs של לקוח אימות Firebase.

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

Node.js

// Verify the ID token while checking if the token is revoked by passing
// checkRevoked true.
let checkRevoked = true;
getAuth()
  .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.UserDisabledError:
    # Token belongs to a disabled user record.
    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!' })
      });
    }
  });
});