Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is it safe to store access token in next-auth session? #7976

Closed
JeremyXXXuuu opened this issue Jul 7, 2023 · 15 comments
Closed

Is it safe to store access token in next-auth session? #7976

JeremyXXXuuu opened this issue Jul 7, 2023 · 15 comments
Labels
question Ask how to do something or how something works

Comments

@JeremyXXXuuu
Copy link

Question 💬

What I want is to access the protected data from resource provider, so I need to pass the access token to the backend API

Using next-auth, I was able to store the access token to the session. And I find out this official example: https://github.com/nextauthjs/next-auth-refresh-token-example/tree/57f84dbc50f30233d4ee389c7239212858ecae14.
Actually, put access_token in next-auth session, thats great, we can use getServerSession() to get our access token and fetch protected resourse in server side, but I found out that every time when we called useSession() in client side, it triggers a get request on the client side ( your_api_url/auth/session ) to retrieve the session information, including the access token. This means that the access token could potentially be exposed in the client-side network traffic.
image
image

Does access token exposed in the client mean that there is some security risk? Authorization code flow requires the completion of code and token exchange in the server side is to protect the token in the server side rather than the client.

Is there some other way to store access token in nextauth?

How to reproduce ☕️

/api/[...nextauth]/route.ts

  callbacks: {
    async jwt({ token, user, account }) {
      // Initial sign in
      if (account && user) {
        return {
          accessToken: account.access_token,
          accessTokenExpires: Date.now() + account.expires_in * 1000,
          refreshToken: account.refresh_token,
          user
        }
      }

      // Return previous token if the access token has not expired yet
      if (Date.now() < token.accessTokenExpires) {
        return token
      }

      // Access token has expired, try to update it
      return refreshAccessToken(token)
    },
    async session({ session, token }) {
      session.user = token.user
      session.accessToken = token.accessToken
      session.error = token.error

      return session
    }
  }
})

/index.ts

export default function Home() {
  const { data: session } = useSession()

Contributing 🙌🏽

Yes, I am willing to help answer this question in a PR

@JeremyXXXuuu JeremyXXXuuu added the question Ask how to do something or how something works label Jul 7, 2023
@balazsorban44
Copy link
Member

balazsorban44 commented Jul 9, 2023

you don't have to expose the access token to the client, see the docs https://next-auth.js.org/configuration/callbacks#session-callback

This callback decides what's exposed of the session via the /api/auth/session endpoint.

You can proxy API requests through the Next.js backend where you can safely read the whole content of the session, including your access_token.

By default, we already only expose name, image and email for presentational purposes.

@waza-ari
Copy link

Dear @balazsorban44 , sorry for reopening this issue. I wonder if you could give any guidance on how to safely read the whole content of the session in the Next.js backend? I've been trying to do that for some time but haven't found a way to retrieve this information.

@HaseebAhmed456
Copy link

@waza-ari I believe you can do so with the help of next-auth getServerSession method. https://next-auth.js.org/configuration/nextjs#getserversession

@waza-ari
Copy link

@HaseebAhmed456 I tried that, it returns the same attributes that are exposed to the client session using the session callback.

@HaseebAhmed456
Copy link

HaseebAhmed456 commented Apr 18, 2024

@waza-ari You need to call getServerSession inside your next backend i.e API routes which provides as public end point and you can route your requests through that endpoint. I also couldn't find any other solution as I was having the same issue

@waza-ari
Copy link

I understand. I'm using server components and server data fetching already, so I don't need the data on the client. running getServerSession on server side also only returns whatever is exposed by the session callback. I'm looking for a way to get the AT on server side without exposing it to the client session.

@HaseebAhmed456
Copy link

@balazsorban44 Is there any other solution to this other than routing your requests through next backend proxy api. As we need access_token in our client components but getting this info via useSession exposes the access_token in network calls

@waza-ari
Copy link

if you need it in your client components it has to be sent to the client somehow, there is no way around it. That's different from our use case, as we only want to get the AT on the server side.

@HaseebAhmed456
Copy link

I understand. I'm using server components and server data fetching already, so I don't need the data on the client. running getServerSession on server side also only returns whatever is exposed by the session callback. I'm looking for a way to get the AT on server side without exposing it to the client session.

Well in your case you can use server actions which are basically functions that only run on the server. https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations

@waza-ari
Copy link

I fully understand and were using them. Again, I'm looking for a way to get the AT in the server actions without exposing them to the client. getServerSession doesn't include the AT unless added via the session callback, but that's adds the AT ALSO to the client which we want to avoid.

@HaseebAhmed456
Copy link

HaseebAhmed456 commented Apr 18, 2024

I fully understand and were using them. Again, I'm looking for a way to get the AT in the server actions without exposing them to the client. getServerSession doesn't include the AT unless added via the session callback, but that's adds the AT ALSO to the client which we want to avoid.

vercel/next.js#52006
You might want to look at this. They discussed the problem in great detail. I think we might need to encrypt the access_token ourselves and then decrypt it before sending to secure API's. Manual excryption feels more intuitive to me then other solutions available for now

@rafaelalvesmartins
Copy link

Hello! I am using version 5.0.0-beta.4 of NextAuth. To retrieve the session on the server-side, I used the following code:

javascript
Copy code
import { auth } from "@/auth";

export default async function Page() {
const session = await auth();
const token = session.accessToken;

return (

Welcome {session?.user.name}!

);
}
I found this code at: https://authjs.dev/getting-started/migrating-to-v5

@olems
Copy link

olems commented Jun 5, 2024

The only clean way I have found to do this in v5 without exposing the access token client side is to use getToken() from next-auth/jwt to decode the jwt token cookie serverside, even though the v5 docs says using this is not recommended. This requires the authjs secret and the hashing salt. The latter is undocumented but you can find the details in #9133

@andrew-stratyfy
Copy link

@waza-ari I am assuming you don't mind the access token being present in the encrypted JWT, as long as the client can't access the decrypted version. Obviously if you don't even want the client to get the encrypted access token you can't store it in the JWT, period.

I eventually figured out that while you have to pass a config both when setting up the next-auth endpoints and sign-in / sign-out functions, and when getting the auth() function, it doesn't have to be exactly the same. Hence:

const baseConfig = {
  callbacks: {
    async jwt(...) { ... },
    // no session(), or your custom session() that does not add the access token
  },
  ...,
};

const serverConfig = {
  ...baseConfig,
  callbacks: {
    ...baseConfig.callbacks,
    async session({ session, token }) {
      session.accessToken = token.accessToken;
      // etc
      return session;
    },
  },
};

// The GET handler doesn't get the access token, so neither does the client
const { handlers, signIn, signOut } = NextAuth(baseConfig);

// This will get the access token for use server-side
const { auth } = NextAuth(serverConfig);

We are on 5.0.0beta, so this example does not use the removed getServerSession(), but I imagine it's similar.

@internalG
Copy link

Anyone tried this solution? It's not documented but promising. Is it the best practice to hide sensitive data from client but available on server? Since v5 is still beta I don't want to be broken later.

@waza-ari I

const baseConfig = {
  callbacks: {
    async jwt(...) { ... },
    // no session(), or your custom session() that does not add the access token
  },
  ...,
};

const serverConfig = {
  ...baseConfig,
  callbacks: {
    ...baseConfig.callbacks,
    async session({ session, token }) {
      session.accessToken = token.accessToken;
      // etc
      return session;
    },
  },
};

// The GET handler doesn't get the access token, so neither does the client
const { handlers, signIn, signOut } = NextAuth(baseConfig);

// This will get the access token for use server-side
const { auth } = NextAuth(serverConfig);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Ask how to do something or how something works
Projects
None yet
Development

No branches or pull requests

8 participants