إدارة جلسات المستخدم

جلسات مصادقة Firebase طويلة الأمد. في كل مرة يقوم فيها المستخدم بتسجيل الدخول، يتم إرسال بيانات اعتماد المستخدم إلى الواجهة الخلفية لمصادقة Firebase ويتم استبدالها برمز معرف Firebase (JWT) ورمز التحديث. الرموز المميزة لـ Firebase ID قصيرة العمر وتستمر لمدة ساعة؛ يمكن استخدام رمز التحديث لاسترداد رموز المعرف الجديدة. تنتهي صلاحية الرموز المميزة للتحديث فقط عند حدوث أحد الإجراءات التالية:

  • يتم حذف المستخدم
  • تم تعطيل المستخدم
  • تم اكتشاف تغيير كبير في حساب المستخدم. يتضمن ذلك أحداثًا مثل تحديثات كلمة المرور أو عنوان البريد الإلكتروني.

يوفر Firebase Admin SDK القدرة على إبطال الرموز المميزة للتحديث لمستخدم محدد. بالإضافة إلى ذلك، يتم أيضًا توفير واجهة برمجة التطبيقات (API) للتحقق من إبطال الرمز المميز للمعرف. باستخدام هذه الإمكانات، يمكنك التحكم بشكل أكبر في جلسات المستخدم. يوفر SDK القدرة على إضافة قيود لمنع استخدام الجلسات في ظروف مشبوهة، بالإضافة إلى آلية للتعافي من سرقة الرمز المميز المحتملة.

إبطال رموز التحديث

يمكنك إلغاء رمز التحديث الحالي الخاص بالمستخدم عندما يقوم المستخدم بالإبلاغ عن جهاز مفقود أو مسروق. وبالمثل، إذا اكتشفت ثغرة أمنية عامة أو اشتبهت في وجود تسرب واسع النطاق للرموز النشطة، فيمكنك استخدام listUsers API للبحث عن جميع المستخدمين وإلغاء الرموز المميزة الخاصة بهم للمشروع المحدد.

تؤدي عمليات إعادة تعيين كلمة المرور أيضًا إلى إلغاء الرموز المميزة الموجودة للمستخدم؛ ومع ذلك، فإن الواجهة الخلفية لمصادقة Firebase تتعامل مع الإلغاء تلقائيًا في هذه الحالة. عند الإلغاء، يتم تسجيل خروج المستخدم ومطالبته بإعادة المصادقة.

فيما يلي مثال على التنفيذ الذي يستخدم 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}`);
  });

جافا

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)

ج #

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

كشف إبطال رمز الهوية

نظرًا لأن الرموز المميزة لمعرف Firebase هي JWTs عديمة الحالة، يمكنك تحديد الرمز المميز الذي تم إبطاله فقط عن طريق طلب حالة الرمز المميز من الواجهة الخلفية لمصادقة Firebase. لهذا السبب، يعد إجراء هذا الفحص على الخادم الخاص بك عملية مكلفة، وتتطلب رحلة إضافية ذهابًا وإيابًا للشبكة. يمكنك تجنب تقديم طلب الشبكة هذا عن طريق إعداد قواعد أمان Firebase التي تتحقق من الإلغاء بدلاً من استخدام Admin SDK لإجراء التحقق.

اكتشاف إبطال رمز التعريف المميز في قواعد أمان Firebase

لكي نتمكن من اكتشاف إبطال رمز التعريف باستخدام قواعد الأمان، يجب علينا أولاً تخزين بعض البيانات التعريفية الخاصة بالمستخدم.

قم بتحديث البيانات التعريفية الخاصة بالمستخدم في قاعدة بيانات Firebase Realtime.

احفظ الطابع الزمني لإلغاء رمز التحديث. يعد هذا ضروريًا لتتبع إبطال رمز التعريف عبر قواعد أمان 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})

أضف علامة اختيار إلى قواعد أمان 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 . إذا تم إبطال الرمز المميز للمستخدم، فيجب تسجيل خروج المستخدم من العميل أو مطالبته بإعادة المصادقة باستخدام واجهات برمجة تطبيقات إعادة المصادقة التي توفرها مجموعات 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.
    }
  });

جافا

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)

ج #

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!' })
      });
    }
  });
});