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

Have a way to set session in server-side setup

Open synaptiko opened this issue 4 years ago • 1 comments

Feature request

Is your feature request related to a problem? Please describe.

As this library states "An isomorphic JavaScript client for Supabase." I would expect it to have a way how I can set session for supabase.auth part in the server-side setup. There is already client-side functionality like that which relies on URL hash: getSessionFromUrl](https://github.com/supabase/gotrue-js/blob/edcd2ffde6dfe81d02700a52a79e635a5ab8839d/src/GoTrueClient.ts#L404).

Describe the solution you'd like

I started using supabase with Remix and I have session information saved in http-only cookie. I consider this safer way, at least for XSS-based attacks (especially because the session contains refresh_token). But currently there is no easy way to set the session when initializing the supabase client on the server-side.

Describe alternatives you've considered

I implemented my own "hacky way" for now but it's a bit fragile, considering I want to keep the dependencies up-to-date. I solved it like this:

import { Session, SupabaseClient, SupabaseClientOptions } from '@supabase/supabase-js';
import { DEFAULT_HEADERS } from '@supabase/supabase-js/dist/main/lib/constants';
import { SupabaseAuthClient } from '@supabase/supabase-js/dist/main/lib/SupabaseAuthClient';
import invariant from 'tiny-invariant';

const supabaseUrl = process.env.PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.PUBLIC_SUPABASE_ANON_KEY;

class ServerSideSupabaseAuthClient extends SupabaseAuthClient {
  setCurrentSession(currentSession: Session) {
    this.currentSession = currentSession;
    this.currentUser = currentSession.user;
  }
}

const DEFAULT_OPTIONS = {
  schema: 'public',
  autoRefreshToken: true,
  persistSession: true,
  detectSessionInUrl: true,
  multiTab: true,
  headers: DEFAULT_HEADERS,
};

export class ServerSideSupabaseClient extends SupabaseClient {
  constructor(protected supabaseUrl: string, protected supabaseKey: string, options?: SupabaseClientOptions) {
    super(supabaseUrl, supabaseKey, options);

    const settings = { ...DEFAULT_OPTIONS, ...options };

    this.auth = this._initSupabaseAuthClientOverride(settings);
  }

  setSession(session: Session) {
    (this.auth as ServerSideSupabaseAuthClient).setCurrentSession(session);
  }

  private _initSupabaseAuthClientOverride({
    autoRefreshToken,
    persistSession,
    detectSessionInUrl,
    localStorage,
    headers,
    fetch,
  }: SupabaseClientOptions) {
    const authHeaders = {
      Authorization: `Bearer ${this.supabaseKey}`,
      apikey: `${this.supabaseKey}`,
    };
    return new ServerSideSupabaseAuthClient({
      url: this.authUrl,
      headers: { ...headers, ...authHeaders },
      autoRefreshToken,
      persistSession,
      detectSessionInUrl,
      localStorage,
      fetch,
    });
  }
}

export function createSupabaseClient() {
  invariant(typeof supabaseUrl === 'string', 'PUBLIC_SUPABASE_URL env var has to be configured');
  invariant(typeof supabaseAnonKey === 'string', 'PUBLIC_SUPABASE_ANON_KEY env var has to be configured');

  return new ServerSideSupabaseClient(supabaseUrl, supabaseAnonKey, {
    autoRefreshToken: true,
    persistSession: false,
    detectSessionInUrl: false,
  });
}

export function isSupabaseSession(value: any): value is Session {
  return (
    value != null &&
    (value as Session).user !== undefined &&
    (value as Session).user !== null &&
    typeof (value as Session).token_type === 'string' &&
    typeof (value as Session).access_token === 'string' &&
    typeof (value as Session).refresh_token === 'string' &&
    typeof (value as Session).expires_at === 'number'
  );
}

With this I can easily set the session:

  const supabase = createSupabaseClient();

  ...

  if (sessionData) {
    invariant(isSupabaseSession(sessionData), 'expected a valid Supabase session');
    supabase.setSession(sessionData as unknown as SupabaseSession);
  }

Additional context

I know there is setAuth but that only works with authToken, while I think I could keep all the info in supabase.auth.session.

First I checked https://github.com/mitchelvanbever/remix-auth-supabase/ but I didn't like it because it seems to be using (SUPABASE_SERVICE_KEY)[https://github.com/mitchelvanbever/remix-auth-supabase/blob/main/examples/email-password/.env.example].

I also checked https://github.com/one-aalam/remix-starter-kit but I would prefer to keep all the logic on server-side (this starter-kit has combination of both server/client-side logic, as far as I can see).

synaptiko avatar Feb 05 '22 18:02 synaptiko

Similar issue here, did you work it out?

lili21 avatar Mar 11 '22 21:03 lili21

Please review our latest docs on server side rendering.

Re-open the issue or continue the discussion if you have further questions.

hf avatar Dec 30 '22 17:12 hf