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.42k stars 2.12k forks source link

Amplify support for NextAuth.js #8678

Closed IgorDePaula closed 1 year ago

IgorDePaula commented 3 years ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

GraphQL API

Amplify Categories

auth

Environment information

``` System: OS: Linux 5.8 Ubuntu 20.10 (Groovy Gorilla) CPU: (8) x64 AMD FX(tm)-8300 Eight-Core Processor Memory: 237.21 MB / 15.62 GB Container: Yes Shell: 5.0.17 - /bin/bash Binaries: Node: 12.19.0 - ~/.nvm/versions/node/v12.19.0/bin/node Yarn: 1.12.3 - ~/.yarn/bin/yarn npm: 7.20.2 - ~/.nvm/versions/node/v12.19.0/bin/npm Watchman: 4.9.0 - /usr/bin/watchman Browsers: Chrome: 89.0.4389.82 Firefox: 90.0 npmPackages: @ampproject/toolbox-optimizer: undefined () @aws-amplify/ui-react: ^1.2.8 => 1.2.8 @babel/core: undefined () @headlessui/react: ^1.3.0 => 1.3.0 @heroicons/react: ^1.0.3 => 1.0.3 @tailwindcss/aspect-ratio: ^0.2.1 => 0.2.1 @tailwindcss/forms: ^0.3.3 => 0.3.3 @tailwindcss/line-clamp: ^0.2.1 => 0.2.1 @tailwindcss/typography: ^0.4.1 => 0.4.1 amphtml-validator: undefined () arg: undefined () async-retry: undefined () async-sema: undefined () autoprefixer: ^10.3.1 => 10.3.1 aws-amplify: ^4.0.3 => 4.2.2 aws-appsync: ^4.1.1 => 4.1.1 bfj: undefined () cacache: undefined () ci-info: undefined () comment-json: undefined () compression: undefined () conf: undefined () content-type: undefined () cookie: undefined () css-loader: undefined () debug: undefined () devalue: undefined () escape-string-regexp: undefined () eslint: 7.31.0 => 7.31.0 eslint-config-next: 11.0.1 => 11.0.1 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 () luxon: ^2.0.1 => 2.0.1 mini-css-extract-plugin: undefined () nanoid: undefined () neo-async: undefined () next: 11.0.1 => 11.0.1 next-auth: ^3.27.3 => 3.27.3 ora: undefined () postcss: ^8.3.6 => 8.3.6 (8.2.13) postcss-flexbugs-fixes: undefined () postcss-loader: undefined () postcss-preset-env: undefined () postcss-scss: undefined () react: 17.0.2 => 17.0.2 react-dom: 17.0.2 => 17.0.2 react-hook-form: ^7.12.1 => 7.12.1 recast: undefined () resolve-url-loader: undefined () sass-loader: undefined () schema-utils: undefined () semver: undefined () send: undefined () source-map: undefined () string-hash: undefined () strip-ansi: undefined () tailwindcss: ^2.2.7 => 2.2.7 terser: undefined () text-table: undefined () unistore: undefined () web-vitals: undefined () webpack: undefined () webpack-filter-warnings-plugin: ^1.2.1 => 1.2.1 webpack-sources: undefined () npmGlobalPackages: @aws-amplify/cli: 5.1.2 amplify-cli: 1.0.0 ask-cli: 2.22.4 npm: 7.20.2 react-native-cli: 2.0.1 wscat: 4.0.1 ```

Describe the bug

In nextjs 11, The Auth from amplify get authenticated user, but the API not. The Auth passes the user to next session, I get session with the user but the API not.

Expected behavior

The API from amplify got authenticated user automatically.

Reproduction steps

  1. install amplify api and auth on next (with next auth)
  2. make login and do login

Code Snippet

// Put your code below this line.
// [...nextauth].js

import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import {Auth} from 'aws-amplify'

export default NextAuth({
    pages: {
        error: "/login", // Changing the error redirect page to our custom login page
    },
    callbacks: {
        async session(session, token) {
            return token
        },
    },
    providers: [
        Providers.Credentials({
            name: "Credentials",
            async authorize(credentials) {
                try {
                    const user = await Auth.signIn(credentials.email, credentials.password)
                    if (user) {
                        const data = {
                           name: user.attributes.name, email: user.attributes.email, image:'asdasdasd',id:user.attributes.sub,
                        }
                        return data
                    }
                } catch (e) {
                    const errorMessage = e.message
                    throw new Error(errorMessage + "&email=" + credentials.email)
                }
            },
        }),
    ],
})

//dashboard.js
import {CalendarIcon, LocationMarkerIcon, UsersIcon} from '@heroicons/react/solid'
import {useState, useEffect} from 'react'
import {useTheme} from "../src/ThemeContext";
import {API, Auth} from "aws-amplify";
import {listProjects} from "../src/graphql/queries";
import Loader from "../src/components/Loader"
import {useSession} from 'next-auth/client'
import {useRouter} from "next/router";

export default function Dashboard() {
    const [session, loadingSession] = useSession()
    const router = useRouter()
    useEffect(async () => {
        if (!loadingSession && !session) {
            router.push('/login')
        }
    }, [session, loadingSession])

    const [projects, setProjects] = useState([])
    const [loading, setLoading] = useState(true)
    const [user, setUser] = useState()
    const theme = useTheme()
    useEffect(async () => {
        const postData = await API.graphql({
            query: listProjects,
        })
        const user = await Auth.currentAuthenticatedUser()
        setUser(user.attributes)
        setProjects(postData.data.listProjects.items)
        setLoading(false)
    }, [])
    return (
        <div>
            <div className="md:flex md:items-center md:justify-between">
                <div className="flex-1 min-w-0">
                    <h2 className="text-2xl font-bold leading-7 text-gray-900 sm:text-3xl sm:truncate">Projetos</h2>
                </div>
                <div className="mt-4 flex md:mt-0 md:ml-4">
                    {/*<button
                        type="button"
                        className="inline-flex items-center px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
                    >
                        Edit
                    </button>*/}
                    <a href="/project/new"
                       type="button"
                       className={`ml-3 inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-${theme.primary.bg.normal} hover:bg-${theme.primary.bg.hover} focus:outline-none`}
                    >

                        Novo projeto
                    </a>

                </div>
            </div>

            <div
                className={`bg-white shadow overflow-hidden mt-5 sm:rounded-md ${loading && 'h-64 flex items-center justify-center'}`}>
                {loading && <Loader/>}
                {!loading && <ul className="divide-y divide-gray-200">
                    {projects.map((project) => (
                        <li key={project.id}>
                            <a href={`/project/${project.id}`} className="block hover:bg-gray-50">
                                <div className="px-4 py-4 sm:px-6">
                                    <div className="flex items-center justify-between">
                                        <p className="text-sm font-medium text-gray-700 truncate">{project.name}</p>
                                        <div className="ml-2 flex-shrink-0 flex">
                                            <p className="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
                                                {/*  {position.type}*/}
                                            </p>
                                        </div>
                                    </div>
                                    <div className="mt-2 sm:flex sm:justify-between">
                                        <div className="sm:flex">
                                            <p className="flex items-center text-sm text-gray-500">
                                                {/* <UsersIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"
                                                           aria-hidden="true"/>*/}
                                                {project.description}
                                                {/*{position.department}*/}
                                            </p>
                                            {/*<p className="mt-2 flex items-center text-sm text-gray-500 sm:mt-0 sm:ml-6">
                                                <LocationMarkerIcon
                                                    className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"
                                                    aria-hidden="true"/>
                                                 {position.location}
                                            </p>*/}
                                        </div>
                                        <div className="mt-2 flex items-center text-sm text-gray-500 sm:mt-0">
                                            {/*<CalendarIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-gray-400"
                                                          aria-hidden="true"/>
                                            <p>
                                                Closing on <time dateTime={position.closeDate}>{position.closeDateFull}</time>
                                            </p>*/}
                                        </div>
                                    </div>
                                </div>
                            </a>
                        </li>
                    ))}
                </ul>}
            </div>
        </div>
    )
}

Log output

``` // Put your logs below this line 15:35:49.354 Uncaught (in promise) The user is not authenticated asyncToGenerator.js:6 Babel 6 React 2 Babel 21 invoke runtime.js:294 defineIteratorMethods runtime.js:119 asyncGeneratorStep Babel ```

aws-exports.js

/ eslint-disable / // WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = { "aws_project_region": "us-east-1", "aws_cognito_identity_pool_id": "us-east-1:b57b4029-e7ec-4ac3-9fe8-fd228axxx", "aws_cognito_region": "us-east-1", "aws_user_pools_id": "us-east-1_NBb11vxuxxxx", "aws_user_pools_web_client_id": "4qu3b114r3d7at7uupxxx6v", "oauth": {}, "aws_appsync_graphqlEndpoint": "https://iungoxxxi.appsync-api.us-east-1.amazonaws.com/graphql", "aws_appsync_region": "us-east-1", "aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS" };

export default awsmobile;

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

chrisbonifacio commented 3 years ago

Hey @IgorDePaula 👋 Thank you for raising this issue. I am not too familiar with NextAuth but if you're using Amplify in a SSR context, we provide this withSSRContext utility function to reinitialize Amplify libraries on the server to help maintain user session between pages. Have you tried using this yet?

https://docs.amplify.aws/start/getting-started/data-model/q/integration/next#api-with-server-side-rendering-ssr

IgorDePaula commented 3 years ago

@chrisbonifacio Thank you for response. No, I'm not using nextjs with SSR. 3 weeks ago I'm using amplify without authenticator components on nextjs, but it stopped from anywhere. Now I can singIn with user, but any pages, any resources recognize the user, I'm using nextjs 11.

chrisbonifacio commented 3 years ago

@IgorDePaula are you able to log the user on the client side with Auth.currentAuthenticatedUser to confirm that Amplify still has the credentials to pass to API.graphql? I don't know how NextAuth works but if you're already using Amplify/Cognito to manage your user's session, I don't know that you would need it.

EDIT: From reading the NextAuth documentation, it seems to me that the authentication does indeed happen on the server. There would be no way, as far as I can tell, for the Amplify instance created on the client to have been properly configured and be able to authorize API.graphql calls automatically. The way we recommend persisting user session (in an Amplify project) between requests for the user is to use the withSSRContext utility function which has to reinitialize Amplify on the server in order to properly create and use credentials for other categories such as API and DataStore.

IgorDePaula commented 3 years ago

@chrisbonifacio I discovery that I developed the app in mock api, all works fine, Thinking that next auth was syncronizing with nextauth, but no. I got authenticate an user with nextauth and amplify using credentials, but the cookies/session is not passed to nextauth, with this, in anothr pages the auth amplify module not recognize the authenticated user. I have/must/need/obligatory use authenticator from amplify, I can't use next auth only auth module from amplify.

IgorDePaula commented 3 years ago

This is bad, vary bad.

chrisbonifacio commented 3 years ago

Right, there's no connection at the moment between Amplify and NextAuth. I labeled this as a feature request for the team to consider adding support for in the future.

IgorDePaula commented 3 years ago

Thank you very much

IgorDePaula commented 3 years ago

@chrisbonifacio my try https://gist.github.com/IgorDePaula/f3db1e5c4e7c94603f356dd76881d7f5

chrisbonifacio commented 3 years ago

Just out of curiosity, could you try calling Amplify.configure({...awsconfig, ssr: true}) under the imports of your index.js file and see if the API calls are authenticated afterwards? It may require refreshing the page after sign in or perhaps the redirect to the dashboard will suffice.

IgorDePaula commented 3 years ago

I'll try.

IgorDePaula commented 3 years ago

@chrisbonifacio You're right. Now the amplify try set something on navigador.

Captura de tela de 2021-08-15 15-23-21 But with ssr not works.

cwomack commented 1 year ago

Hello, @IgorDePaula. In reviewing some of the older issues in our repo, I came across this one and wanted to check in to see if you found a workaround or solution yet. NextAuth is still not supported "out of the box" with Amplify at this time (making this feature request still valid), but let us know if there's any further context or progress you were able to make. Thanks!

IgorDePaula commented 1 year ago

@cwomack No, I'm not using next-auth with amplify. I understand how works it now.