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

Re-authenticate and re-fetch Customer-data with auth-token. #2940

Closed
KariNarhi28 opened this issue Jul 9, 2024 · 6 comments
Closed

Re-authenticate and re-fetch Customer-data with auth-token. #2940

KariNarhi28 opened this issue Jul 9, 2024 · 6 comments

Comments

@KariNarhi28
Copy link

KariNarhi28 commented Jul 9, 2024

Is your feature request related to a problem? Please describe.
I have the Apollo Client set up with cache persistence and the middleware/afterware handling the auth-token to/from LocalStorage, but when doing page refresh, the authenticated state is lost even when the token clearly is still saved.

Then the activeCustomer-query returns null, even though the query request has the auth-token in the headers.

As said in Vendure's documents, the activeCustomer-query only works for registered and logged-in Customers.

So how do I fetch that data with the saved auth-token since I cannot automatically do a login-mutation which requires email/password -input?

Describe the solution you'd like
A new mutation that re-authenticates the user with the saved auth-token and then returns the user (Customer) data.

Describe alternatives you've considered
Extend activeCustomer to optionally take in the auth-token as a parameter and return the data if the auth-token is valid.

Additional context
Basically the issue here is that after I have registered and logged-in as a Customer, I have to re-login every single time if I refresh page or when VS Code does it when saving code-changes.

If the issue is with Apollo Client -configuration, I'd appreciate an example of a working solution for persisting the auth-state.

EDIT: As a side-note, how is the "rememberMe"-field supposed to work?

I set it as true when logging in but it does not show in the database or in the Customers data.

How is it supposed to remember my login or credentials?

@KariNarhi28
Copy link
Author

Here are the HttpLink and Middleware/Afterware for the Apollo-client:
(based on the Angular Storefront -starter, which unfortunately seemed to be broken):

/////////////// HTTP LINK SETUP ////////////////////////

  // Get the API host, port, and path from the environment
  const { apiHost, apiPort, shopApiPath } = environment;

  // Define the GraphQL API URI
  const uri = `${apiHost}:${apiPort}/${shopApiPath}`;

  /**
   * HTTP options
   */
  const httpOptions: Options = {
    uri: () => {
      if (languageCode) {
        return `${uri}?languageCode=${languageCode}`;
      } else {
        return `${uri}`;
      }
    },
  };

  /**
   * HTTP link handler
   */
  const httpLinkHandler = httpLink.create(httpOptions);

  /////////////// AFTERWARE & MIDDLEWARE SETUP ////////////////////////

  /**
   * Afterware which sets the auth token in localStorage
   */
  const afterware = new ApolloLink((operation, forward) => {
    return forward(operation).map((response) => {
      // Apollo operation context
      const context = operation.getContext();
      // Auth header
      const authHeader: string =
        context.response.headers.get('vendure-auth-token');

      // Check if the auth header is present and the platform is the browser
      if (authHeader && isPlatformBrowser(platformId)) {
        // If the auth token has been returned by the Vendure
        // server, we store it in localStorage
        localStorage.setItem(AUTH_TOKEN_KEY, authHeader);
      }
      return response;
    });
  });

  /**
   * Auth middleware which sets the auth token in the request headers
   */
  const auth = new ApolloLink((operation, forward) => {
    // Check if the platform is the browser
    if (isPlatformBrowser(platformId)) {
      // Get the auth token from localStorage
      const authToken = localStorage.getItem(AUTH_TOKEN_KEY);

      if (authToken) {
        // Set the operation context
        operation.setContext({
          // Set the auth token in the request headers
          headers: channelToken
            ? new HttpHeaders()
                .set('Authorization', `Bearer ${authToken}`)
                .set('vendure-token', channelToken)
            : new HttpHeaders().set('Authorization', `Bearer ${authToken}`),
        });
      }
    }
    return forward(operation);
  });

At the end of the factoryFunction for Apollo-client:

 // Return Apollo client options
  return {
    credentials: 'include', // 'same-origin' | 'include' | 'omit
    cache: cache,
    link: ApolloLink.from([auth, afterware, httpLinkHandler]),
    ssrForceFetchDelay: 500,
    ssrMode: true,
  };

Apollo-client provider setup:

/**
 * Apollo client provider
 */
const APOLLO_CLIENT_PROVIDER: FactoryProvider = {
  provide: APOLLO_OPTIONS,
  useFactory: apolloOptionsFactory,
  deps: [HttpLink, TransferState, PLATFORM_ID],
};

/**
 * Apollo providers
 */
export const apolloProviders: ApplicationConfig['providers'] = [
  Apollo,
  APOLLO_CLIENT_PROVIDER,
];

@KariNarhi28
Copy link
Author

OK. I solved it.

It was the Apollo-Angular Client Hydration for SSR that prevented the auth-session:

if (isBrowser) {
    // Get the state from the transfer state
    const state = transferState.get(STATE_KEY, {});

    // Restore the Apollo cache
    cache.restore(state);
  } else {
    // Serialize the Apollo cache
    transferState.onSerialize(STATE_KEY, () => {
      return cache.extract();
    });

    // Reset cache after extraction to avoid sharing between requests
    cache.reset();
  }

I believe the Client Hydration is useful for SSR but apparently the documented way breaks the querying logic.

@KariNarhi28
Copy link
Author

Extra explanation after investigation:

Apparently what happened was that due to Hydration, the queries were run on server-side and when client-side is rendered, those queries get hydrated from cache, but since the auth-token was in localstorage, the server-side activeCustomer-query could not get the session and then the client-side query got null-response as well.

It might work if there was someway to handle the auth-token on server side.

@mschipperheyn
Copy link
Collaborator

Just for reference: storing authentication tokens in localStorage is not considered secure. It's better to store it as a cookie. https://medium.com/@coderoyalty/should-you-store-sensitive-tokens-in-localstorage-ce13698676f3

@KariNarhi28
Copy link
Author

So Vendure's own documentation instructs this bad habit?

localStorage is used in these examples:

@michaelbromley
Copy link
Member

IMO there is some nuance here:

In the linked article the author gives 2 scenarios that localstorage is vulnerable to:

  1. Lack of encryption and attacker gains access to device. IMO in this scenario everything is moot anyway if attacker has got that far.
  2. XSS attacks.

But then in the cookie section, XSS is also a vulnerability for cookies, which the author says "utilize secure web development frameworks and libraries that offer built-in protections against XSS attacks, such as AngularJS, React, and Vue.js.". But the same applies to localstorage.

On balance I'd say that cookies have the edge over localstorage when used with httponly, secure & samesite options. But the major disadvantage is that they can be really troublesome when your API is on a different domain to your client app, which is very common with headless projects.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants