ניהול הפעלות משתמש

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

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

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

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

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

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

הנה דוגמה ליישום שמשתמש ב-Admin 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}`);
  });

Java

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 ID הם JWT חסרי מדינה, אתה יכול לקבוע שאסימון בוטל רק על ידי בקשת הסטטוס של האסימון מה-backend של Firebase Authentication. מסיבה זו, ביצוע בדיקה זו בשרת שלך היא פעולה יקרה, הדורשת נסיעה נוספת לרשת הלוך ושוב. אתה יכול להימנע מלבקש רשת זו על ידי הגדרת כללי אבטחה של Firebase הבודקים ביטול במקום להשתמש ב-SDK של Admin כדי לבצע את הבדיקה.

זיהוי שלילת אסימון מזהה בכללי האבטחה של Firebase

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

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

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

Node.js

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

Java

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})

הוסף המחאה לכללי האבטחה של Firebase

כדי לאכוף בדיקה זו, הגדר כלל ללא גישת כתיבה של לקוח לאחסון זמן הביטול לכל משתמש. ניתן לעדכן זאת עם חותמת הזמן 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 של אימות מחדש שסופקו על ידי ערכות ה-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;
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.
    }
  });

Java

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.
    }
}

השב לביטול אסימון על הלקוח

אם האסימון מבוטל דרך ה-Admin 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!' })
      });
    }
  });
});