Firebase 刷新令牌过期

问题描述 投票:0回答:3

在使用 Firebase 的 REST API 测试我们的产品之一(Web 应用程序)的安全性时,我们惊讶地发现,在 Firebase 实现的 V3 中,刷新令牌永不过期,允许任何刷新令牌永远创建新的代币

虽然本地存储今天看起来是一个相当安全的解决方案,但我们担心明天可能会失败,即使是很短的时间,并且我们无法阻止某人使用任何这些刷新令牌。

双因素身份验证将有助于缓解该问题,但第一步仍然会受到损害。

是否有一种方法可以使用 Firebase 将代币列入黑名单或类似行为,而无需我们自己处理所有代币交换(例如铸造)?我们在查看文档时找不到此类功能。

任何建议表示赞赏。

firebase local-storage token refresh-token
3个回答
6
投票

身份验证会话不会因 Firebase 登录而过期。但 ID 令牌必须每小时刷新一次,才能保持对服务的访问。如果您禁用帐户,刷新令牌将失败,并且该帐户将无法再访问服务。无法使单个令牌失效。


5
投票

Firebase 最近在管理 sdk 中实现了

revokeRefreshTokens()
虽然这不会让你杀死无效的 JWT,但它确实允许你阻止刷新令牌(至少从我到目前为止的测试来看),并且它允许更清晰的控制Firebase 数据库内部的流动。

请参阅管理员管理会话了解粗略示例


0
投票

想为这个问题添加一个具体的例子。这就是我使用上下文提供程序/他们处理全局状态的方式使用 SolidJS 处理它的方式:

我的

routes.tsx
看起来像这样:

import { lazy } from 'solid-js';
import { Route, Router } from '@solidjs/router';
import { getCurrentUser } from '~/apis/current-user.api';
import AuthContextProvider from '~/contexts/Auth.context';


import HomeRoutes from '~/pages/auth/routes';

const LandingPage = lazy(() => import('./pages/landing/index'));
const user = await getCurrentUser();

export const Routes = () => {
  return (<AuthContextProvider
    user={user}>
      <Router>
        <Route path='/'>
          <Route path='/' component={LandingPage} />,
          <HomeRoutes/>,
          <Route path='/*' component={LandingPage} />,
        </Route>,
      </Router>
  </AuthContextProvider>)
};

我正在获取当前用户并将其传递给我的

AuthContextProvider
,因为它位于所有路由的顶层,每个组件都可以访问它,并且能够调用
const { user } = useAuthContext();
来获取用户信息。

~/apis/current-user.api 看起来像这样:

import { API_URL, COOKIES, getHeadersWithAuth } from "~/constants/api";
import { emptyUser, User } from "~/models/user.model";
import { firebaseAuth } from '~/utils/firebase';

const TOKEN_EXPIRATION_TIME = 55 * 60 * 1000; // 55 minutes in milliseconds

export const refreshUserToken = async () => {
  const userToken = localStorage.getItem("userToken");
  const tokenCreationTime = parseInt(localStorage.getItem("tokenCreationTime"), 10);
  const currentTime = Date.now();

  if (userToken && tokenCreationTime && currentTime - tokenCreationTime < TOKEN_EXPIRATION_TIME) {
    sessionStorage.setItem(COOKIES.ACCESS_TOKEN, userToken);
    return true;
  }

  try {
    // Force a token refresh
    const user = firebaseAuth.currentUser;
    if (!user) {
      return false;
    }
    const userToken = await user.getIdToken(true);
    sessionStorage.setItem(COOKIES.ACCESS_TOKEN, userToken);

    // Update the local storage with new token and creation time
    localStorage.setItem("userToken", userToken);
    localStorage.setItem("tokenCreationTime", Date.now().toString());
    return true;

  } catch (error) {
    return false;
  }
};

export const getCurrentUser = async () => {
  const result = await refreshUserToken();
  if (!result) {
    return emptyUser();
  }

  if (!sessionStorage.getItem(COOKIES.ACCESS_TOKEN)) {
    return emptyUser();
  }

  const endPoint = `${API_URL}/auth/current-user`;
  const res = await fetch(endPoint, {
    method: 'GET',
    headers: getHeadersWithAuth(),
    // credentials: 'include',
  }).catch(err => {
    console.error(`Error: getCurrentUser - ${endPoint}`, err);
    return err;
  });

  if (res.status !== 200) {
    sessionStorage.removeItem(COOKIES.ACCESS_TOKEN);
    // Remove tokens from local storage
    localStorage.removeItem("userToken");
    localStorage.removeItem("refreshToken");
    localStorage.removeItem("tokenCreationTime");
    return emptyUser();
  }

  type data = {
    user: User;
  }

  const { user: _user }: data = await res.json();
  const user = _user ? new User(_user) : emptyUser();
  return user;
}

这样,每当客户端刷新或转到新页面时,都会调用

getCurrentUser
函数并检查令牌是否已过期。如果没有,它将使用会话令牌中找到的内容。如果没有,它将创建一个新的。

我正在使用本地存储,以便用户即使在关闭窗口并返回时也保持登录状态。我使用

setPersistence
browserLocalPersistence
选项来做到这一点。

当用户注销时,我将从本地存储中删除数据并调用

revokeRefreshTokens
,这会导致refreshToken不再有效。

如果您愿意,您当然可以更进一步,使事情变得更加安全,但这应该是一个很好的起点。

© www.soinside.com 2019 - 2024. All rights reserved.