如何防止使用Firebase同时登录同一用户?

时间:2014-01-23 23:08:13

标签: javascript jquery web-applications firebase

我希望新会话基本上“退出”任何以前的会话。例如,当您在一台计算机上进行经过身份验证的会话时,在另一台计算机上启动新会话并在我们的应用程序上使用firebase进行身份验证将在第一台计算机上注销另一个会话。

我无法找到任何允许我“远程”退出会话的方法。我知道我可以在会话中使用unauth()和goOffline()。但是,如何从同一用户的其他经过身份验证的会话中执行此操作?

感谢您的帮助!

背景信息:

  1. 我使用简单的电子邮件/密码登录进行firebase身份验证
  2. 我还没有设置安全规则,虽然这是在开发中
  3. 我正在使用Javascript和Firebase

2 个答案:

答案 0 :(得分:9)

一般的想法是,您希望在Firebase中创建一些元数据,告诉您用户登录的位置数。然后,您可以使用此信息限制其访问权限。

为此,您需要生成自己的令牌(以便您的安全规则可以使用这些信息)。

1)生成令牌

使用custom login生成自己的令牌。每个令牌应包含客户端的唯一ID(IP地址?UUID?)

var FirebaseTokenGenerator = require("firebase-token-generator");
var tokenGenerator = new FirebaseTokenGenerator(YOUR_FIREBASE_SECRET);
var token = tokenGenerator.createToken({ id: USER_ID, location_id: IP_ADDRESS });

2)使用状态存储用户的location_id

查看managing presence引物了解详情:

var fb = new Firebase(URL);

// after getting auth token back from your server
var parts = deconstructJWT(token);
var ref = fb.child('logged_in_users/'+token.id);

// store the user's location id
ref.set(token.location_id);

// remove location id when user logs out
ref.onDisconnect().remove();

// Helper function to extract claims from a JWT. Does *not* verify the
// validity of the token.
// credits: https://github.com/firebase/angularFire/blob/e8c1d33f34ee5461c0bcd01fc316bcf0649deec6/angularfire.js
function deconstructJWT(token) {
  var segments = token.split(".");
  if (!segments instanceof Array || segments.length !== 3) {
    throw new Error("Invalid JWT");
  }
  var claims = segments[1];
  if (window.atob) {
    return JSON.parse(decodeURIComponent(escape(window.atob(claims))));
  }
  return token;
}

3)添加安全规则

在安全规则中,强制只有当前唯一位置可以读取数据

{
  "some_restricted_path": {
     ".read": "root.child('logged_in_users/'+auth.id).val() === auth.location_id"
  }
}

4)控制对logged_in_users的写入权限

您需要设置一些控制对logged_in_users的写访问权限的系统。显然,用户应该只能写入自己的记录。如果您希望第一次登录尝试始终获胜,那么使用".write": "!data.exists()"

阻止写入(如果存在值)(直到他们注销)

但是,您可以通过允许上次登录获胜来大大简化,在这种情况下,它会覆盖旧的位置值,之前的登录将无效并且无法读取。

5)这不是控制同步数量的解决方案

您不能使用它来阻止Firebase的多个并发。有关完成此操作的更多数据,请参阅goOffline()和goOnline()(或获取付费计划,以便您无需连接上限)。

答案 1 :(得分:6)

TL; DR

https://pastebin.com/jWYu53Up


我们也遇到了这个话题。尽管该线程已经过时,并且它并未完全概述我们想要实现的完全相同的愿望,但是我们可以吸收@kato的一些通用概念。概念大致相同,但此主题绝对值得更新。

抬头:立即阅读该说明,要意识到一个事实,您可能会发现它与上下文无关,因为它不能完全涵盖原始的SO问题。实际上,组装一个系统以防止同时进行多个会话是完全不同的思维模式。更准确地说,这是适合我们情况的心理模型。 :)

例如,当您在一台计算机上处​​于经过身份验证的会话中时,在另一台计算机上启动新会话并在我们的应用程序上使用firebase进行身份验证将注销第一台计算机上的另一会话。

维护这种“同时登录预防”类型意味着1)即使每个客户端的活动会话都来自同一设备,也应加以区分2)客户端应从AFAICT Firebase所在的特定设备中退出。能够。 FWIW,您可以revoke tokens明确使指定用户的所有刷新令牌都过期,因此,系统会提示您再次登录,但这样做的缺点是会破坏所有现有会话(甚至是一个会话)刚刚被激活)。

这些“开销”导致解决问题的方式略有不同。区别在于1)无需跟踪具体设备2)以编程方式将客户端注销,而不必破坏其任何活动会话以增强用户体验。


利用Firebase Presence通过繁重的工作来跟踪客户端的连接状态变化(即使由于某种奇怪的原因而终止了连接),但这是要注意的:它不是 Firestore自带。请参考Connecting to Cloud Firestore以使数据库保持同步。还值得一提的是,与示例相比,我们没有设置对特殊.info/connected路径的引用。相反,我们利用onAuthStateChanged()观察者来响应身份验证状态更改。

const getUserRef = userId => firebase.database().ref(`/users/${userId}`);

firebase.auth().onAuthStateChanged(user => {
   if (user) {
      const userRef = getUserRef(user.uid);

      return userRef
         .onDisconnect()
         .set({
            is_online: false,
            last_seen: firebase.database.ServerValue.TIMESTAMP
         })
         .then(() =>
            // This sets the flag to true once `onDisconnect()` has been attached to the user's ref.
            userRef.set({
               is_online: true,
               last_seen: firebase.database.ServerValue.TIMESTAMP  
            });
         );
   }
});

正确设置onDisconnect()后,如果用户尝试在另一个活动会话旁边启动登录,则必须确保该用户的会话,为此,将请求转发到数据库并对照相应的标志。因此,由于此额外的往返行程,识别多个会话比平时花费更多的时间,因此应该相应地调整UI。

const ensureUserSession = userId => {
   const userRef = getUserRef(userId);

   return userRef.once("value").then(snapshot => {
      if (!snapshot.exists()) {
         // If the user entry does not exist, create it and return with the promise.
         return userRef.set({
            last_seen: firebase.database.ServerValue.TIMESTAMP
         });
      }

      const user = snapshot.data();

      if (user.is_online) {
         // If the user is already signed in, throw a custom error to differentiate from other potential errors.
         throw new SessionAlreadyExists(...);
      }

      // Otherwise, return with a resolved promise to permit the sign-in.
      return Promise.resolve();
   });
};

将这两个代码段组合在一起将产生https://pastebin.com/jWYu53Up