nowaythatworked / auth-astro

Community maintained Astro integration of @auth/core
255 stars 38 forks source link

Use of JWT callback to persist additional token info results in session being null. #33

Open msardi23 opened 1 year ago

msardi23 commented 1 year ago

I am using the JWT callback to add additional information to the token like access_token, refresh_token and expires_at as outlined in the Auth.js docs here for the JWT callback and here for refresh token rotation.

While I have this working fine using Next.js 13 and Qwik, I have been struggling with Astro. As soon as I include logic in the JWT callback to enhance the token the session ends up being empty. When I remove the enhancement in the JWT callback the default session data is returned.

No errors are reported in the console. I am using the Azure AD provider.

export default {
  secret: import.meta.env.AUTH_SECRET,
  trustHost: true,
  providers: [
    AzureAd({
      clientId: import.meta.env.AZURE_CLIENT_ID,
      clientSecret: import.meta.env.AZURE_CLIENT_SECRET,
      tenantId: import.meta.env.AZURE_TENANT_ID,
      authorization: {
        params: {
          scope: "...removed...",
        },
      },
    }),
  ] as Provider[],

  callbacks: {
    async session({ session, token }) {
      if (token) {
        session.access_token = token.access_token;
      }
      return session;
    },
    async jwt({ token, user, account, profile, isNewUser }) {
      if (account) {
        token.access_token = account.access_token;
      }
      return token;
    },
  },
};

If I then check session via .../api/auth/session it returns an empty object.

If I comment out token.access_token = account.access_token; in the JWT callback the default session data is returned.

Same issue occurs using both JS and TS projects.

Please help 😀

dvartdal commented 6 months ago

I have somehow a similar issue @msardi23 . However, for me, it works when only adding the account.access_token in my token, however, adding both the access_token and the refresh_token gives me the same error as you, where the getSession() returns null.

Not adding the refresh_token makes the getSession() return an existing session.

This is my code that works, and I have commented out the line adding the refresh_token to the token object for reference.

import AzureADB2C from '@auth/core/providers/azure-ad-b2c'
import { defineConfig } from 'auth-astro'

export default defineConfig({
    providers: [
        AzureADB2C({
            issuer: `https://${import.meta.env.AZUREADB2C_TENANT}.b2clogin.com/${
                import.meta.env.AZUREADB2C_TENANT_ID
            }/v2.0/`,
            wellKnown: `https://${import.meta.env.AZUREADB2C_TENANT}.b2clogin.com/${
                import.meta.env.AZUREADB2C_TENANT
            }.onmicrosoft.com/${import.meta.env.AZUREADB2C_POLICY}/v2.0/.well-known/openid-configuration`,
            authorization: {
                url: `https://${import.meta.env.AZUREADB2C_TENANT}.b2clogin.com/${
                    import.meta.env.AZUREADB2C_TENANT
                }.onmicrosoft.com/${import.meta.env.AZUREADB2C_POLICY}/oauth2/v2.0/authorize`,
                params: {
                    scope: `YOUR SCOPES`
                }
            },
            token: `https://${import.meta.env.AZUREADB2C_TENANT}.b2clogin.com/${
                import.meta.env.AZUREADB2C_TENANT
            }.onmicrosoft.com/${import.meta.env.AZUREADB2C_POLICY}/oauth2/v2.0/token`,
            clientId: import.meta.env.AZUREADB2C_CLIENT_ID,
            clientSecret: import.meta.env.AZUREADB2C_CLIENT_SECRET,
            allowDangerousEmailAccountLinking: true
        })
    ],
    callbacks: {
        async signIn({ user, account, profile, email, credentials }) {
            if (profile) {
                if (user) {
                    user.email = profile.email
                }
            }
            return true
        },
        async jwt({ token, user, account, profile }) {
            if (account && user) {
                const newToken = {
                    accessToken: account.access_token,
                    accessTokenExpires: Date.now() + account.expires_in! * 1000,
                    // refreshToken: account.refresh_token,
                    user
                }

                // console.log(newToken)
                return newToken
            } 

            // Return the current token if it's not expired or if there was no account object
            return token
        }
    }
})

For me, it seems that also adding a request token, maxes out the token size limit, or something. Because if I only add, for example: refreshToken: "refresh token", it works, and session is set.

From what I can see, from the Astro.request and the request cookies, the authcookie is not set when adding the full refreshToken, but is set when only adding accessToken.

The cookies remaining are: "authjs.csrf-token" and "authjs.callback-url" which for me, seems that something has happened when prosessing the extra data. The authjs cookie: "authjs.session-token" is missing.

When removing refreshToken so the session is not null anymore, the request header looks correct with the "authjs.session-token" cookie now in place.

I haven't calculated the size, but we might be hitting the browser cookie size limit (4kb). I know they have done something for this limitation for NextAuthJs:

https://github.com/nextauthjs/next-auth/discussions/3579#discussioncomment-1929584

Edit: Seemed to only work in dev. Built in prod or preview; session cookie is not set at all when doing changes in the callback as @msardi23 mentioned.

nowaythatworked commented 3 weeks ago

I just tested this on the latest astro, @auth/core and auth-astro version without any issues. Do you still have this problem?

dvartdal commented 3 weeks ago

I'm using my own version setting the refresh cookie in a separate cookie. However, I'll see if I can take a look at it and test!