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

SvelteKitAuth: How to update session from client to server and vice versa #9147

Closed
aakash14goplani opened this issue Nov 15, 2023 · 17 comments
Closed
Labels
documentation Relates to documentation triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.

Comments

@aakash14goplani
Copy link

What is the improvement or update you wish to see?

Scenario 1: Client to Server

In SvelteKitAuth, how to communicate session updates from client to server?

In NextAuth, we do something like:

const { data: session, status, update } = useSession()
<button onClick={() => update({ name: "John Doe" })}>Edit name</button>

Scenario 2: Server to Client

In SvelteKitAuth, we manage session within jwt({ ... }) and session({ ... }) callbacks. Let's consider a scenario in which user object was mutated by some event (like response from API call), how to update global session object in that case?

Is there any context that might help us understand?

The NextAuth has many helper methods exposed for both Client and Server API that makes it easy t implement above mentioned scenarios. How do we implement those in SvelteKitAuth?

Does the docs page already exist? Please link to it.

https://next-auth.js.org/getting-started/client#updating-the-session

@aakash14goplani aakash14goplani added documentation Relates to documentation triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime. labels Nov 15, 2023
@aakash14goplani
Copy link
Author

aakash14goplani commented Dec 18, 2023

@stalkerg
Copy link

Seems like it's not implemented.

@stalkerg
Copy link

Okey, it's actually supported, you can use next function:

import { base } from '$app/paths';
import type { Session } from '@auth/core/types';

export async function updateSession(data: Session): Promise<Session | null> {
  const sessionUrl = `${base}/auth/session`;

  const csrfTokenResponse = await fetch(`${base}/auth/csrf`);
  const { csrfToken } = await csrfTokenResponse.json();

  const res = await fetch(sessionUrl, {
    method: 'post',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      data,
      csrfToken,
    }),
  });

  const json = await res.json();
  return json;
}

also you should extend JWT callback in SvelteKitAuth options:

     async jwt({
        token,
        user,
        session,
        trigger,
      }: {
        token: JWT,
        user: SessionUser,
        session?: Session,
        trigger?: string | undefined,
      }) {
        if (trigger === 'signIn' && user) {
          Object.assign(token, { user });
        } else if (trigger === 'update') {
          Object.assign(token, { user: session?.user });
        }
        return token;
      },

it's should cover update trigger.

@aakash14goplani
Copy link
Author

Hello @stalkerg - thanks for your efforts. Updating session from client to server is still broken in SvleteKitAuth. The code you have provided won't work as the session-token cookie does not gets updated. You can refer this PR (and associated issues) for more detail understanding. Thanks.

@stalkerg
Copy link

stalkerg commented Jan 15, 2024

@aakash14goplani it's working fine for me, I can update session user and it's keeps during browser restart.
cookie authjs.session-token also updated correctly.

@stalkerg
Copy link

stalkerg commented Jan 15, 2024

The PR #9497 is more about run getSession or set session from the SSR, if you will run my function from the client it will not a case.
Also, with a small change it will work even on SSR.

@aakash14goplani
Copy link
Author

The PR #9497 is more about run getSession or set session from the SSR, if you will run my function from the client it will not a case. Also, with a small change it will work even on SSR.

Which small change Yury? Can you please let me know?

@stalkerg
Copy link

stalkerg commented Jan 16, 2024

@aakash14goplani this function can return also cookie and after that you can proxy a such cookie to the browser.

return { json, cookie: res.headers.getSetCookie() };

something like this, after in your SSR function you can just set a such cookies.

@aakash14goplani
Copy link
Author

aakash14goplani commented Feb 4, 2024

After recent changes made in SvelteKitAuth v0.10 and v0.11, I am using following approach for to-and-fro communication.

Client to Server

General Idea: Update data by making API call

  • Client initiates an API request and passes a query param (from ".svelte" files)
    async function updateUserData() {
      await fetch(base + '/api/update-user-data?query=update-user-data', {
        method: 'POST'
      });
    }
  • We can create a dummy API route to handle this request. (routes/api/update-user-data)
    export const POST = (() => {
      try {
        return new Response('User data updated', { status: 200 });
      } catch (error: any) {
        return new Response('Error while updating user data: ' + error?.message, { status: 200 });
      }
    }) satisfies RequestHandler;
  • Since SvelteKitAuth is configured via hooks, it can intercept all the incoming network request. We can filter the one with query params within the "jwt" callback. Why "jwt" callback - because these callbacks are triggered multiple times and on every network requests.
    async jwt({ token, account, profile }) {
      ...
      const shouldUpdateToken = args.event.url.searchParams.get('query') === 'update-user-data';
      if (shouldUpdateToken) {
        console.log('token before update [library]: ', token?.library);
        token = {
          ...token,
          library: 'SvelteKitAuth: v0.11.1' // <- add items here, hardcode values or extract out of request payload
        };
        console.log('token after update [library]: ', token?.library);
      }
      return token;
    }
  • With the introduction of auth(), the newly added values will persist in cookies and hence server will be updated with latest data without user having to re-login (WHICH WAS NOT POSSIBLE for versions <v0.10)

Server to Client

General Idea: Update data using hydration

  • In the root "layout.ts" file, we can return session object to client. Please note - I said "layout.ts" and not "layout.server.ts"
  • The "layout.server.ts" will hydrated only once during initial page load and if user updates data mid-way, they would have to refresh the page or re-login to see updated data on client side. Hence idea is to stick with "layout.ts" file.
  • In "layout.ts", there is a special hack that must be implemented (you can do this in layout.server.ts but I prefer layout.ts). The hack is to include "url" parameter and do some operation using url parameter. This is important as "url" parameter will force this load function to execute on every navigation change and thus we can return latest session information back to client!
  • We can create a dummy end point that returns the session data (routes/api/get-user-data)
    export const GET = (async ({ locals }) => {
      const session = await locals.auth();
      return new Response(JSON.stringify(session), { status: 200 });
    }) satisfies RequestHandler;
  • Here is the complete code
    export const load = (async ({ url, fetch, data }) => {
      let session;
      try {
        if (browser) {
          if (url.href.includes('xxx')) console.log(''); // <- very important
          const sessionObject = await fetch(url.origin + base + '/api/get-user-data');
          session = await sessionObject.json();
        } else {
          session = data.session;
        }
      } catch (ex: any) {
        console.log('Error fetching session data, defaulting to parent data');
        session = data.session;
      }
      return { session };
    }) satisfies LayoutLoad;
  • Client can get latest data using $page.data.session

This is the long route that I have to implement in SvelteKit as there are no helper methods exposed by SvelteKitAuth (when compared with NextAuth). So @balazsorban44 @ThangHuuVu and @ndom91 can you please go through this approach and let me know if this is correct way (for now) or something could be improved?

Example Repo: https://github.com/aakash14goplani/sveltekit-auth-session-token/tree/new-version-test

@benevbright

This comment was marked as off-topic.

@benevbright

This comment was marked as off-topic.

@benevbright

This comment was marked as off-topic.

@aakash14goplani
Copy link
Author

Ok. I found what was my issue. This explained . I was calling auth on Next.js server component rendering and I expected it would update the cookie. But It doesn't as the comment explains. I should call the API separately from client component useEffect or whatever. Now cookie gets updated correctly.

@benevbright - Glad that you're able to find the solution but you posted in a wrong thread. This thread is meant for the "SvelteKit" framework and not "Next" framework!

@benevbright
Copy link

@aakash14goplani you're right. I hid my comments as off topic. 🙏

@debuggerpk
Copy link

comment by @stalkerg works for me. I am doing the complete registration when I need the user to fill the profile after registration.

here is my code.

export const actions: Actions = {
  /**
   *
   * @param {RequestEvent} event sveltekit request event
   * @returns
   */
  complete: async (event: RequestEvent) => {
    const form = await superValidate(event.request, zod(schema));
    const session = (await event.locals.auth()) as QuantmAdapterSession;

    if (!form.valid) {
      return fail(400, { form });
    }

    const name = form.data.name;

    const getopts = { method: 'GET' };
    const postopts = { method: 'POST' };
    const headers = { 'Content-Type': 'application/json' };

    const refresh = async (team: Team) =>
      event
        // get authjs crsf token
        .fetch(`${base}/auth/csrf`, { ...getopts })
        .then(response => response.json())
        // assign user to team
        .then(csrf => {
          // @ts-expect-error we know there will be user.
          session.user.team_id = team.id;
          return JSON.stringify({ data: session, ...csrf });
        })
        // update token with updated user
        .then(body => event.fetch(`${base}/auth/session`, { ...postopts, headers, body }))
        .then(response => response.json())
        .then(() => ({ form, team }));

    return api().auth.createTeam({ name }).then(refresh);
  },
};

@ndom91
Copy link
Member

ndom91 commented Apr 12, 2024

I haven't read through all of the posts in this thread, but there is an update / unstable_update method that's returned from NextAuth() in the Next.js side of things (live usage example here: https://next-auth-example.vercel.app/client-example).

So for implementing this in SvelteKit + Auth.js projects, maybe its helpful to look at that next-auth implementation. Yall can find it here: https://github.com/nextauthjs/next-auth/blob/main/packages/next-auth/src/lib/actions.ts#L110-L133

@aakash14goplani
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Relates to documentation triage Unseen or unconfirmed by a maintainer yet. Provide extra information in the meantime.
Projects
None yet
Development

No branches or pull requests

5 participants