مدیریت جلسات کاربر

جلسات Firebase Authentication عمر طولانی دارند. هر بار که کاربر وارد سیستم می‌شود، اعتبار کاربری به باطن Firebase Authentication ارسال می‌شود و با یک نشانه Firebase ID (یک JWT) و توکن تازه‌سازی مبادله می‌شود. توکن های Firebase ID کوتاه مدت هستند و یک ساعت دوام می آورند. نشانه رفرش را می توان برای بازیابی نشانه های ID جدید استفاده کرد. نشانه‌های Refresh تنها زمانی منقضی می‌شوند که یکی از موارد زیر رخ دهد:

  • کاربر حذف شده است
  • کاربر غیرفعال است
  • یک تغییر عمده حساب برای کاربر شناسایی می شود. این شامل رویدادهایی مانند به‌روزرسانی رمز عبور یا آدرس ایمیل است.

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

جاوا

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 ID JWT‌های بدون حالت هستند، می‌توانید تشخیص دهید که یک توکن تنها با درخواست وضعیت توکن از باطن Firebase Authentication لغو شده است. به همین دلیل، انجام این بررسی روی سرور شما یک عملیات گران قیمت است که نیاز به یک رفت و برگشت اضافی در شبکه دارد. می توانید با تنظیم Firebase Security Rules که به جای استفاده از Admin SDK برای بررسی، ابطال را بررسی می کند، از انجام این درخواست شبکه جلوگیری کنید.

شناسایی ابطال رمز شناسه در Firebase Security Rules

برای اینکه بتوانیم با استفاده از قوانین امنیتی ابطال شناسه شناسه را شناسایی کنیم، ابتدا باید برخی از فراداده های خاص کاربر را ذخیره کنیم.

ابرداده های خاص کاربر را در Firebase Realtime Database به روز کنید.

مهر زمانی ابطال نشانه رفرش را ذخیره کنید. این برای ردیابی لغو رمز شناسه از طریق Firebase Security Rules لازم است. این امکان بررسی کارآمد در پایگاه داده را فراهم می کند. در نمونه کدهای زیر از 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 Security Rules اضافه کنید

برای اجرای این بررسی، قانونی را بدون دسترسی نوشتن کلاینت تنظیم کنید تا زمان ابطال هر کاربر ذخیره شود. همانطور که در مثال‌های قبلی نشان داده شده است، می‌توان آن را با مهر زمانی 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",
      }
    }
  }
}

هر داده ای که نیاز به دسترسی تأیید شده دارد باید قانون زیر را پیکربندی کند. این منطق فقط به کاربران احراز هویت شده با توکن های ID لغو نشده اجازه می دهد تا به داده های محافظت شده دسترسی داشته باشند:

{
  "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.

در سرور خود، منطق زیر را برای ابطال توکن رفرش و اعتبار سنجی توکن ID پیاده سازی کنید:

هنگامی که قرار است رمز شناسه کاربر تأیید شود، پرچم بولی checkRevoked اضافی باید به verifyIdToken ارسال شود. اگر رمز کاربر باطل شود، کاربر باید از سیستم کلاینت خارج شود یا از او خواسته شود با استفاده از APIهای احراز هویت مجدد ارائه شده توسط SDKهای سرویس گیرنده احراز Firebase Authentication مجدد انجام دهد.

برای مقداردهی اولیه Admin SDK برای پلتفرم خود، دستورالعمل‌های صفحه راه‌اندازی را دنبال کنید. نمونه هایی از بازیابی رمز ID در قسمت 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، برای هر درخواست احراز هویت شده، کد ID را بررسی کنید و بررسی کنید که آیا آدرس 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!' })
      });
    }
  });
});