dbAuth authentication succeeds but with null currentUser

I’m noticing a behavior that I think should be classified as a bug:

After a successful logIn() on a page useAuth() returns isAuthenticated, but with null for currentUser and userMetadata.

This results in additional unnecessary authentication(s) when code assumes !currentUser means not authenticated. I believe there are places in the AuthProvider code that makes this assumption and trigger additional authentications.

After some debugging I noticed that this is what is happening:

  1. The initial call to useAuth() on a page calls getToken() which tries to fetch the token from the server.
  2. The server response is a null token because the user is not yet logged in (no session cookie).
  3. dbAuth caches the null token
  4. The page calls logIn() which succeeds. The server sends a set-cookie for the session.
  5. logIn() next calls reauthenticate() internally, which kicks off another getToken()
  6. That call to getToken() never goes to the server because dbAuth finds the previously cached null token and returns it instead of fetching from from the server and getting the actual token
  7. The page re-renders with isAuthenticated: true, currentUser: null, userMetadata: null
  8. Calling navigate() to another route at this point triggers another authentication because calls to useAuth() have currentUser: null

It seems to me that if the cached token is null, getToken should fetch from the server. I don’t know the code very well but I was thinking the code below could be a fix.

Can someone who knows the code better determine the most appropriate fix and include it in the next release?

IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/packages/auth-providers/dbAuth/web/src/dbAuth.ts b/packages/auth-providers/dbAuth/web/src/dbAuth.ts
--- a/packages/auth-providers/dbAuth/web/src/dbAuth.ts	(revision 1075258d60cc0d0fdda0257576f999b26ac5a07d)
+++ b/packages/auth-providers/dbAuth/web/src/dbAuth.ts	(date 1704231152678)
@@ -86,7 +86,7 @@
       return getTokenPromise
     }
 
-    if (isTokenCacheExpired()) {
+    if (isTokenCacheExpired() || cachedToken === null) {
       getTokenPromise = fetch(`${getApiDbAuthUrl()}?method=getToken`, {
         credentials,
       })

I’m getting this behavior as well.

I get two calls to the auth endpoint. The first one sends a SetCookie with the proper value, but the second one resets it for some reason (changed api port to 8921 to rule out hidden cookies on standard 8911 port):

Any idea on what could be happening?

@rob Any thoughts on the cachedToken case here with dbAuth and session cookies?

It’s been a while since I’ve been in this code! I’m out this week for spring break, but will take a look next week.

I think this is something different…can you verify that the cookie set in the first request is actually being sent up to the server in the second request? There should be a Cookie header in the second request that matches the Set-Cookie from the response of the first.

What browser are you using? There are some quirks with some browsers that won’t let you set a Secure cookie on localhost, (Chrome allows you to do this). We have a check in the auth.js function that should only set Secure if you’re in a non-development environment to prevent this, but something to look into.

Before we added this ^ check, this is the exact behavior we saw in Safari: first request set a cookie, second request cleared it, because the second request wouldn’t sent up the Secure cookie in a non-secure environment. To be safe the server removes the cookie on any request that doesn’t appear as authenticated already (like the cookie not being set).

We only cache the token for 5 seconds, to make sure it isn’t requested multiple times in a single page load (any call to useAuth() would trigger a call to the server otherwise). We keep this as short as possible (instead of something like 6 hours) to be sure that if you revoke access to your site you can ban/remove them effectively instantly.

Is it possible that’s the source of the request pattern you’re seeing? If you click on a link to another page that uses useAuth(), and it’s been more than 5 seconds, you’ll get another check with the server to verify the currentUser.

Hey, I know I’m not OP but I’m having the same issue. It looks like the cookie is being set after the first request to /auth. I can confirm that it is sent back on the second request (to getToken) which unsets the cookie, and then a third call (webAuthnAuthOptions) fails with HTTP 400. This is with nothing other than me setting up dbAuth with webAuthn.

Check your Secure cookie setting: dbAuth on serve using ip address doesn’t work - #5 by rob That post and my following one was the solution to that person having a similar issue!

That was one of my first thoughts as well, but this is immediately after generating all the code for dbAuth on the newest version of Redwood, so it includes the check for a development environment to make it non-secure. I confirmed that by looking in the initial request, and it was non-secure.

From the initial request:

session=;HttpOnly;Path=/;SameSite=Strict;Expires=Sun, 16 Jun 2024 15:23:50 GMT

From the second:

session=;HttpOnly;Path=/;SameSite=Strict;Expires=Thu, 01 Jan 1970 00:00:00 GMT

It looks like Discourse mangled that. Here it is again without triggering markdown issues. Note the session ID is contained in the first request.

First:

session=(removed);HttpOnly;Path=/;SameSite=Strict;Expires=Sun, 16 Jun 2024 15:23:50 GMT

Second:

session=;HttpOnly;Path=/;SameSite=Strict;Expires=Thu, 01 Jan 1970 00:00:00 GMT