تدوم جلسات مصادقة Firebase لفترة طويلة. في كل مرة يقوم المستخدم بتسجيل الدخول ، يتم إرسال بيانات اعتماد المستخدم إلى الواجهة الخلفية لمصادقة Firebase ويتم تبادلها برمز مميز لمعرف Firebase (JWT) ورمز التحديث المميز. الرموز المميزة لـ Firebase ID قصيرة العمر وتستمر لمدة ساعة ؛ يمكن استخدام رمز التحديث لاسترداد الرموز المميزة الجديدة للمعرف. تنتهي صلاحية الرموز المميزة للتحديث فقط عند حدوث أي مما يلي:
- تم حذف المستخدم
- المستخدم معطل
- تم الكشف عن تغيير حساب كبير للمستخدم. يتضمن هذا أحداثًا مثل تحديثات كلمة المرور أو عنوان البريد الإلكتروني.
يوفر Firebase Admin SDK القدرة على إبطال الرموز المميزة للتحديث لمستخدم محدد. بالإضافة إلى ذلك ، تتوفر أيضًا واجهة برمجة تطبيقات للتحقق من إبطال رمز المعرف. بفضل هذه الإمكانات ، يمكنك التحكم بشكل أكبر في جلسات المستخدم. توفر 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!' })
});
}
});
});