#I do SSR auth. What's the best way to set up an authenticated client-side Realtime connection?

57 messages · Page 1 of 1 (latest)

shut monolith
#

I am currently handling authentication entirely server-side via an /api/auth endpoint, so I don't have any Appwrite client instances running client-side.

However, Realtime requires being authenticated on the client side to stream events the user has access to.

My challenge is how to leverage the SSR auth I already have in place to securely authenticate a client-side Appwrite instance to subscribe to Realtime events. So far, I see two possible approaches:

  1. Return the session cookie in my server's /api/auth endpoint response and use it to authenticate the client-side Appwrite instance.
  2. Reimplement client-side authentication, separate from my current SSR setup, to authenticate the Appwrite client directly.

For the first approach, I'm concerned that exposing the session cookie to JavaScript (even if it was originally set as httpOnly) opens up security vulnerabilities to XSS attacks or malicious browser extensions. For the second approach, it feels like a step backward in terms of developer experience. Additionally, maintaining both SSR and client-side authentication seems to violate DRY principles.

What’s the best approach to securely set up an authenticated client-side Appwrite Realtime connection without giving up on SSR auth?

alpine kelp
shut monolith
#

Hi Steven, thank you for your response and suggestion.

I do currently have an implementation that gives me access to the session cookie on my backend and on the client. I am just concerned about the potential security risks of returning the session cookie to the client as part of my endpoint response (which theoretically makes it JavaScript-accessible, unlike returning it as a secure set-cookie header configured to be httpOnly).

Another concern I have is that having an authenticated client-side Appwrite client instance (necessary to subscribe to Realtime events) eliminates the control I have over filtering and transforming the data I return to users through my Nuxt server functions. If clients can communicate directly with my Appwrite database without a server endpoint as an intermediary, the only control I have over what they get are explicit Appwrite resource permissions, which are a great baseline, but not as flexible as custom functions. But, I guess that's a different topic altogether. The only issue I currently have with the sharing of the session cookie approach is the risks it opens up.

alpine kelp
shut monolith
alpine kelp
shut monolith
# alpine kelp there are options on the cookie. like the `httpOnly`

Yes, but if I do that then I can't access the cookie with my client-side code, and so I can't use it to authenticate the client-side Appwrite client. That is precisely the reason I opened this thread. If the cookie is set to httpOnly, it is secure, but can't be used client-side. On the other hand, if I return it as part of my API auth response payload, that breaches the security protections of httpOnly, no?

Or maybe I'm wrong? Like, do you know if the data objects server endpoints in Next or Nuxt return can be accessed by malicious browser extensions or via an XSS attack? (assuming the fetch requests happen over https and the fetched data is never rendered, just used by Next or Nuxt internally)

alpine kelp
shut monolith
# alpine kelp you wouldn't access it with client-side code. the browser should send the cookie...

That's right. Currently, on my api/auth endpoint, I get the cookie from the request to determine if a user is already authenticated, and use it to set a session in a server-side Appwrite client instance.

// this is a server-side util:
export function createSessionClient(event: H3Event<EventHandlerRequest>) {
  const runtimeConfig = useRuntimeConfig(event)

  const client = new Client()
    .setEndpoint(runtimeConfig.public.appwriteEndpoint)
    .setProject(runtimeConfig.public.appwriteProject)

  const session = getCookie(event, SESSION_COOKIE)

  if (session)
    client.setSession(session)

  const account = new Account(client)
  const databases = new Databases(client)

  return { account, databases }
}

Now, to subscribe to channels with Appwrite Realtime, I need to set up the connection using a client-side Appwrite client. I would like to use the same client.setSession(SESSION_COOKIE) technique, but to do that I need to have access to the session cookie client-side. It is not enough that the browser automatically sends the cookie along with my request this time because, unlike my server endpoint, Appwrite doesn't automatically instantiate a new client instance and set its session based on the cookies the browser sent along. So, I need to authenticate the client-side Appwrite client instance myself, and to do so, I am currently returning the session cookie along with the response payload of my server auth API endpoint. Again, the problem with that approach is that, as far as I know, it bypasses the security protections of the cookie (even if it was initially set as httpOnly), and exposes it to potential extraction by malicious client-side code, no?

alpine kelp
# shut monolith That's right. Currently, on my `api/auth` endpoint, I get the cookie from the re...

I would like to use the same client.setSession(SESSION_COOKIE) technique
instead of doing client.setSession() the idea is to have the cookie automatically send because that's how cookies work

Appwrite doesn't automatically instantiate a new client instance and set its session based on the cookies the browser sent along
If set up as I suggested, the cookie should automatically be sent to the request to Appwrite and be authenticated

exposes it to potential extraction by malicious client-side code, no?
Honestly, if you had an XSS vulnerability, you have way bigger problems

shut monolith
# alpine kelp > I would like to use the same `client.setSession(SESSION_COOKIE)` technique ins...

Thank you for your explanation. I believe I misunderstood your original suggestion, my apologies.

I'm currently developing my app in localhost, have no custom domain yet, and am using Appwrite's default endpoint (https://cloud.appwrite.io/v1').

In my api/auth server endpoint, I have a login function where I am currently setting the session cookie:

// inside an event handler in /server/api/auth.ts:
  const logIn = async (email: string, password: string) => {
    const session = await authAccount.createEmailPasswordSession(email, password)
    setCookie(event, SESSION_COOKIE, session.secret, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      expires: new Date(session.expire),
      path: '/',
    })
    // ...update payload response object with user id and email
  }

I would like to follow your suggestion of configuring the cookie with domain: '.myapp.com', but since I don't have a domain yet, and I am using Appwrite's default endpoint, I am not sure how to proceed. I thought of calling setCookie twice, once as it is now, and another with domain: 'cloud.appwrite.io' but, if I remember correctly, it's not possible to set cookies for a third-party domain.

I guess for now, I could just add the session cookie to the payload response object and use it with setSession() to authenticate the client-side Appwrite client instance that will establish the Realtime subscription. And, once I have a custom domain, then configure Appwrite's endpoint to live at appwrite.mydomain.com, and update the cookie definition in my logIn function to be automatically sent to .mydomain.com, as you suggested.

Is this reasoning correct, or am I perhaps misunderstanding the challenge of configuring the session cookie for localhost and cloud.appwrite.io?

Thank you for your time and support.

alpine kelp
shut monolith
past lark
#

Sorry to revive this thread, but what solution did you eventually go with? Im facing the same exact type of issues, and even client.setSession with the cookie i return from my nextjs backend api route doesnt work. I can see that realtime connections work with collections with read "any" but not when requiring a valid session. @shut monolith

shut monolith
# past lark Sorry to revive this thread, but what solution did you eventually go with? Im fa...

I tried updating the hosts file to forward requests to appwrite.localhost to cloud.appwrite.io, but couldn't get it to work. The setSession issue you mention seems to be a bug with Realtime. Other authed requests work, but only Realtime doesn't recognize the auth status of instances authenticated with setSession. I posted a thread about it earlier here:

https://discord.com/channels/564160730845151244/1298814622845112352

And, since then, I've opened an issue reporting the bug today in their GitHub:

https://github.com/appwrite/appwrite/issues/8925

Please upvote it if you're having the same issue.

In the meantime, I've been working with Realtime by setting the read permission to any as well, but will implement an authed connection soon. Unfortunately, the only way I see to do that until the issue is resolved is to reimplement auth client-side. I think I will keep my SSR and API endpoints for most requests, and use client-side auth just to set up the instance that subscribes to Realtime. It's an unfortunate duplication of code and there might be two simultaneous sessions per user, but for now I haven't found any other viable workaround 😦

GitHub

👟 Reproduction steps When I authenticate a client-side Appwrite instance with client.setSession(sessionCookie), Realtime doesn't recognize the authed status of the instance. I'm doing SSR a...

shut monolith
# blissful rivet Any update on this.

Until Appwrite supports setSession to authenticate users with the secret from SSR auth, I don't see any new workarounds to the ones already mentioned. Since I don't have a custom Appwrite endpoint yet, I'm doing auth server-side and client-side (with client-side auth only to authenticate the Realtime connection).

timid crystal
obtuse flint
timid crystal
#

Not until Appwrite themselves replies

obtuse flint
timid crystal
#

I'm using it myself in production

obtuse flint
#

ok good ty then for taking your time to figure it out

timid crystal
#

Ye nw, I ran out of options so I coded it myself

obtuse flint
# timid crystal I'm using it myself in production

may I ask what is your way of working it around both the client and the server, like do you pass down the cookie and set the session to it or do you use jwt, or what, I know I might sound stupid I do know how to implement it properly I just need your prespective

timid crystal
# obtuse flint may I ask what is your way of working it around both the client and the server, ...

For me no realtime is established on the server (nextjs/vercel doesn't natively support long running server function anyway). So more accurately it supports SSR without error but don't establish any connections. To use it there is two files in the github issue I uploaded, the AppwriteRealtimeClient is basically Appwrite client without any other features except the realtime (still works the same way as normal Appwrite client, I remove other functionality to minimise the bundle size). The thing I modified is now if you use setSession on the client it will work for realtime just like any other modules, or you can use the other file I provided which get the Appwrite realtime client already authenticated. Another change I made is that the subscribe function will now return a status change to track if the client is disconnected or hanging.

obtuse flint
#

I was just talking about realtime on the client, I understand what you did in your files I was talking about getting an advice on passing the needed parameter in setSession weather it was a session cookie or jwt, I was asking what do you have in your setup do you pass down the cookie and set the session

#

Im worried about security

#

Call it nextjs 101 if you want but I just need to understand

timid crystal
#

thank you @bill-zhanxg! ✌️ ...would the session secret from the currently active session cookie also be valid for this, or does it have to be a new/separate session? ...and is it supposed to be also working with taking the secret from a token (createToken return) instead of a session secret from createSession (or similar methods that return the session)?

"@jspm2013 yes, it can be any active session (it works just like how the normal Appwrite client works, should read the docs). And no, token secret has nothing to do with session, must be session secret (aka I just call it session)."

lusty patio
#

Im digging into Nuxt SSR and have found alot of your comments @timid crystal. Im confused if you pass the session secret to the client or not?

I have done that during testing with Nuxt and I know Nuxt will put it in a script tag with NUXT_DATA id. That just feels really bad from a security perspective

obtuse flint
lusty patio
#

Its a httpOnly cookie though

#

With secure option set to true

obtuse flint
#

and technically there is no other way for you to make it work without passing it to the client

#

when it comes to nextjs I would be using server components and would get the cookie straight from the headers and pass it down to a client component without any requests or server actions

obtuse flint
#

(im not exactly sure this is solid :))

timid crystal
# lusty patio Im digging into Nuxt SSR and have found alot of your comments <@7683674299737047...

Ye there is no other way of making it work, the client MUST know the session secret in order to establish the realtime connect. Actually, there is one other way of making it work but it might break a lot of stuff, where you make the user authenticate twice, one for your own SSR which set a httpOnly cookie on path /, and another for Appwrite to set a_session_{project_id} cookie on path .cloud.appwrite.com (or custom domain).

#

Tbh it's a lot better than Appwrite's original code where you have to set the fallbackCookie in localStorage to make it work.

#

Or another way is to create a new session on the user everytime when the user access the realtime page, and pass it. When the user leaves the page revoke the session secret

#

There are many ways you could make this more secure, just depends on if you want to implement them or not

timid crystal
lusty patio
#

I haven’t tried this but in theory, when server side rendering I could get the user and pass to frontend to be able to display all info correctly.

Set the web-sdk endpoint to point to a catch all route in Nuxt API, add the x-appwrite-header on the server and then proxy it to my appwrite endpoint

#

The hard part about that is to proxy ws connections in node

#

Then you wouldn’t have to actually verify the client, it will work in development environment since I’m keeping track of the cookie. And from what I understand always have the c-appwrite-session header which would be missing unless I used your library @timid crystal

timid crystal
lusty patio
#

Not using serverless

#

I have a dedicated machine

timid crystal
#

ah okay. Yes you could try it should work, but know that it would take a lot of effort to make it work

lusty patio
#

I have proxied request in docker network before, but I wonder if a http 300 redirect code is enough

#

After you set the header

#

I’ll get back to you if that works

#

Because then it shouldn’t be any cpu time on serverless

timid crystal
#

ye alright

lusty patio
#

Seems like http 302 will remove the headers because the client will just resend the request