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

fetchAuthSession is very slow on Angular SSR #14190

Open
3 tasks done
martin-yumsto opened this issue Feb 4, 2025 · 17 comments
Open
3 tasks done

fetchAuthSession is very slow on Angular SSR #14190

martin-yumsto opened this issue Feb 4, 2025 · 17 comments
Assignees
Labels
Angular Related to Angular 2+ Auth Related to Auth components/category question General question SSR Issues related to Server Side Rendering

Comments

@martin-yumsto
Copy link

Before opening, please confirm:

JavaScript Framework

Angular

Amplify APIs

Authentication

Amplify Version

v6

Amplify Categories

auth

Backend

Other

Environment information

 System:
    OS: macOS 15.3
    CPU: (12) arm64 Apple M3 Pro
    Memory: 1.89 GB / 36.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.18.0 - ~/.nvm/versions/node/v20.18.0/bin/node
    npm: 10.8.2 - ~/.nvm/versions/node/v20.18.0/bin/npm
    pnpm: 8.14.1 - /opt/homebrew/bin/pnpm
  Browsers:
    Chrome: 132.0.6834.160
    Safari: 18.3
  npmPackages:
    @angular-devkit/build-angular: ^19.1.5 => 19.1.5 
    @angular/animations: ^19.1.4 => 19.1.4 
    @angular/cdk: ^19.1.2 => 19.1.2 
    @angular/cli: ^19.1.5 => 19.1.5 
    @angular/common: ^19.1.4 => 19.1.4 
    @angular/compiler: ^19.1.4 => 19.1.4 
    @angular/compiler-cli: ^19.1.4 => 19.1.4 
    @angular/core: ^19.1.4 => 19.1.4 
    @angular/forms: ^19.1.4 => 19.1.4 
    @angular/localize: ^19.1.4 => 19.1.4 
    @angular/platform-browser: ^19.1.4 => 19.1.4 
    @angular/platform-browser-dynamic: ^19.1.4 => 19.1.4 
    @angular/platform-server: ^19.1.4 => 19.1.4 
    @angular/router: ^19.1.4 => 19.1.4 
    @angular/service-worker: ^19.1.4 => 19.1.4 
    @angular/ssr: ^19.1.5 => 19.1.5 
    @apollo/client: 3.11.10 => 3.11.10 
    @aws-crypto/sha256-browser: ^5.2.0 => 5.2.0 
    @aws-sdk/signature-v4: ^3.374.0 => 3.374.0 
    @contentful/rich-text-types: ^17.0.0 => 17.0.0 
    @rive-app/canvas-lite: ^2.25.2 => 2.25.2 
    @segment/analytics-next: ^1.76.0 => 1.76.0 
    @sentry/angular: ^8.41.0 => 8.41.0 
    @types/crypto-js: ^4.1.1 => 4.2.2 
    @types/express: ^4.17.21 => 4.17.21 
    @types/jasmine: ~3.8.0 => 3.8.2 
    @types/node: ^20.17.6 => 20.17.6 
    apollo-angular: ^8.0.0 => 8.0.0 
    aws-amplify: ^6.12.0 => 6.12.0 
    canvas-confetti: ^1.9.2 => 1.9.2 
    contentful: ^11.2.1 => 11.2.1 
    express: ^4.15.2 => 4.18.2 
    graphql: ^16.9.0 => 16.9.0 
    is-mobile: ^4.0.0 => 4.0.0 
    jasmine-core: ~3.8.0 => 3.8.0 
    karma: ~6.3.0 => 6.3.20 
    karma-chrome-launcher: ~3.1.0 => 3.1.1 
    karma-coverage: ~2.0.3 => 2.0.3 
    karma-jasmine: ~4.0.0 => 4.0.2 
    karma-jasmine-html-reporter: ~1.7.0 => 1.7.0 
    plyr: ^3.7.8 => 3.7.8 
    rxjs: ~7.5.6 => 7.5.7 
    tslib: ^2.3.0 => 2.6.2 
    typescript: ~5.6.3 => 5.6.3 
  npmGlobalPackages:
    corepack: 0.29.3
    npm: 10.8.2

Describe the bug

I have noticed fetchAuthSession is very slow initially no matter the package - client/server. It's especially an issue on the SSR as it's purpose is to optimize for speed.

It quite commonly takes around 300ms for the first (and only on the SSR) fetchAuthSession to get resolved. I noticed quite some calls are seemingly redundant and synchronous by monkey patching fetch:

Getting user info
runWithAmplifyServerContext: 0.101ms
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetId
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetId
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetCredentialsForIdentity
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetCredentialsForIdentity
[SSR Fetch] x-amz-target: AWSCognitoIdentityProviderService.GetUser
userInfo: 450ms

I have found #14079 and linked PR and issue which reports similar for NextJs. Unfortunately the fix seems to only address the issue for nextJs, as I'm on 6.12.0 and still face them.

Are there any plans to address the issue for lower level adapter which we need to use for Angular, too ? Or is my implementation likely wrong ?

Expected behavior

It should fetch fast

Reproduction steps

I only use Amplify Auth v6 gen 1 with backend deployed by terraform.
Framework is Angular SSR V19; I tried to remove all the Angular specific code, to make code snippet as small as possible

Code Snippet

export const runWithAmplifyServerContext = <T>(
  cookies: Record<string, string>,
  operation: (contextSpec: AmplifyServer.ContextSpec) => Promise<T>,
): Promise<T> => {
  const cookieStorage = cookieKeyValueStorage(cookies);

  console.time('runWithAmplifyServerContext');
  return runAmplifyServerContext(
    amplifyConfig,
    {
      Auth: {
        tokenProvider: createUserPoolsTokenProvider(
          amplifyConfig.Auth,
          cookieStorage,
        ),
        credentialsProvider: createAWSCredentialsAndIdentityIdProvider(
          amplifyConfig.Auth,
          cookieStorage,
        ),
      },
    },
    (context) => { console.timeEnd('runWithAmplifyServerContext'); return operation(context)},
  );
};

  private async getUserInfo(): Promise<UserInfo | undefined> {
    const taskCleanup = this.taskService.add();

    console.log('Getting user info');
    console.time('userInfo');
    const userInfo = await (this.isPlatformBrowser
      ? this.getClientUserInfo()
      : this.getServerUserInfo());
    console.timeEnd('userInfo');
    taskCleanup();

    return userInfo;
  }

  private async getServerUserInfo(): Promise<UserInfo | undefined> {
    if (!this.cookies) {
      return undefined;
    }

    return await runWithAmplifyServerContext(this.cookies, (contextSpec) =>
      Promise.all([
        serverFetchAuthSession(contextSpec),
        serverFetchUserAttributes(contextSpec),
      ]),
    )
      .then(([session, userAttributes]) => ({
        id: userAttributes['custom:id'] ?? '',
        username: userAttributes.email ?? '',
        groups: new Set<string>(
          session.tokens?.accessToken.payload[
            'cognito:groups'
          ] as Array<string>,
        ),
      }))
      .catch(() => {
        return undefined;
      });
  }

Log output

// Put your logs below this line


aws-exports.js

export const amplifyConfig = {
  Auth: {
    Cognito: {
      allowGuestAccess: true,
      userPoolId: 'eu-central-X,
      identityPoolId: 'eu-central-X',
      userPoolClientId: 'XXXX',
    },
  },
};

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

@github-actions github-actions bot added pending-triage Issue is pending triage pending-maintainer-response Issue is pending a response from the Amplify team. labels Feb 4, 2025
@cwomack cwomack added Auth Related to Auth components/category Angular Related to Angular 2+ SSR Issues related to Server Side Rendering labels Feb 5, 2025
@cwomack cwomack self-assigned this Feb 5, 2025
@cwomack
Copy link
Member

cwomack commented Feb 5, 2025

Hey, @martin-yumsto 👋 and thanks for opening this issue. Can you clarify if you're using the awsCredentials on the server side at all? And appreciate you linking the related Next.JS issue and fix. We'll look into this on the Angular side with SSR as well.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 5, 2025
@cwomack cwomack added question General question pending-community-response Issue is pending a response from the author or community. and removed pending-triage Issue is pending triage labels Feb 5, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 5, 2025

Hi @martin-yumsto looking at your logs

[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetId
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetId
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetCredentialsForIdentity
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetCredentialsForIdentity

Did you actually observe duplicate network calls within one runWithAmplifyServerContext() call?

@martin-yumsto
Copy link
Author

Hey, @martin-yumsto 👋 and thanks for opening this issue. Can you clarify if you're using the awsCredentials on the server side at all? And appreciate you linking the related Next.JS issue and fix. We'll look into this on the Angular side with SSR as well.

Hello, appreciate you having a look into this as it was bothering me for a month now.

I believe so, if I understand question well. My app is PWA and I wanted to keep it simple, so I don't make any exceptions for SSR and auth secured part of the code. Got this service that signs all the requests, my backend gateway of choice is AppSync which doesn't support real unauth access, so I need to sign requests for IAM anyway:

export class AccessService {
  private isPlatformBrowser = isPlatformBrowser(inject(PLATFORM_ID));
  private cookies = extractCookiesFromRequest(
    inject<Request | null>(REQUEST, { optional: true }),
  );

  accessHeaders(
    request: HttpRequest<unknown>,
  ): Observable<Record<string, any>> {
    return from(this.getAccessHeaders(request));
  }

  private async getServerAuthSession(): Promise<AuthSession> {
    if (!this.cookies) {
      return {};
    }

    return await runWithAmplifyServerContext(this.cookies, (contextSpec) =>
      serverFetchAuthSession(contextSpec),
    );
  }

  private getAuthSession(): Promise<AuthSession> {
    return this.isPlatformBrowser
      ? fetchAuthSession()
      : this.getServerAuthSession();
  }

  private async getAccessHeaders(request: HttpRequest<unknown>) {
    let headers = request.headers
      .keys()
      .reduce((acc: Record<string, string>, key) => {
        acc[key] = request.headers.get(key) || '';
        return acc;
      }, {});
    try {
      const { tokens, credentials } = await this.getAuthSession();

      if (tokens) {
        headers['Authorization'] = tokens.accessToken.toString();
        return headers;
      }

      if (!credentials) {
        return headers;
      }

      const signer = new SignatureV4({
        service: 'appsync',
        region: environment.awsRegion,
        credentials: credentials,
        sha256: Sha256,
      });

      const url = new URL(request.url);

      const existingHeaders = request.headers.keys().reduce(
        (acc, key) => {
          acc[key] = request.headers.get(key) || '';
          return acc;
        },
        {} as { [key: string]: string },
      );

      const signedRequest = await signer.sign({
        method: request.method,
        protocol: url.protocol,
        headers: {
          ...existingHeaders,
          'Content-Type': existingHeaders['Content-Type'] || 'application/json',
          Host: url.host,
        },
        body: request.body ? JSON.stringify(request.body) : '',
        hostname: url.hostname,
        path: url.pathname,
      });

      return { ...headers, ...signedRequest.headers };
    } catch {
      return headers;
    }
  }
}

@github-actions github-actions bot added pending-maintainer-response Issue is pending a response from the Amplify team. and removed pending-community-response Issue is pending a response from the author or community. labels Feb 5, 2025
@martin-yumsto
Copy link
Author

martin-yumsto commented Feb 5, 2025

Hi @martin-yumsto looking at your logs

[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetId
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetId
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetCredentialsForIdentity
[SSR Fetch] x-amz-target: AWSCognitoIdentityService.GetCredentialsForIdentity

Did you actually observe duplicate network calls within one runWithAmplifyServerContext() call?

Hi @HuiSF,

I believe so, it's been over a month since I did original investigation and then was hoping to get some help on different discord servers. But nobody seemed to be able to help - so I'm now here.

Let me double check tomorrow to be sure I wasn't hallucinating, however I'm very firm that single invoke of runWithAmplifyServerContext caused a lot of strange behavior - I also noticed that same cookies are being manipulated repeatedly with same operations in the createKeyValueStorageFromCookieStorageAdapter, like same cookie being deleted/set multiple times etc.

Here are my wrappers which I originally didn't post:

export const cookieKeyValueStorage = (cookies: Record<string, string>) =>
  createKeyValueStorageFromCookieStorageAdapter({
    get: (name: string) => ({
      name: name,
      value: cookies?.[name],
    }),
    getAll: () =>
      Object.entries(cookies ?? {}).map(([name, value]) => ({ name, value })),
    set: (name: string, value: string) => {
      // console.info('response headers: ', this.response?.headers)
      // if(!this.response?.headers){
      //   return;
      // }

      // this.response.headers['Set-Cookie'] = `${name}=${value}`;
      console.info('set', name, value);
      // use framework cookie API to set a cookie
      //
      // you should implement this function if you need to update the
      // token cookies on the client side from the server side
    },
    delete(name: string) {
      console.info('delete', name);
      // use framework cookies API to delete a cookie
    },
  });

import {
  createUserPoolsTokenProvider,
  createAWSCredentialsAndIdentityIdProvider,
  runWithAmplifyServerContext as runAmplifyServerContext,
  type AmplifyServer,
} from 'aws-amplify/adapter-core';
import { amplifyConfig } from '../auth-config';
import { cookieKeyValueStorage } from './cookie-key-value-storage';

export const runWithAmplifyServerContext = <T>(
  cookies: Record<string, string>,
  operation: (contextSpec: AmplifyServer.ContextSpec) => Promise<T>,
): Promise<T> => {
  const cookieStorage = cookieKeyValueStorage(cookies);

  return runAmplifyServerContext(
    amplifyConfig,
    {
      Auth: {
        tokenProvider: createUserPoolsTokenProvider(
          amplifyConfig.Auth,
          cookieStorage,
        ),
        credentialsProvider: createAWSCredentialsAndIdentityIdProvider(
          amplifyConfig.Auth,
          cookieStorage,
        ),
      },
    },
    operation,
  );
};

EDIT:
taking a look at the code I use RxJs's shareReplay on reading that userInfo. So it should pretty much guarantee that it's called only once.

@HuiSF
Copy link
Member

HuiSF commented Feb 5, 2025

Thanks for the additional details @martin-yumsto re:

I also noticed that same cookies are being manipulated repeatedly with same operations in the createKeyValueStorageFromCookieStorageAdapter, like same cookie being deleted/set multiple times etc.

This was expected as when tokens are refreshed, the underlying token store interface that uses your cookieKeyValueStorage will delete each item before writing new value in. However, this behavior has been improved since version 6.12.2. Could you upgrade to the latest version and test again?

I'll follow up once you confirm whether there are duplicate network calls made while calling fetchAuthSession().

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 5, 2025
@martin-yumsto
Copy link
Author

martin-yumsto commented Feb 6, 2025

@HuiSF,

indeed, it's

      Promise.all([
        serverFetchAuthSession(contextSpec),
        serverFetchUserAttributes(contextSpec),
      ]),

that causes duplicate calls, each of them isolated causes following requests:

serverFetchAuthSession:

Outgoing Request
  Method: POST
  Headers: AWSCognitoIdentityService.GetId
Outgoing Request
  Method: POST
  Headers: AWSCognitoIdentityService.GetCredentialsForIdentity

serverFetchUserAttributes:

Outgoing Request
  Method: POST
  Headers: AWSCognitoIdentityService.GetId
Outgoing Request
  Method: POST
  Headers: AWSCognitoIdentityService.GetCredentialsForIdentity
Outgoing Request
  Method: POST
  Headers: AWSCognitoIdentityProviderService.GetUser

EDIT: That being said, calling them both simultaneously doesn't seem to cause the long processing. Calling them individually/isolated has the same times, arguably even worse, because I have seen 613ms for the first time.


will delete each item before writing new value in

That was expected from my side. What I witnessed was that cookie storage tried to perform same operation on the same cookie - e.g. delete on cookie named "A" multiple times. However I suppose it result of me calling both serverFetchAuthSession and serverFetchUserAttributes so not that surprising in the end.

I'm still waiting for results of bumping to 6.12.3, I didn't face expired session yet.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 6, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 7, 2025

Hi @martin-yumsto thank you very much for providing additional details.

Each runWithAmplifyServerContext creates an isolated context which resumes user session under the hood with fetching AWS credentials. So multiple call of runWithAmplifyServerContext will trigger multiple service calls as what you observed.

If you need to call both fetchAuthSession and fetchUserAttributes for one single incoming request, you should do this in one runWithAmplifyServerContext call. E.g.

const result = runWithAmplifyServerContext({
  amplifyConfig,
    {
    Auth: {
      tokenProvider: createUserPoolsTokenProvider(
        amplifyConfig.Auth,
        cookieStorage,
      ),
      credentialsProvider: createAWSCredentialsAndIdentityIdProvider(
        amplifyConfig.Auth,
        cookieStorage,
      ),
    },
  },
  async (contextSpect) => {
    const session = await fetchAuthSession(contextSpec); 
	const attribtues = await fetchUserAttributes(contextSpec);
	return { session, attributes };
  },
});

Note that fetchAuthSession() is awaited, as fetchAuthSession may refresh token when needed, and fetch AWS creds. The the subsequent call fetchUserAttributes() will use refreshed token and fetched creds to prevent extra service calls.

To make this work, you'd need to alter your key value store implementation, so your get method can provide the token refreshed by fetchAuthSession() to the subsequent fetchUserAttributes().

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 7, 2025
@martin-yumsto
Copy link
Author

martin-yumsto commented Feb 10, 2025

Hi @HuiSF,

for unknown reasons times just got much better, it's still not amazing as it's now around 100-120ms to resolve. Didn't change any code or packages at all. Not sure if it's related to networking.

I'll proceed and implement properly the delete and set cookie operations for the store. I suppose I need to implement some temporary one request caching on cookies as you mentioned, because result of the delete or set operations is new header on the response, but I guess get operation also expects to get those new values when again calling get.

One question regarding your point - how crucial is it to only call runWithAmplifyServerContext once within one SSR request and what are side effects of breaking that ?

My current implementation is that each API request during the SSR/CSR is signed using headers from fetchAuthSession and each of the signing happens in it's specific runWithAmplifyServerContext, so I run quite few of them simultaneously.

I'd have to rethink my architecture and move it somewhere much higher in the design. Possibly creating one ServerContext and utilizing dependency injection to get it to all the places I need, that being said, it'll still be challenging not to call simultaneously fetchAuthSession within that single context() because still multiple API requests happening)

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 10, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 11, 2025

Hi @martin-yumsto the latencies may depend on network conditions between your app server and Cognito service endpoints. The latency may be observable when the tokens need to be refreshed on the server side.

In general, if you are aiming to perform multiple Amplify server-side APIs call within one request->response cycle, you should wrap all the API calls within one runWithAmplifyServerContext call, this ensure refreshed tokens and AWS credentials are shared between the API calls to reduce the overhead.

runWithAmplifyServerContext creates isolated context based on the incoming requests to ensure Amplify server-side APIs work without cross-request state pollution.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 11, 2025
@martin-yumsto
Copy link
Author

Hi @HuiSF,

I need to spent bit more time on it, did all the refactoringt o wrap my angular ssr with runAmplifyServerContext to call it just once and ended up with breaking my prod Attempted to get the Amplify Server Context that may have been destroyed..

Moreover, I realized previously reported improvement in times was just due to accessing app using unauth session - with identity pool, when I switched to logged user with user pool today I'm back to 700ms.

I'll keep you posted when I got single runAmplifyServerContext implemented and some progress.

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 12, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 12, 2025

Thanks @martin-yumsto

Re:

Attempted to get the Amplify Server Context that may have been destroyed.

Also make sure you don't have @aws-amplify/core or any other @aws-amplify namespaced packages as directly dependencies of your project.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 12, 2025
@martin-yumsto
Copy link
Author

@HuiSF,

I got bit confused, after trying everything and reading documentation and github lib code from top to bottom, left to right I'm still facing the issue of having the Amplify Server Context destroyed and I started to question my plans of introducing single runAmplifyServerContext.

It seems not even NextJs code does that, from the documentation it looks every invoke of server API is wrapped in it's own freshly created runAmplifyServerContext including component level code and your util wrapper also creates new context every single time.

Can you please clarify if I understood you well that calling runWithAmplifyServerContext multiple times in different angular services is not proper implementation ?

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 13, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 14, 2025

Hey @martin-yumsto there is no different how the underlying generic runWithAmplifyServerContext implementation between frameworks. Can you sharing example code of your combined implementation? If there are multiple asynchronous function calls in the operation, they must be all awaited for, either inside the operation or by retuning an aggregated promise, i.e. using Promise.all.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 14, 2025
@martin-yumsto
Copy link
Author

martin-yumsto commented Feb 17, 2025

Hey @HuiSF, I made minimal reproduction repo to make it easier, this is post refactoring version that work locally, however, it doesn't work when I deploy it to lambda (still haven't figured why the amplify context gets destroyed before it's used).

This is my output, you can see userInfo still takes really long to resolve:

Page reload sent to client(s).
req:  { token: { value: Symbol(AmplifyServerContextToken) } }
this.amplifyContext:  { token: { value: Symbol(AmplifyServerContextToken) } }
userInfo: 600.159ms
userInfo:  {
  id: ',
  username: '',
  groups: Set(5) {
  }
}
response:  Response {
  status: 200,
  statusText: '',
  headers: Headers { 'Content-Type': 'text/html;charset=UTF-8' },
  body: ReadableStream { locked: false, state: 'readable', supportsBYOB: true },
  bodyUsed: false,
  ok: true,
  redirected: false,
  type: 'default',
  url: ''
}
finally runWithAmplifyServerContext

EDIT: I created also repro for my first version which uses multiple runWithAmplifyServerContext and works on both local dev as well as lambda deployment, here are logs from that one:

userInfo: 716.035ms
userInfo:  {
  id: '',
  username: '',
  groups: Set(5) {
  }
}

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 17, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 18, 2025

Hi @martin-yumsto thanks for providing the sample code repo.

Which callsite that the the error "Attempted to get the Amplify Server Context that may have been destroyed." was thrown from precisely? Is that this getServerUserInfo() function?

This is function it seemed to use an "injected" contextSpec object to call the Amplify server-side fetchAuthSession() and fetchUserAttribute() which could be problematic. You should not cache contextSpec anytime and use it to invoke API at a later time. A contextSpec gets created at the beginning of a runWithAmplifyServerContext call, and gets destroyed before runWithAmplifyServerContext returns. So you should always use runWithAmplifyServerContext to wrap calls of Amplify server-side APIs.

In addition, looking at this Promise.all(), since they are concurrent, two refresh token service calls could initiated so it adds up the latency. You can do the following:

runWithAmplifyServerContext({
  amplifyConfig,
  Auth: {},
  async operation(contextSpec): {
    // fetchAuthSession and fetchUserAttributes will be running in the same context since they
    // are receiving the same contextSpec, so fetchUserAttributes() will use new tokens
    // refreshed by fetchAuthSession()
    const session = await fetchAuthSession(contextSpec);
    const attrs = await fetchUserAttributes(contextSpect);

    return [session, attrs];
  }
})

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 18, 2025
@martin-yumsto
Copy link
Author

thank you for taking a look so swiftly @HuiSF !

Just to make sure I understand your point well - my multiple runWithAmplifyServerContext in this branch https://github.com/martin-yumsto/amplify-ssr-repro/tree/multiple-amplify-server-contexts version is perfectly fine and nothing to be changed about that.

I understand your point for the Promise.all and it's easy fix, however, I need to call amplify to get Authentication header for every single request if I understand it well - thus the interceptor. Please let me know should I implement dummy request, I thought without is enough to illustrate real world. I cannot really guarantee those run sequentially, actually it's very likely that multiple requests will happen concurrently.

What would you recommend for that use case ?

@github-actions github-actions bot added the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 18, 2025
@HuiSF
Copy link
Member

HuiSF commented Feb 19, 2025

Hi @martin-yumsto looking at the branch multiple-amplify-server-contexts you linked, the usage of the runWithAmplifyServerContext function in access.service.ts and auth.service.ts makes sense to me. I want to callout though, you need to make sure the source of the cookies you pass into the your runWithAmplifyServerContext wrapper function to not be mingled between incoming requests from different end users.

Regarding:

however, I need to call amplify to get Authentication header for every single request if I understand it well - thus the interceptor.

Similarly, since your intercepting your GraphQL API globally by intercepting the underlying outgoing http request, you need to make sure the cookies are correct to avoid cross requests state pollution.

@github-actions github-actions bot removed the pending-maintainer-response Issue is pending a response from the Amplify team. label Feb 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Angular Related to Angular 2+ Auth Related to Auth components/category question General question SSR Issues related to Server Side Rendering
Projects
None yet
Development

No branches or pull requests

3 participants