aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.43k stars 2.13k forks source link

When using a REST API authenticated with a Bearer token in Next.js SSR, I always get "ERROR: No current user" #8812

Closed DanProudfoot closed 3 years ago

DanProudfoot commented 3 years ago

Before opening, please confirm:

JavaScript Framework

React, Next.js

Amplify APIs

Authentication, REST API

Amplify Categories

auth, api

Environment information

``` System: OS: macOS 11.5.1 CPU: (8) x64 Intel(R) Core(TM) i5-1038NG7 CPU @ 2.00GHz Memory: 1.28 GB / 16.00 GB Shell: 5.8 - /bin/zsh Binaries: Node: 16.6.2 - ~/.nvm/versions/node/v16.6.2/bin/node npm: 6.14.14 - ~/.nvm/versions/node/v16.6.2/bin/npm Browsers: Chrome: 92.0.4515.159 Edge: 92.0.902.67 Safari: 14.1.2 npmPackages: @ampproject/toolbox-optimizer: undefined () @aws-amplify/ui-react: ^1.2.11 => 1.2.11 @babel/core: undefined () @reduxjs/toolkit: ^1.6.1 => 1.6.1 @reduxjs/toolkit-query: 1.0.0 @reduxjs/toolkit-query-react: 1.0.0 amphtml-validator: undefined () arg: undefined () async-retry: undefined () async-sema: undefined () aws-amplify: ^4.2.5 => 4.2.5 bfj: undefined () bootstrap: ^5.1.0 => 5.1.0 cacache: undefined () chart.js: ^3.5.1 => 3.5.1 chart.js-auto: undefined () chart.js-helpers: undefined () chartjs: ^0.3.24 => 0.3.24 chartjs-plugin-annotation: ^1.0.2 => 1.0.2 chartjs-plugin-datalabels: ^2.0.0 => 2.0.0 ci-info: undefined () cli-select: undefined () comment-json: undefined () compression: undefined () conf: undefined () content-type: undefined () cookie: undefined () cross-spawn: undefined () css-loader: undefined () date-fns: ^2.23.0 => 2.23.0 debug: undefined () devalue: undefined () enzyme: ^3.11.0 => 3.11.0 enzyme-adapter-react-16: ^1.15.6 => 1.15.6 enzyme-to-json: ^3.6.2 => 3.6.2 escape-string-regexp: undefined () eslint: ^7.32.0 => 7.32.0 eslint-config-next: 11.1.0 => 11.1.0 eslint-config-prettier: ^8.3.0 => 8.3.0 file-loader: undefined () find-cache-dir: undefined () find-up: undefined () fresh: undefined () gzip-size: undefined () http-proxy: undefined () ignore-loader: undefined () is-animated: undefined () is-docker: undefined () is-wsl: undefined () json5: undefined () jsonwebtoken: undefined () loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () markdown-to-jsx: ^7.1.3 => 7.1.3 mime-types: ^2.1.32 => 2.1.32 mini-css-extract-plugin: undefined () nanoid: ^3.1.25 => undefined (3.1.25, , 2.1.11) neo-async: undefined () next: ^11.1.0 => 11.1.0 node-sass: ^6.0.1 => 6.0.1 ora: undefined () papaparse: ^5.3.1 => 5.3.1 polished: ^4.1.3 => 4.1.3 postcss-flexbugs-fixes: undefined () postcss-loader: undefined () postcss-preset-env: undefined () postcss-scss: undefined () react: ^17.0.2 => 17.0.2 react-bootstrap: ^1.6.1 => 1.6.1 react-bootstrap/AbstractNav: undefined () react-bootstrap/AbstractNavItem: undefined () react-bootstrap/Accordion: undefined () react-bootstrap/AccordionCollapse: undefined () react-bootstrap/AccordionContext: undefined () react-bootstrap/AccordionToggle: undefined () react-bootstrap/Alert: undefined () react-bootstrap/Badge: undefined () react-bootstrap/BootstrapModalManager: undefined () react-bootstrap/Breadcrumb: undefined () react-bootstrap/BreadcrumbItem: undefined () react-bootstrap/Button: undefined () react-bootstrap/ButtonGroup: undefined () react-bootstrap/ButtonToolbar: undefined () react-bootstrap/Card: undefined () react-bootstrap/CardColumns: undefined () react-bootstrap/CardContext: undefined () react-bootstrap/CardDeck: undefined () react-bootstrap/CardGroup: undefined () react-bootstrap/CardImg: undefined () react-bootstrap/Carousel: undefined () react-bootstrap/CarouselCaption: undefined () react-bootstrap/CarouselItem: undefined () react-bootstrap/CloseButton: undefined () react-bootstrap/Col: undefined () react-bootstrap/Collapse: undefined () react-bootstrap/Container: undefined () react-bootstrap/Dropdown: undefined () react-bootstrap/DropdownButton: undefined () react-bootstrap/DropdownItem: undefined () react-bootstrap/DropdownMenu: undefined () react-bootstrap/DropdownToggle: undefined () react-bootstrap/ElementChildren: undefined () react-bootstrap/Fade: undefined () react-bootstrap/Feedback: undefined () react-bootstrap/Figure: undefined () react-bootstrap/FigureCaption: undefined () react-bootstrap/FigureImage: undefined () react-bootstrap/Form: undefined () react-bootstrap/FormCheck: undefined () react-bootstrap/FormCheckInput: undefined () react-bootstrap/FormCheckLabel: undefined () react-bootstrap/FormContext: undefined () react-bootstrap/FormControl: undefined () react-bootstrap/FormFile: undefined () react-bootstrap/FormFileInput: undefined () react-bootstrap/FormFileLabel: undefined () react-bootstrap/FormGroup: undefined () react-bootstrap/FormLabel: undefined () react-bootstrap/FormText: undefined () react-bootstrap/Image: undefined () react-bootstrap/InputGroup: undefined () react-bootstrap/Jumbotron: undefined () react-bootstrap/ListGroup: undefined () react-bootstrap/ListGroupItem: undefined () react-bootstrap/Media: undefined () react-bootstrap/Modal: undefined () react-bootstrap/ModalBody: undefined () react-bootstrap/ModalContext: undefined () react-bootstrap/ModalDialog: undefined () react-bootstrap/ModalFooter: undefined () react-bootstrap/ModalHeader: undefined () react-bootstrap/ModalTitle: undefined () react-bootstrap/Nav: undefined () react-bootstrap/NavContext: undefined () react-bootstrap/NavDropdown: undefined () react-bootstrap/NavItem: undefined () react-bootstrap/NavLink: undefined () react-bootstrap/Navbar: undefined () react-bootstrap/NavbarBrand: undefined () react-bootstrap/NavbarCollapse: undefined () react-bootstrap/NavbarContext: undefined () react-bootstrap/NavbarToggle: undefined () react-bootstrap/Overlay: undefined () react-bootstrap/OverlayTrigger: undefined () react-bootstrap/PageItem: undefined () react-bootstrap/Pagination: undefined () react-bootstrap/Popover: undefined () react-bootstrap/PopoverContent: undefined () react-bootstrap/PopoverTitle: undefined () react-bootstrap/ProgressBar: undefined () react-bootstrap/ResponsiveEmbed: undefined () react-bootstrap/Row: undefined () react-bootstrap/SafeAnchor: undefined () react-bootstrap/SelectableContext: undefined () react-bootstrap/Spinner: undefined () react-bootstrap/SplitButton: undefined () react-bootstrap/Switch: undefined () react-bootstrap/Tab: undefined () react-bootstrap/TabContainer: undefined () react-bootstrap/TabContent: undefined () react-bootstrap/TabContext: undefined () react-bootstrap/TabPane: undefined () react-bootstrap/Table: undefined () react-bootstrap/Tabs: undefined () react-bootstrap/ThemeProvider: undefined () react-bootstrap/Toast: undefined () react-bootstrap/ToastBody: undefined () react-bootstrap/ToastContext: undefined () react-bootstrap/ToastHeader: undefined () react-bootstrap/ToggleButton: undefined () react-bootstrap/ToggleButtonGroup: undefined () react-bootstrap/Tooltip: undefined () react-bootstrap/createChainedFunction: undefined () react-bootstrap/createWithBsPrefix: undefined () react-bootstrap/divWithClassName: undefined () react-bootstrap/helpers: undefined () react-bootstrap/transitionEndListener: undefined () react-bootstrap/triggerBrowserReflow: undefined () react-bootstrap/types: undefined () react-bootstrap/usePopperMarginModifiers: undefined () react-bootstrap/useWrappedRefWithWarning: undefined () react-chartjs-2: ^3.0.4 => 3.0.4 react-dom: ^17.0.2 => 17.0.2 react-icons: ^4.2.0 => 4.2.0 react-map-gl: ^6.1.16 => 6.1.16 react-player: ^2.9.0 => 2.9.0 react-query: ^3.21.0 => 3.21.0 react-to-print: ^2.13.0 => 2.13.0 recast: undefined () resolve-url-loader: undefined () sass-loader: undefined () schema-utils: undefined () semver: undefined () send: undefined () shortid: ^2.2.16 => 2.2.16 source-map: undefined () string-hash: undefined () strip-ansi: undefined () styled-components: ^5.3.1 => 5.3.1 styled-components/macro: undefined () styled-components/native: undefined () styled-components/primitives: undefined () terser: undefined () text-table: undefined () unistore: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () npmGlobalPackages: @aws-amplify/cli: 5.3.1 npm: 6.14.14 ```

Describe the bug

I'm migrating a Create React App app over to Next.js and I'm finding troubles getting the API to work in the SSR backend environment. In the client-side rendered app, everything has been running fine. Using react-query, I've been able to call things like API.get("api","foo/bar") with absolutely no problem. But in migrating to Next.js and trying to run the same query in getServerSideProps has been a massive headache.

The API in question is a lambda built by someone else at my company, and their process has always involved Bearer token authentication.

The Amplify config file has stayed the same between the two. I don't have an aws-exports file as it seems like the project was initial setup without it, before my time:

// config.js
import { Auth } from "aws-amplify";

const config = {
  Auth: {
    identityPoolId: envVar.cognito.IDENTITY_POOL_ID,
    region: envVar.cognito.REGION,
    userPoolId: envVar.cognito.USER_POOL_ID,
    userPoolWebClientId: envVar.cognito.APP_CLIENT_ID,
    mandatorySignIn: false
  },

  API: {
    endpoints: [
      {
        name: "api",
        endpoint: envVar.apiGateway.URL,
        region: envVar.apiGateway.REGION,
        custom_header: async () => {
          return {
            Authorization: `Bearer ${(await Auth.currentSession())
              .getIdToken()
              .getJwtToken()}`
          };
        }
      }
    ]
  },

  Storage: {
    AWSS3: {
      bucket: envVar.s3.BUCKET_NAME,
      region: envVar.s3.REGION
    }
  }
};

As you can see, the endpoint's custom_header has to use the jwtToken as a Bearer token to authenticate with the endpoint. This has been working perfectly in the Front End, but failing in Next.js

My Next.js setup is as follows (to show that I'm doing everything as expected):

// pages/index.js
import Amplify, { withSSRContext } from "aws-amplify";

import config from "~/config";

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

export default function Index({ charityStatus }) {
  return "Nothing to see here";
}

export async function getServerSideProps(context) {
  const { Auth, API } = withSSRContext(context);

  try {
    const user = await Auth.currentAuthenticatedUser();
    const charityRegNo = user.attributes["custom:charity_number"];

    // Fails here, and goes straight to the catch handler
    const charityStatus = await API.get(
      "api",
      `charities/${charityRegNo}/status`
    );

    console.log(charityStatus);
    return {
      props: {
        charityRegNo,
        charityStatus
      } // will be passed to the page component as props
    };
  } catch (error) {
    console.error("ERROR:", error);

    return {};
  }
}

I think I'm somewhat aware of what the issue is - jwtToken logic in the config is using Auth from Amplify, while the one in my function is using the version supplied by withSSRContext and it seems like in other issues (#7374, #5435 ) that this might be a problem? But I have no idea how to work around it.

My logic in my custom _app.js seems to be working as expected

// pages/_app.js
import { withAuthenticator } from "@aws-amplify/ui-react";

function CustomApp({ Component, pageProps }) {
  return <Component {...pageProps} />;
}

export default withAuthenticator(CustomApp);

To the point where if I console.log user in getServerSideProps I get a valid result with my credentials visible. So I'm pretty confident I have a real, signed in user.

I'm just completely stumped. I feel like the answer is just staring me in the face and I can't see it.

if you need any more info, please let me know.

Expected behavior

The API should authenticate and return data, much like it does when fetched client-side

Reproduction steps

  1. Try to connect to an endpoint expecting a Bearer auth header in getServerSideProps

Code Snippet

// See bug explainer above

Log output

``` // Put your logs below this line ```

aws-exports.js

No response

Manual configuration

See above

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

chrisbonifacio commented 3 years ago

Hi @DanProudfoot 👋 are you still experiencing this issue and in need of assistance?

DanProudfoot commented 3 years ago

Hi @chrisbonifacio I never managed to fix it, but had to give up. I still don't think it worked in the end, I probably hacked around it. So I think it would be useful to fix

chrisbonifacio commented 3 years ago

So, I was able to reproduce this issue. What the problem seems to be is that the Auth config from the client does not apply to the server side api calls. We reinitialize/reconfigure Auth & API libraries when we call withSSRContext and pass in credentials from the context. You can get the jwtToken needed to authorize the API Gateway requests like so from within getServerSideProps

export async function getServerSideProps(context) {
  const { Auth, API } = withSSRContext(context);

  try {
    const user = await Auth.currentAuthenticatedUser();

    const data = await API.get("api", `/data`, {
      headers: {
        custom_header: `Bearer ${user.signInUserSession.idToken.jwtToken}`, // get jwtToken
      },
    });

    return {
      props: {
        data,
      },
    };
  } catch (error) {
    console.log(error);
    return {
      props: {
        data: null,
      },
    };
  }
}
github-actions[bot] commented 1 year ago

This issue has been automatically locked since there hasn't been any recent activity after it was closed. Please open a new issue for related bugs.

Looking for a help forum? We recommend joining the Amplify Community Discord server *-help channels or Discussions for those types of questions.