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

Next.js and AWS Amplify "No current user" at getServerSideProps - REST, not GraphQL

Open phstc opened this issue 3 years ago • 5 comments

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

Authentication, REST API

Amplify Categories

auth, api

Environment information

  System:
    OS: macOS 12.5
    CPU: (10) arm64 Apple M1 Max
    Memory: 7.33 GB / 64.00 GB
    Shell: 5.8.1 - /bin/zsh
  Binaries:
    Node: 16.16.0 - ~/.volta/tools/image/node/16.16.0/bin/node
    Yarn: 1.22.19 - ~/.volta/bin/yarn
    npm: 8.11.0 - ~/.volta/tools/image/node/16.16.0/bin/npm
  Browsers:
    Chrome: 104.0.5112.101
    Safari: 15.6
  npmPackages:
    @ampproject/toolbox-optimizer:  undefined ()
    @aws-amplify/ui-react: ^3.4.1 => 3.4.1
    @aws-amplify/ui-react-internal:  undefined ()
    @aws-amplify/ui-react-legacy:  undefined ()
    @babel/core:  undefined ()
    @babel/runtime:  7.15.4
    @edge-runtime/primitives:  1.1.0-beta.26
    @hapi/accept:  undefined ()
    @headlessui/react: ^1.6.6 => 1.6.6
    @heroicons/react: ^1.0.6 => 1.0.6
    @napi-rs/triples:  undefined ()
    @next/react-dev-overlay:  undefined ()
    @segment/ajv-human-errors:  undefined ()
    @tailwindcss/forms: ^0.5.2 => 0.5.2
    @vercel/nft:  undefined ()
    acorn:  undefined ()
    amphtml-validator:  undefined ()
    arg:  undefined ()
    assert:  undefined ()
    async-retry:  undefined ()
    async-sema:  undefined ()
    autoprefixer: ^10.4.8 => 10.4.8
    aws-amplify: ^4.3.33 => 4.3.33
    babel-packages:  undefined ()
    babel-plugin-inline-react-svg: ^2.0.1 => 2.0.1
    browserify-zlib:  undefined ()
    browserslist:  undefined ()
    buffer:  undefined ()
    bytes:  undefined ()
    chalk:  undefined ()
    ci-info:  undefined ()
    cli-select:  undefined ()
    comment-json:  undefined ()
    compression:  undefined ()
    conf:  undefined ()
    constants-browserify:  undefined ()
    content-disposition:  undefined ()
    content-type:  undefined ()
    cookie:  undefined ()
    cross-spawn:  undefined ()
    crypto-browserify:  undefined ()
    cssnano-simple:  undefined ()
    debug:  undefined ()
    devalue:  undefined ()
    domain-browser:  undefined ()
    edge-runtime:  undefined ()
    eslint: 8.22.0 => 8.22.0
    eslint-config-next: 12.2.5 => 12.2.5
    events:  undefined ()
    find-cache-dir:  undefined ()
    find-up:  undefined ()
    fresh:  undefined ()
    get-orientation:  undefined ()
    glob:  undefined ()
    gzip-size:  undefined ()
    http-proxy:  undefined ()
    https-browserify:  undefined ()
    icss-utils:  undefined ()
    ignore-loader:  undefined ()
    image-size:  undefined ()
    is-animated:  undefined ()
    is-docker:  undefined ()
    is-wsl:  undefined ()
    jest-worker:  undefined ()
    json5:  undefined ()
    jsonwebtoken:  undefined ()
    loader-utils:  undefined ()
    lodash.curry:  undefined ()
    lru-cache:  undefined ()
    micromatch:  undefined ()
    mini-css-extract-plugin:  undefined ()
    nanoid:  undefined ()
    native-url:  undefined ()
    neo-async:  undefined ()
    next: 12.2.5 => 12.2.5
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    postcss: ^8.4.16 => 8.4.16 (8.4.14)
    postcss-flexbugs-fixes:  undefined ()
    postcss-modules-extract-imports:  undefined ()
    postcss-modules-local-by-default:  undefined ()
    postcss-modules-scope:  undefined ()
    postcss-modules-values:  undefined ()
    postcss-preset-env:  undefined ()
    postcss-safe-parser:  undefined ()
    postcss-scss:  undefined ()
    postcss-value-parser:  undefined ()
    process:  undefined ()
    punycode:  undefined ()
    querystring-es3:  undefined ()
    raw-body:  undefined ()
    react: 18.2.0 => 18.2.0 (18.0.0)
    react-dom: 18.2.0 => 18.2.0
    react-is:  17.0.2
    react-refresh:  0.12.0
    react-server-dom-webpack:  undefined ()
    regenerator-runtime:  0.13.4
    sass-loader:  undefined ()
    schema-utils:  undefined ()
    semver:  undefined ()
    send:  undefined ()
    setimmediate:  undefined ()
    source-map:  undefined ()
    stream-browserify:  undefined ()
    stream-http:  undefined ()
    string-hash:  undefined ()
    string_decoder:  undefined ()
    strip-ansi:  undefined ()
    tailwindcss: ^3.1.8 => 3.1.8
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    tty-browserify:  undefined ()
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    util:  undefined ()
    vm-browserify:  undefined ()
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
  npmGlobalPackages:
    corepack: 0.10.0
    npm: 8.11.0

Describe the bug

I'm trying to use getServerSideProps + SSR.API.get (REST, not GraphQL) and I'm getting "No current user". If I move the code to useEffect, it works fine.

export async function getServerSideProps({req}) {
  const { Auth, API } = withSSRContext({req});
 
  try {
    const apiName = "API";
    const path = `/user/self`;

    // throws No current user
    user = await API.get(apiName, path);

    return { props: { user } }    
  } catch (e) {
    console.error(e);
    throw e
  }
}

The API Authorization is configured (it works with useEffect), and I also have SSR enabled ssr: true.

  // configuration
  API: {
    endpoints: [
      {
       /* ... */
        custom_header: async () => {
          return {
            Authorization: `Bearer ${(await Auth.currentSession())
              .getIdToken()
              .getJwtToken()}`,
          };
        },
      },
    ],
  },
  ssr: true,

I tried to re-set the Authorization, although SSR.Auth.currentSession() works fine. I could get a token, but I'm still getting "No current user".

export const getServerSideProps = async ({ req }) => {
  const SSR = withSSRContext({ req });

    const options = {
      headers: {
        Authorization: `Bearer ${(await SSR.Auth.currentSession())
          .getIdToken()
          .getJwtToken()}`,
      },
    };

    const user = await SSR.API.get("API", "users/self", options); // throws No current user

Could it be my version of next: 12.2.5?

I found a similar issue on StackOverflow but no answers in there.

Expected behavior

Be able to make authorized requests from getServerSideProps.

Reproduction steps

  1. Use REST API instead of GraphQL
  2. Try to make an authorized call from getServerSideProps

Code Snippet

// Put your code below this line.

Log output

// Put your logs below this line


aws-exports.js

No response

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

phstc avatar Sep 04 '22 14:09 phstc

Could it be my version of next: 12.2.5?

Quick update: I tried with nextjs 11.1.4 and got the same error.

phstc avatar Sep 08 '22 02:09 phstc

Ideally this should work with REST API - we had tested this just recently. Are you changing your cookiestorage strategy at all from the Auth category?

abdallahshaban557 avatar Sep 08 '22 17:09 abdallahshaban557

@abdallahshaban557 no changes in the cookiestorage.

This is my entire Amplify config:


const amplifyConfig = {
  ssr: true,

  Auth: {
    identityPoolId: process.env.NEXT_PUBLIC_IDENTITY_POOL_ID,
    region: process.env.NEXT_PUBLIC_REGION,
    userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID,
    userPoolWebClientId: process.env.NEXT_PUBLIC_USER_POOL_WEB_CLIENT_ID,
  },

  API: {
    endpoints: [
      {
        name: "API",
        endpoint: process.env.NEXT_PUBLIC_ENDPOINT,
        custom_header: async () => {
          return {
            Authorization: `Bearer ${(await Auth.currentSession())
              .getIdToken()
              .getJwtToken()}`,
          };
        },
      },
    ],
  },

  Storage: {
    AWSS3: {
      bucket: process.env.NEXT_PUBLIC_BUCKET,
      region: process.env.NEXT_PUBLIC_REGION,
    },
  },
};

phstc avatar Sep 08 '22 17:09 phstc

I'm currently using fetch, and it is working fine.

    const options = {
      headers: {
        Authorization: `Bearer ${(await SSR.Auth.currentSession())
          .getIdToken()
          .getJwtToken()}`,
      },
    };

    // const user = await SSR.API.get("API", "users/self"); // throws No current user
    // const user = await SSR.API.get("API", "users/self", options); // throws No current user
   const response = await fetch("URL...users/self", { ...options, method: "GET" }); // works

It seems to be something specific with API.

phstc avatar Sep 13 '22 21:09 phstc

Interesting - thank you for the update! We will check this and update you!

abdallahshaban557 avatar Sep 13 '22 21:09 abdallahshaban557

Hi :wave: @phstc!

I have reproduced this on the server with a manual configuration of some old cognito and API resources, but the problem does not happen if new resources are created from the Amplify CLI.

It also does not work for me by calling it in useEffect but I was able to obtain the resources from getServerSideProps with fetch.

I have a few follow up questions so that we can figure out the root cause:

  1. Which type of Auth do you have set up for this project? API Key or Cognito?
  2. If it is API key, what is the expiration on the API key? Is it expired?

tannerabread avatar Sep 27 '22 01:09 tannerabread

@tannerabread thanks for looking into it.

  1. Cognito.
  2. ☝️

Sample CDK + Lambda.

    const restApiAuthorizer = new apiGateway.CfnAuthorizer(
      this,
      "APIAuthorizer",
      {
        restApiId: restApi.restApiId,
        name: "APIAuthorizer",
        type: AuthorizationType.COGNITO,
        identitySource: "method.request.header.Authorization",
        providerArns: [props.userPool.userPoolArn],
      }
    );


    guideWithIdResource.addMethod("GET", new LambdaIntegration(readFn), {
      authorizationType: AuthorizationType.COGNITO,
      authorizer: {
        authorizerId: restApiAuthorizer.ref,
      },
    });

phstc avatar Sep 27 '22 02:09 phstc

@phstc I was finally able to get passed the "No current user" error.

It seems like the custom header in your amplify configure might be causing the issue. I did quite a bit of digging and logging and noticed that the error was actually being thrown from AuthClass and the more specific error was "Failed to get user from user pool"

I was able to get rid of the error by removing the entire custom_header section from Amplify.configure.

Personally, I am now getting 401/403 errors but I believe that is a different issue from how I set up this API.

Please let me know if this solves the issue for you as well

EDIT: Another note, it works successfully when I manually configure the project with the resources that I made through the CLI, so my 401/403 errors were definitely from some bad resource setup on my end.

My final configuration was as follows:

Amplify.configure({
  Auth: {
    region: "XX-XXXX-X",
    userPoolId: "XX-XXXX-X_abcd1234",
    userPoolWebClientId: "a1b2c3d4e5f6g7h8i9j0k1l2m3",
    identityPoolId: "XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab",
  },
  API: {
    endpoints: [
      {
        name: "apiName",
        endpoint: "https://12a3bcdefg.execute-api.us-east-1.amazonaws.com/dev/",
        region: "XX-XXXX-X"
      },
    ],
  },
  ssr: true,
});

This in turn allowed me to use API.get as expected in getServerSideProps:

export async function getServerSideProps({ req }) {
  const apiName = "apiName";
  const path = "items";

  const SSR = withSSRContext({ req });
  const response = await SSR.API.get(apiName, path);

  return {
    props: {
      response,
    },
  };
}

tannerabread avatar Sep 28 '22 01:09 tannerabread

I played around with it some more this morning and was able to get the other resources working as well.

For my cognito user pool and REST API set up outside of this project, I had to set COGNITO_USER_POOLS as the authorization in the API Gateway console for both /items and /{proxy+}: image

This allowed me to use the configuration from my last comment and getServerSideProps like this:

export async function getServerSideProps({ req }) {
  const SSR = withSSRContext({ req });

  const apiName = "apiName";
  const path = "items";
  const myInit = {
    headers: {
      Authorization: `Bearer ${(await SSR.Auth.currentSession())
        .getIdToken()
        .getJwtToken()}`,
    },
  };

  const response = await SSR.API.get(apiName, path, myInit);

  return {
    props: {
      response,
    },
  };
}

The SSR docs here explain that

withSSRContext utility creates an instance of Amplify scoped to a single request (req) using those cookie credentials.

I think the custom_header config authorization was improperly hydrating the scoped Amplify instance with a promise instead of the results of Auth.currentSession()

tannerabread avatar Sep 28 '22 13:09 tannerabread

Hi :wave: @phstc Are you still having problems with this issue? Did my findings help you out?

tannerabread avatar Oct 06 '22 13:10 tannerabread

Hi 👋 Closing this as resolved. If you are still experiencing this issue and in need of assistance, please feel free to comment and provide us with additional information so we can re-open this issue and be better able to assist you.

Thank you!

tannerabread avatar Oct 13 '22 15:10 tannerabread

hi @tannerabread

I'm using COGNITO_USER_POOLS.

image

I couldn't resume the project yet. But I will let you know if I still face problems.

Thank you

phstc avatar Oct 13 '22 15:10 phstc

@phstc Sorry to be hasty on closing this issue. I'll be here if you need anything else. Thanks!

tannerabread avatar Oct 13 '22 15:10 tannerabread

@tannerabread just tested it again with another app but with the same setup, and I'm still getting the same error.

image

export const getServerSideProps = async ({ req }) => {
  const SSR = withSSRContext({ req });

  try {
    const myInit = {
      headers: {
        Authorization: `Bearer ${(await SSR.Auth.currentSession())
          .getIdToken()
          .getJwtToken()}`,
      },
    };

    // works fine!
    // const r = await fetch(`${process.env.NEXT_PUBLIC_ENDPOINT}/orders`, myInit);
    // console.log(await r.json());

    // this outputs
    console.log("before");
    // throws an error "No current user"
    const orders = await SSR.API.get("API", "orders", myInit);
    // this does not output, it fails on the line above
    console.log("after", orders);

    return {
      props: { orders },
    };
  } catch (e) {
    console.error(e);
    return {
      props: {},
    };
  }
};

phstc avatar Jan 05 '23 00:01 phstc

Hi @phstc would you be able to provide a sample repo of this that I could test from?

tannerabread avatar Jan 18 '23 17:01 tannerabread

@phstc I was finally able to get passed the "No current user" error.

It seems like the custom header in your amplify configure might be causing the issue. I did quite a bit of digging and logging and noticed that the error was actually being thrown from AuthClass and the more specific error was "Failed to get user from user pool"

I was able to get rid of the error by removing the entire custom_header section from Amplify.configure.

Personally, I am now getting 401/403 errors but I believe that is a different issue from how I set up this API.

Please let me know if this solves the issue for you as well

EDIT: Another note, it works successfully when I manually configure the project with the resources that I made through the CLI, so my 401/403 errors were definitely from some bad resource setup on my end.

My final configuration was as follows:

Amplify.configure({
  Auth: {
    region: "XX-XXXX-X",
    userPoolId: "XX-XXXX-X_abcd1234",
    userPoolWebClientId: "a1b2c3d4e5f6g7h8i9j0k1l2m3",
    identityPoolId: "XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab",
  },
  API: {
    endpoints: [
      {
        name: "apiName",
        endpoint: "https://12a3bcdefg.execute-api.us-east-1.amazonaws.com/dev/",
        region: "XX-XXXX-X"
      },
    ],
  },
  ssr: true,
});

This in turn allowed me to use API.get as expected in getServerSideProps:

export async function getServerSideProps({ req }) {
  const apiName = "apiName";
  const path = "items";

  const SSR = withSSRContext({ req });
  const response = await SSR.API.get(apiName, path);

  return {
    props: {
      response,
    },
  };
}

Removing the custom_header worked for mee <3

didemkkaslan avatar Jan 24 '23 16:01 didemkkaslan

Hi, same problem here with same stack (nextjs 13.1, REST, SSR, amplify + cognito)

  • error using custom_header (fail on every Auth.current*())
  • 401 with this amplify configuration
export const configuration = {
    Auth: {
        region: process.env.NEXT_PUBLIC_REGION,
        userPoolId: process.env.NEXT_PUBLIC_USERPOOL_ID,
        userPoolWebClientId: process.env.NEXT_PUBLIC_USERPOOL_WEB_CLIENT_ID,
        signUpVerificationMethod: 'code',
    },
    API: {
        endpoints: [
            {
                name: "dev",
                endpoint: "https://xxxxxxx.execute-api.xx-xxxxxxx-0.amazonaws.com/dev",
                region: "xx-xxxxxxx-0"
            },
        ],
    },
};

The only way for working it's to set Authorization Bearer header in getServerSideProps grabbing jwt from context by Auth.currentSession(). But this involves apply this code every request. API-Gateway every endpoint is setted with Authorization COGNITO_USER_POOLS.

Is it possible in my configuration i haven't Identity Pool Id? No federation identity setted here. Is it neccessary?

pix303 avatar Feb 07 '23 21:02 pix303

@didemkkaslan if I remove custom_header then nothing works, I get 401 from APIGW.

@tannerabread would you be open to pair up on this?. It's going to be much quicker for me to share my screen than set up a new project. Please, let me know if that works for you.

phstc avatar Feb 19 '23 16:02 phstc

Another behavior that's very strange. If I click on a link, from another website, for example, I click on localhost:3000/something from example.com, it fails for the first time, getServerSideProps can't authorize the request, then, if I manually enter that URL, it works just fine (with my custom header, I still have the initial issue).

phstc avatar Feb 19 '23 16:02 phstc

I recently upgraded my Next11 project to use WEB_COMPUTE and I ran into problems with the SSR context working locally but not when deployed. One major problem was receiving 'No current user'. The issue finally resolved when I changed the SSRContext variable from a const to a let.

Changing

  const SSR = withSSRContext({ req });

to

  let SSR = withSSRContext({ req });

  //Calling Amplify classes as just examples.
  SSR.Auth.currentAuthenticatedUser()
  SSR.API......
  SSR.Datastore.....

This change resolved an issue with Auth and other SSR Amplify classes when migrating from Next11 to Next12|13 and changing the Amplify platform to WEB_COMPUTE.

Here's my current working dependencies

"dependencies": {    
  "@aws-amplify/ui-react": "4.3.7",
  "@aws-amplify/core": "5.0.11",
  "@aws-amplify/geo":"2.0.11",
  "aws-amplify": "5.0.11",
  "next": "^12.3.4",
  "react": "17.0.2",
  "react-dom": "17.0.2",
}

blackmouth avatar Feb 19 '23 16:02 blackmouth

@blackmouth I get TypeError: SSR.Auth.function is not a function my dependencies:

    "@aws-amplify/auth": "^5.1.2",
    "@aws-amplify/ui-react": "^4.3.1",
    "aws-amplify": "^5.0.11",
    "next": "13.1.1",

phstc avatar Feb 19 '23 18:02 phstc

Sorry for the confusion, I updated my comment.

blackmouth avatar Feb 19 '23 18:02 blackmouth

Is this related to some cookies setting which must be explicitly placed in aws-exports.json? Hours and hours of search only vaguely makes reference.

weisisheng avatar Feb 28 '23 03:02 weisisheng

Hi @phstc following up here,

I also haven't been able to reproduce your issue and I get the expected result when configuring similar to what you have. However one thing l'd like to confirm - on your Authorizer for API Gateway, are you specifying the Token Source?

APIGWY Authorizer

@weisisheng for your question, you shouldn't have to modify cookie config from aws-exports

nadetastic avatar Mar 02 '23 01:03 nadetastic

Hi @nadetastic

Yes, I am.

image

BTW this works fine on the client side. The issue is when running it through getServerSideProps. Also, this works on getServerSideProps when I use fetch (example here).

phstc avatar Mar 02 '23 01:03 phstc

Thanks for the confirmation @phstc.

The only thing I can think of now is an issue specific to CDK - I tested by manually adding via the console and also using the Amplify CLI I am able to get things working.

Could you share a simplified sample of your CDK stack? In addition to trying reproduce this with your CDK stack, I'm also hoping you can confirm if all your resources are created with the CDK - Cognito, API, Lambda etc - and are just hooking the resources to you Amplify app.

nadetastic avatar Mar 07 '23 20:03 nadetastic

@tannerabread @nadetastic I was able to create a repo cognito-amplify-cdk-10290 with the code to reproduce the problem.

I added deploy instructions in the README to configure the sample code. Please, let me know if that works for you.

Once you run it locally npm run dev and then open http://localhost:3000, there will be two links:

image

One example is working using fetch discussed previously, and the other one is the broken one using SSR.API.

phstc avatar Mar 29 '23 22:03 phstc

@phstc thank you very much for sharing the sample app! I will follow up soon

nadetastic avatar Mar 30 '23 00:03 nadetastic

@phstc I was able to get this working as seen on my fork of your repo - https://github.com/nadetastic/cognito-amplify-cdk-10290/commit/0efcc007dfc35fccc408cff9173eb93caca12b37

In short, updated from using the custom header within amplifyConfig.API to setting it within the headers right before calling SSR.API.get(). This was pointed out before but you mentioned that you were still having issues after removing the custom header, but I'd like to note that I created new resources using the CDK stack you provided, so it doesn't seem to be an issue with the CDK definition (backend looks unchanged in my repo, but I did modify it as you stated in the README just didn't push the changes).

Are you able to try with the changes I have added? - https://github.com/nadetastic/cognito-amplify-cdk-10290

nadetastic avatar Mar 31 '23 05:03 nadetastic

Hi @nadetastic thank you so much for looking into this. I really appreciate it ❤️

Are you able to try with the changes I have added?

Yes, with the changes it works, but 👇

you mentioned that you were still having issues

Sorry for not clarifying that. If I remove the custom header, then I need to add myInit to all requests, SSR or not. I added an example here.

One of the reasons for custom_header was to enforce and simplify (not having to explicitly add myInit) other requests, SSR or not.

But apparently, that option does not work with SSR. Is that expected or a bug?


There's still another issue with both approaches, custom-header or not.

If you click here http://localhost:3000/works-ssr (from the GitHub comment) it fails with No Current User, but if you copy http://localhost:3000/works-ssr and paste that into your browser, it works. If you click from an external link (Gmail, GitHub, Slack, etc.), it fails, but if you enter the URL directly or if it's a link within localhost (or whatever app domain), it works.

phstc avatar Mar 31 '23 16:03 phstc