刷新 Firebase ID 令牌服务器端

huangapple go评论59阅读模式
英文:

refresh firebase id token server-side

问题

I am working on an app with Next.js 13 and firebase auth with id tokens.

我正在开发一个使用Next.js 13和Firebase身份验证的应用,使用ID令牌进行身份验证。

I want to leverage Next.JS built-in capability for server-side components to fetch user data faster, therefore I need to verify id tokens on the server at initial request. When no user is logged in on protected routes, I want to redirect to the login page.

我希望利用Next.JS内置的服务器端组件获取用户数据的能力,因此我需要在服务器上验证ID令牌以满足初始请求。在受保护的路由上没有用户登录时,我希望重定向到登录页面。

The problem arises when the user was inactive for >1h and the id token has expired. The next request header will send the expired token causing auth.verifyIdToken to reject it. This will redirect the user to the login page, before any client-side code had a chance to run, including user.getIdToken to refresh the token.

当用户闲置超过1小时并且ID令牌已过期时,问题就出现了。下一个请求标头将发送已过期的令牌,导致auth.verifyIdToken拒绝它。这会在任何客户端代码有机会运行之前将用户重定向到登录页面,包括user.getIdToken来刷新令牌。

Is there a way to refresh the id token on the server-side? I read here, that there is a work-around using Firebase REST API, which seems insecure.

有没有办法在服务器端刷新ID令牌?我在这里读到,有一种使用Firebase REST API的解决方法,但似乎不够安全。

Context

上下文

I use the firebaseui package for login, which creates the initial id token & refresh token. Then I have an AuthContextProvider to provide & refresh the id token on the client:

我使用firebaseui进行登录,它创建了初始的ID令牌和刷新令牌。然后我有一个AuthContextProvider在客户端上提供和刷新ID令牌:

const ServerAuthContextProvider = ({
  children,
  user,
  cookie,
}: {
  children: ReactNode;
  user: UserRecord;
  cookie: Cookie;
}) => {
  useEffect(() => {
    if (typeof window !== "undefined") {
      (window as any).cookie = cookie;
    }
    return auth.onIdTokenChanged(async (snap) => {
      if (!snap) {
        cookie.remove("__session");
        cookie.set("__session", "", { path: "/" });
        return;
      }
      const token = await snap.getIdToken();
      cookie.remove("__session");
      cookie.set("__session", token, { path: "/" });
    });
  }, [cookie]);

  return (
    <serverAuthContext.Provider
      value={{
        user,
        auth,
      }}
    >
      {children}
    </serverAuthContext.Provider>
  );
};

服务器端根组件

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
  const { user } = await verifyAuthToken();
  if (!user) redirect("/login");

  return (
    <html lang="en">
      <body>
        <ServerAuthContextProvider user={user}>
          {children}
        </ServerAuthContextProvider>
      </body>
    </html>
  );
};

服务器端令牌验证

const verifyAuthToken = async () => {
  const auth = getAuth(firebaseAdmin);

  try {
    const session = cookies().get("__session");
    if (session?.value) {
      console.log("found token");
      const token = await auth.verifyIdToken(session.value);
      const { uid } = token;
      console.log("uid found: ", uid);
      const user = await auth.getUser(uid);
      return {
        auth,
        user,
      };
    }
  } catch (error: unknown) {
    if (typeof error === "string") {
      console.log("error", error);
      return {
        auth,
        error,
      };
    } else if (error instanceof Error) {
      console.log("error", error.message);

      return {
        auth,
        error: error.message,
      };
    }
  }
  return {
    auth,
  };
};
英文:

I am working on an app with Next.js 13 and firebase auth with id tokens.

I want to leverage Next.JS built-in capability for server-side components to fetch user data faster, therefore I need to verify id tokens on the server at initial request. When no user is logged in on protected routes, I want to redirect to login page.

The problem arises when the user was inactive for >1h and the id token has expired. The next request header will send the expired token causing auth.verifyIdToken to reject it. This will redirect the user to login page, before any client-side code had a chance to run, including user.getIdToken to refresh the token.

Is there a way to refresh the id token on server-side? I read here, that there is a work-around using firebase REST API, which seems insecure.

<h2>Context</h2>
I use the firebaseui package for login, which creates the initial id token & refresh token. Then I have an AuthContextProvider to provide & refresh the id token on the client:

const ServerAuthContextProvider = ({
  children,
  user,
  cookie,
}: {
  children: ReactNode;
  user: UserRecord;
  cookie: Cookie;
}) =&gt; {
  useEffect(() =&gt; {
    if (typeof window !== &quot;undefined&quot;) {
      (window as any).cookie = cookie;
    }
    return auth.onIdTokenChanged(async (snap) =&gt; {
      if (!snap) {
        cookie.remove(&quot;__session&quot;);
        cookie.set(&quot;__session&quot;, &quot;&quot;, { path: &quot;/&quot; });
        return;
      }
      const token = await snap.getIdToken();
      cookie.remove(&quot;__session&quot;);
      cookie.set(&quot;__session&quot;, token, { path: &quot;/&quot; });
    });
  }, [cookie]);

  return (
    &lt;serverAuthContext.Provider
      value={{
        user,
        auth,
      }}
    &gt;
      {children}
    &lt;/serverAuthContext.Provider&gt;
  );
};
  );
};

server-side root component

const RootLayout = async ({ children }: { children: React.ReactNode }) =&gt; {
  const { user } = await verifyAuthToken();
  if (!user) redirect(&quot;/login&quot;);

  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;
        &lt;ServerAuthContextProvider user={user}&gt;
          {children}
        &lt;/ServerAuthContextProvider&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
};

server-side token verification

const verifyAuthToken = async () =&gt; {
  const auth = getAuth(firebaseAdmin);

  try {
    const session = cookies().get(&quot;__session&quot;);
    if (session?.value) {
      console.log(&quot;found token&quot;);
      const token = await auth.verifyIdToken(session.value);
      const { uid } = token;
      console.log(&quot;uid found: &quot;, uid);
      const user = await auth.getUser(uid);
      return {
        auth,
        user,
      };
    }
  } catch (error: unknown) {
    if (typeof error === &quot;string&quot;) {
      console.log(&quot;error&quot;, error);
      return {
        auth,
        error,
      };
    } else if (error instanceof Error) {
      console.log(&quot;error&quot;, error.message);

      return {
        auth,
        error: error.message,
      };
    }
  }
  return {
    auth,
  };
};

答案1

得分: 3

对于 SSR 应用程序,你应该使用会话 cookie。与 ID 令牌不同,它们不会在 1 小时内过期,你可以将它们配置为在最多 14 天内保持活动。<br/>当用户使用 Firebase 客户端 SDK 登录时,你可以使用 getIdToken() 一次,并将其传递给一个生成并设置会话 cookie 的 API,该会话 cookie 可以在后续请求中读取。这样你就不必使用监听器来更新令牌。

请确保你在客户端 SDK 中将用户注销,要么通过将身份验证持久性设置为 none,要么显式注销用户,以便在清除他们的 cookie(使用注销 API)时,客户端没有用户。

英文:

For SSR applications, you should use session cookies. Unlike ID Tokens, they do not expire in 1 hour and you can configure them to be active for up to 14 days. <br/>When a user logs in with Firebase client SDK, you can use getIdToken() once and pass to it an API that generates and sets a session cookie that can be read in subsequent requests.
This way you don't have keep the token updated using a listener.

Do make sure you log the user out from the client SDK either by settings auth persistence to none or explicitly logging out the user so in case you clear they cookie (using a logout API) there is no user on client side.

huangapple
  • 本文由 发表于 2023年2月7日 03:54:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/75365962.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定