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

"No current user" error when calling graphql api endpoints in Next.js v14 project

Open ndaba1 opened this issue 1 year ago • 13 comments

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication, GraphQL API

Amplify Version

v6

Amplify Categories

auth, api

Backend

Amplify CLI

Environment information

  System:
    OS: Windows 11 10.0.22631
    CPU: (12) x64 12th Gen Intel(R) Core(TM) i5-12400F
    Memory: 9.12 GB / 31.84 GB
  Binaries:
    Node: 20.10.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.21 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 10.2.3 - C:\Program Files\nodejs\npm.CMD
    pnpm: 8.13.1 - ~\AppData\Roaming\npm\pnpm.CMD
  Browsers:
    Edge: Chromium (121.0.2277.112)
    Internet Explorer: 11.0.22621.1
  npmPackages:
    @changesets/changelog-github: ^0.5.0 => 0.5.0 
    @changesets/cli: ^2.27.1 => 2.27.1 
    @commitlint/cli: ^18.4.3 => 18.4.3 
    @commitlint/config-conventional: ^18.4.3 => 18.4.3
    @knocklabs/node: ^0.5.0 => 0.5.0
    @repo/aws-types: workspace:^ => 0.1.0
    @repo/prettier-config: workspace:^0.1.0 => 0.1.0
    @repo/types: workspace:^ => 0.1.0
    @segment/analytics-next: ^1.62.0 => 1.62.0
    @segment/analytics-node: ^1.1.4 => 1.1.4
    @turbo/gen: ^1.11.2 => 1.11.2
    @types/node: ^20.10.5 => 20.10.5
    chalk: ^5.3.0 => 5.3.0
    husky: ^8.0.3 => 8.0.3
    lint-staged: ^15.2.0 => 15.2.0
    prettier: ^3.1.1 => 3.1.1
    prompts: ^2.4.2 => 2.4.2
    turbo: ^1.11.2 => 1.11.2
    typescript: ^5.3.3 => 5.3.3
  npmGlobalPackages:
    @aws-amplify/cli: 12.0.3
    @thejumba/auditable-transformer: 1.3.3
    eas-cli: 6.1.0
    pnpm: 8.13.1
    vercel: 33.0.2
    yarn: 1.22.21

Describe the bug

Recently, I've been getting this consistent error No current user on our Next.js v14 application. It appears ever so randomly and always when trying to invoke the amplify GraphQL API from a server component. I've tried multiple solutions such as checking that Amplify.configure or Auth.configure is only called in one place and also checking for duplicate amplify version as described here.

Since the app is running on Amplify v6, there's a utility constant ssrClient that is used to make all graphql api calls on server components as shown in the snippets below.

Expected behavior

The GraphQL API call should be made successfully without any errors being thrown

Reproduction steps

  1. Create a Next.js v14 application
  2. Setup amplify version 6
  3. Configure an amplify graphql api endpoint
  4. Setup SSR functionality for amplify
  5. Call said graphql api endpoint from a server component/server action using utilities provided from aws-amplify/adapter-nextjs package

Code Snippet

Amplify is configured in a file which exports a Providers component that wraps everything else in the app in the root layout.tsx

// app/providers.tsx
"use client"; 

import { Amplify } from "aws-amplify";
import { parseAmplifyConfig } from "aws-amplify/utils";
import amplifyConfig from "@repo/aws-exports";

Amplify.configure(parseAmplifyConfig(amplifyConfig), { ssr: true });

// ... rest of code

We then have some utilities exported for amplify ssr interactions:

// @/lib/amplify-ssr.tsx
import { cookies } from "next/headers";
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import { generateServerClientUsingCookies } from "@aws-amplify/adapter-nextjs/api";
import { parseAmplifyConfig } from "aws-amplify/utils";

import config from "@repo/aws-exports";

export const ssrClient = generateServerClientUsingCookies({
  cookies,
  config: parseAmplifyConfig(config),
  authMode: "userPool",
});

export const { runWithAmplifyServerContext } = createServerRunner({
  config: parseAmplifyConfig(config),
});

And then we use the ssrClient

const res = await ssrClient.graphql({
    query: "someQuery",
    variables: {
       // query variables
    },
  })

Log output

// Put your logs below this line


aws-exports.js

const awsmobile = {
  "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
  "aws_appsync_graphqlEndpoint": "https://XXXXXXXXXXXXXX.appsync-api.us-east-1.amazonaws.com/graphql",
  "aws_appsync_region": "us-east-1",
  "aws_cognito_identity_pool_id": "us-east-1:XXXXXXXXXXXXXX",
  "aws_cognito_mfa_configuration": "ON",
  "aws_cognito_mfa_types": [
    "SMS"
  ],
  "aws_cognito_password_protection_settings": {
    "passwordPolicyCharacters": [],
    "passwordPolicyMinLength": 8
  },
  "aws_cognito_region": "us-east-1",
  "aws_cognito_signup_attributes": [
    "EMAIL",
    "NAME",
    "PHONE_NUMBER"
  ],
  "aws_cognito_social_providers": [],
  "aws_cognito_username_attributes": [
    "PHONE_NUMBER"
  ],
  "aws_cognito_verification_mechanisms": [
    "PHONE_NUMBER"
  ],
  "aws_project_region": "us-east-1",
  "aws_user_files_s3_bucket": "XXXXXXXXXXXXXX",
  "aws_user_files_s3_bucket_region": "us-east-1",
  "aws_user_pools_id": "XXXXXXXXXXXXXX",
  "aws_user_pools_web_client_id": "XXXXXXXXXXXXXX",
  "oauth": {}
}

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

This is what keeps getting captured by sentry

Screenshot 2024-02-11 023709 Screenshot 2024-02-11 024458

ndaba1 avatar Feb 10 '24 23:02 ndaba1

Have you tried doing Amplify.configure(awsConfig) on both the client components and server components or are you already doing that?

JackWSargent avatar Feb 15 '24 13:02 JackWSargent

According to the latest v6 docs, Screenshot 2024-02-16 003112

ndaba1 avatar Feb 15 '24 21:02 ndaba1

Hi @ndaba1 👋 I was able to reproduce the "No current user" error when using the cookie based client on the server side. However, I realized that I had old credentials in Local Storage. I cleared them and then started getting Unauthorized errors.

Screenshot 2024-02-16 at 12 52 42 PM

I simply logged in again and was able to retrieve data with the userPool auth mode again. Screenshot 2024-02-16 at 12 53 08 PM

So, it seems this is reproducible if you have expired tokens (including refresh), preventing Amplify from refreshing the cognito access token and resulting in the "No current user" error.

Can you confirm that your credentials are not expired and that you are able to reproduce this issue with a recently logged in user?

This is the schema I had while reproducing (using Amplify Gen 2 to build the backend)

const schema = a.schema({
  CommunityPost: a
    .model({
      title: a.string().required(),
      poll: a.hasOne("CommunityPoll"),
    })
    .authorization([a.allow.private()]),

  CommunityPoll: a
    .model({
      question: a.string().required(),
      answers: a.hasMany("CommunityPollAnswer").arrayRequired().valueRequired(),
    })
    .authorization([a.allow.private()]),

  CommunityPollAnswer: a
    .model({
      answer: a.string().required(),
      votes: a.hasMany("CommunityPollVote").arrayRequired().valueRequired(),
    })
    .authorization([a.allow.private()]),

  CommunityPollVote: a
    .model({ name: a.string() })
    .authorization([a.allow.private()]),
});

chrisbonifacio avatar Feb 16 '24 17:02 chrisbonifacio

@chrisbonifacio thank you for taking the time to look into this. Yes, the issue occurs even for recently logged in users. To your point, I do think its related to the tokens not being refreshed correctly for whatever reason. Some users actually get randomly redirected to the sign in page when trying to access an SSR page(with RSC) but upon inputting credentials, the error "There is already a signed in user" is returned. And if you refresh the page, then you seem to be logged in again.

ndaba1 avatar Feb 16 '24 18:02 ndaba1

Since I am able to reproduce the issue, I marked this as a bug for the team to investigate further. Seems more auth related than GraphQL related.

To be more confident that we can reproduce it under the same conditions as your application, can you share more details about your auth resource such as the expiration for your tokens?

If you are building a Gen 1 Amplify app, using the Amplify CLI, you can also run the following command and provide us the project identifier in the output. This will let us recreate your exact auth resource.

amplify diagnose --send-report

chrisbonifacio avatar Feb 23 '24 15:02 chrisbonifacio

Hey @chrisbonifacio The project identifier is 1ce1eff4cf18e0517708247971fa723f

ndaba1 avatar Feb 28 '24 11:02 ndaba1

Hello, is there any feedback ? The issue is still persistent

ndaba1 avatar Jun 06 '24 22:06 ndaba1

Hi @ndaba1 By reading "It appears ever so randomly and always when trying to invoke the amplify GraphQL API from a server component" I suspect that the issue was caused by a failure of fetchAuthSession() call internally triggered by .graphql(). This failure is specifically Cognito Rate Limit error. Every fetchAuthSession call in an isolated server context makes three different service calls. This is known and adjustable limitation from Cognito (See the callout in this section of documentation).

If you are using ssrClient to make multiple GraphQL API calls within one Server Component on one incoming request, there is a way to optimize it.

In the meantime, we are actively exploring a better solution to make fetchAuthSession more efficient. In addition, we have improved error throwing from the GraphQL APIs, the error object now contains a underlyingError property. You may log this property to record the underlying error that caused No current user or No federated jwt errors, so you can confirm and determine whether you need to adjust Cognito quotas accordingly.

HuiSF avatar Jul 08 '24 19:07 HuiSF

Hey @HuiSF , Thank you for your well detailed response. Let me try it then get back to you

ndaba1 avatar Jul 11 '24 11:07 ndaba1

@HuiSF we tried your suggested solution and unfortunately, it doesn't seem to work

import type { GraphQLOptionsV6 } from "@aws-amplify/api-graphql";
import { cookies } from "next/headers";
import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import { generateClient } from "aws-amplify/api/server";
import { parseAmplifyConfig } from "aws-amplify/utils";

import awsExports from "@repo/aws-exports";

const config = parseAmplifyConfig(awsExports);

const client = generateClient({ config });

export const { runWithAmplifyServerContext } = createServerRunner({
  config,
});

export const ssrClient = {
  graphql: async <TQuery = unknown>(options: GraphQLOptionsV6<TQuery>) => {
    const res = await runWithAmplifyServerContext({
      nextServerContext: { cookies },
      operation: async (contextSpec) => {
        return client.graphql<TQuery>(contextSpec, {
          query: options.query,
          variables: options.variables,
          authMode: options.authMode,
        });
      },
    });

    return res;
  },
};

This is how we refactored the ssrClient. Are we missing anything here ?

ndaba1 avatar Jul 31 '24 09:07 ndaba1

Hi @ndaba1 thanks for following up.

Looking at your implementation, it's actually no difference from calling runWithAmplifyServerContext() multiple times in order to client.graphql() in a context.

The above linked comment, suggested that to make multiple client.graphql() within a single call of runWithAmplifyServerContext(). Where the calls of client.graphql() require creating the context only once.

HuiSF avatar Jul 31 '24 17:07 HuiSF

Hey @HuiSF, thank you for getting back to me so quickly.

I think I see what you mean: The optimization can only be made for multiple GraphQL API calls made within the same incoming request for a server component but a new server context would still get created for any additional requests, right ? (Please correct me if I'm wrong)

And if that's the case, wouldn't still we risk running into the issue if there are a high number of concurrent requests for a server rendered page (from different users) ?

Also, would you kindly offer some more insights on which Cognito Service Quotas we'd try adjusting to help with this issue ? The callback in the docs doesn't specify which limits one would need to adjust. You mentioned

Every fetchAuthSession call in an isolated server context makes three different service calls.

but I'm not sure which service calls this would be

ndaba1 avatar Jul 31 '24 21:07 ndaba1

That's correct @ndaba1 the optimization was only for multiple GraphQL calls within one incoming request.

fetchAuthSession() on the server side now requires InitiateAuth (if client sent tokens are expired), GetId and GetCredentialsForIdentity these 3 service calls to assume a user session on the server side.

For your use case, I'd recommend first to monito the underlyingError of No current user or No federated jwt errors to ensure they are caused by the rate limit.

HuiSF avatar Jul 31 '24 21:07 HuiSF