auth-astro icon indicating copy to clipboard operation
auth-astro copied to clipboard

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

Open msardi23 opened this issue 1 year ago • 3 comments

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 😀

msardi23 avatar Jun 29 '23 23:06 msardi23

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.

dvartdal avatar Dec 30 '23 10:12 dvartdal

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

nowaythatworked avatar Jun 12 '24 14:06 nowaythatworked

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!

dvartdal avatar Jun 14 '24 13:06 dvartdal