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.4k stars 2.11k forks source link

Nextjs middleware and social Google login #13056

Open Maayanyeru123 opened 4 months ago

Maayanyeru123 commented 4 months ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

Authentication, GraphQL API, Storage

Amplify Version

v6

Amplify Categories

auth

Backend

Amplify CLI

Environment information

 System:
    OS: Windows 11 10.0.22631
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz
    Memory: 21.57 GB / 31.71 GB
  Binaries:
    Node: 20.3.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.21 - ~\AppData\Roaming\npm\yarn.CMD
    npm: 9.6.7 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (122.0.2365.52)
    Internet Explorer: 11.0.22621.1
  npmPackages:
    @ampproject/toolbox-optimizer:  undefined ()
    @aws-amplify/adapter-nextjs: ^1.0.16 => 1.0.16
    @aws-amplify/adapter-nextjs/api:  undefined ()
    @aws-amplify/adapter-nextjs/data:  undefined ()
    @babel/core:  undefined ()
    @babel/runtime:  7.22.5
    @edge-runtime/cookies:  4.0.2
    @edge-runtime/ponyfill:  2.4.1
    @edge-runtime/primitives:  4.0.2
    @hapi/accept:  undefined ()
    @headlessui/react: ^1.7.18 => 1.7.18
    @mswjs/interceptors:  undefined ()
    @napi-rs/triples:  undefined ()
    @next/font:  undefined ()
    @next/react-dev-overlay:  undefined ()
    @opentelemetry/api:  undefined ()
    @reduxjs/toolkit: ^2.2.1 => 2.2.1
    @reduxjs/toolkit-query:  1.0.0
    @reduxjs/toolkit-query-react:  1.0.0
    @reduxjs/toolkit-react:  1.0.0
    @types/node: ^20 => 20.11.16
    @types/react: ^18 => 18.2.55
    @types/react-dom: ^18 => 18.2.18
    @vercel/nft:  undefined ()
    @vercel/og:  0.6.2
    acorn:  undefined ()
    amphtml-validator:  undefined ()
    anser:  undefined ()
    arg:  undefined ()
    assert:  undefined ()
    async-retry:  undefined ()
    async-sema:  undefined ()
    autoprefixer: ^10.4.17 => 10.4.17
    aws-amplify: ^6.0.15 => 6.0.15
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    babel-packages:  undefined ()
    browserify-zlib:  undefined ()
    browserslist:  undefined ()
    buffer:  undefined ()
    bytes:  undefined ()
    ci-info:  undefined ()
    cli-select:  undefined ()
    client-only:  0.0.1
    comment-json:  undefined ()
    compression:  undefined ()
    conf:  undefined ()
    constants-browserify:  undefined ()
    content-disposition:  undefined ()
    content-type:  undefined ()
    cookie:  undefined ()
    cross-spawn:  undefined ()
    crypto-browserify:  undefined ()
    css.escape:  undefined ()
    data-uri-to-buffer:  undefined ()
    debug:  undefined ()
    devalue:  undefined ()
    domain-browser:  undefined ()
    edge-runtime:  undefined ()
    eslint: ^8 => 8.56.0
    eslint-config-next: 14.1.0 => 14.1.0
    events:  undefined ()
    find-cache-dir:  undefined ()
    find-up:  undefined ()
    framer-motion: ^11.0.5 => 11.0.6
    fresh:  undefined ()
    get-orientation:  undefined ()
    glob:  undefined ()
    gzip-size:  undefined ()
    http-proxy:  undefined ()
    http-proxy-agent:  undefined ()
    https-browserify:  undefined ()
    https-proxy-agent:  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-runner:  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: 14.1.0 => 14.1.0
    next-auth: ^5.0.0-beta.9 => 5.0.0-beta.11
    node-fetch:  undefined ()
    node-html-parser:  undefined ()
    ora:  undefined ()
    os-browserify:  undefined ()
    p-limit:  undefined ()
    path-browserify:  undefined ()
    platform:  undefined ()
    postcss: ^8.4.35 => 8.4.35 (8.4.31)
    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 => 18.2.0
    react-builtin:  undefined ()
    react-dom: ^18 => 18.2.0
    react-dom-builtin:  undefined ()
    react-dom-experimental-builtin:  undefined ()
    react-experimental-builtin:  undefined ()
    react-is:  18.2.0
    react-redux: ^9.1.0 => 9.1.0
    react-refresh:  0.12.0
    react-router-dom: ^6.22.1 => 6.22.1
    react-server-dom-turbopack-builtin:  undefined ()
    react-server-dom-turbopack-experimental-builtin:  undefined ()
    react-server-dom-webpack-builtin:  undefined ()
    react-server-dom-webpack-experimental-builtin:  undefined ()
    regenerator-runtime:  0.13.4
    sass-loader:  undefined ()
    scheduler-builtin:  undefined ()
    scheduler-experimental-builtin:  undefined ()
    schema-utils:  undefined ()
    semver:  undefined ()
    send:  undefined ()
    server-only:  0.0.1
    setimmediate:  undefined ()
    shell-quote:  undefined ()
    source-map:  undefined ()
    stacktrace-parser:  undefined ()
    stream-browserify:  undefined ()
    stream-http:  undefined ()
    string-hash:  undefined ()
    string_decoder:  undefined ()
    strip-ansi:  undefined ()
    superstruct:  undefined ()
    tailwindcss: ^3.4.1 => 3.4.1
    tar:  undefined ()
    terser:  undefined ()
    text-table:  undefined ()
    timers-browserify:  undefined ()
    tty-browserify:  undefined ()
    typescript: ^5 => 5.3.3
    ua-parser-js:  undefined ()
    unistore:  undefined ()
    util:  undefined ()
    vm-browserify:  undefined ()
    watchpack:  undefined ()
    web-vitals:  undefined ()
    webpack:  undefined ()
    webpack-sources:  undefined ()
    ws:  undefined ()
    zod:  undefined ()
  npmGlobalPackages:
    @aws-amplify/cli: 12.10.1
    aws-amplify: 6.0.16
    aws-sam-local: 0.2.11
    latest: 0.2.0
    nodemon: 3.0.3
    npx: 10.2.2
    ts-node: 10.9.2
    typescript: 5.3.3
    yarn: 1.22.21

Describe the bug

When using NextJs middleware as stated in Amplify documentation after signing in with Google provider "await fetchAuthSession" Doesn't return the signing user

Expected behavior

Getting the signing user session

Reproduction steps

1, going to app Home page

  1. login with Google account
  2. Going to Home
  3. returns to sign in as user is not authenticated

Code Snippet

aws-exports.js

{
  "aws_project_region": "eu-west-1",
  "aws_cognito_identity_pool_id": XXXX
  "aws_cognito_region": "eu-west-1",
  "aws_user_pools_id": XXXX,
  "aws_user_pools_web_client_id": XXXXX,
  "oauth": {
    "domain": XXXXX,
    "scope": [
      "phone",
      "email",
      "openid",
      "profile",
      "aws.cognito.signin.user.admin"
    ],
    "redirectSignIn": "http://localhost:3000/home",
    "redirectSignOut": "http://localhost:3000/login/signin",
    "responseType": "code"
  },
  "federationTarget": "COGNITO_USER_POOLS",
  "aws_cognito_username_attributes": [],
  "aws_cognito_social_providers": [
    "GOOGLE"
  ],
  "aws_cognito_signup_attributes": [
    "EMAIL"
  ],
  "aws_cognito_mfa_configuration": "OFF",
  "aws_cognito_mfa_types": [
    "SMS"
  ],
  "aws_cognito_password_protection_settings": {
    "passwordPolicyMinLength": 8,
    "passwordPolicyCharacters": []
  },
  "aws_cognito_verification_mechanisms": [
    "EMAIL"
  ]
}

serverUtils.ts

import { createServerRunner } from '@aws-amplify/adapter-nextjs';
import config from '../../amplifyconfiguration.json';

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

layout.tsx

import config from './aws-exports.js';
Amplify.configure(config, {
  ssr: true, 
});

middleware.ts

import { fetchAuthSession } from 'aws-amplify/auth/server';
import { NextRequest, NextResponse } from 'next/server';
import { runWithAmplifyServerContext } from '@/app/utils/server/serverUtils';
const authRoutes =['home'];
export async function middleware(request: NextRequest) {
  const response = NextResponse.next();
  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { request, response },
    operation: async (contextSpec:any) => {
      try {
        const session = await fetchAuthSession(contextSpec);
        return session.tokens !== undefined;
      } catch (error) {
        return false;
      }
    }
  });
  if (authenticated) {
    return NextResponse.next();
  }
  return NextResponse.redirect(new URL('/login/signin', request.url));
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|logo-white.svg|_next/image|logo.svg|login|signin|signup|login/signup/success|icons|img|public|$).*)',

  ]
};

import Image from "next/image";
import { signInWithRedirect } from "aws-amplify/auth";

interface SocialSignInButtonProps {
    provider: 'Google' | 'Facebook' | 'Apple'; // Add more providers as needed
    isLogin: boolean;
    // customState?: string; // Optional prop to pass custom state if needed
}

SocialLoginButton.tsx

const SocialSignInButton: React.FC<SocialSignInButtonProps> = ({ provider, isLogin }) => {
  const buttonText = !isLogin ? 'Sign up with' : '';
  const iconSize = isLogin ? 20 : 24; // Adjust icon size if needed
  const isEnabled = provider === 'Google';
  const buttonClass = `social-button ${provider.toLowerCase()} ${isLogin ? 'icon' : ''} ${!isEnabled ? 'disabled:bg-slate-400' : ''}`;

  const handleClick = () => {
    if (isEnabled) {
      signInWithRedirect({ provider, customState: 'Google' });
    }
  };

  return (
    <div>
      <button
        type="button"
        className={buttonClass}
        disabled={!isEnabled}
        onClick={handleClick}
      >
        <Image src={`/icons/${provider.toLowerCase()}.svg`} alt={`${provider} Logo`} width={iconSize} height={iconSize} priority />
        {!isLogin && ` ${buttonText} ${provider}`}
      </button>
    </div>
  );
};

export default SocialSignInButton;

Log output

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

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": "eu-west-1", "aws_cognito_identity_pool_id": "XXXX", "aws_cognito_region": "eu-west-1", "aws_user_pools_id": "XXXXX", "aws_user_pools_web_client_id": "XXXXX", "oauth": { "domain": "XXXXXX", "scope": [ "phone", "email", "openid", "profile", "aws.cognito.signin.user.admin" ], "redirectSignIn": "http://localhost:3000/home", "redirectSignOut": "http://localhost:3000/login/signin", "responseType": "code" }, "federationTarget": "COGNITO_USER_POOLS", "aws_cognito_username_attributes": [], "aws_cognito_social_providers": [ "GOOGLE" ], "aws_cognito_signup_attributes": [ "EMAIL" ], "aws_cognito_mfa_configuration": "OFF", "aws_cognito_mfa_types": [ "SMS" ], "aws_cognito_password_protection_settings": { "passwordPolicyMinLength": 8, "passwordPolicyCharacters": [] }, "aws_cognito_verification_mechanisms": [ "EMAIL" ] };

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

hasadata commented 4 months ago

+1 I am using amplify Authenticator UI and facing the same issues, the cookies are not set, the middleware is unable to retrieve the user

HuiSF commented 4 months ago

Hi @Maayanyeru123 @hasadata thanks for opening this issue.

Looking at @Maayanyeru123 's configuration, I anticipate you are calling signInWithRedirect from the route /signin, after your end user sign in at Google's UI, it redirects your end user back to the route /home. If that's the case, you would need to follow this instruction to ensure the OAuth completion listener is added to your /home route so it can completes the sign in flow and persist the auth token in cookie store.

hasadata commented 4 months ago

adding the import fixed the issue

cwomack commented 4 months ago

Thanks for the confirmation, @hasadata.

@Maayanyeru123, let us know if it resolves it for you as well.

Maayanyeru123 commented 4 months ago

Thanks for the confirmation, @hasadata.

@Maayanyeru123, let us know if it resolves it for you as well.

Not for me :) probably something I am missing here. The Home page is secured asset so implementing the import 'aws-amplify/auth/enable-oauth-listener'; will even work?

Maayanyeru123 commented 4 months ago

I fixed it with an ugly hack i created a middle page called redirect but this is so ugly

cwomack commented 4 months ago

@Maayanyeru123, would you mind sharing the hacky way that you had to work around this? Wondering if we can provide any suggestions on it if you can share the frontend code. Thanks!

Maayanyeru123 commented 4 months ago

@Maayanyeru123, would you mind sharing the hacky way that you had to work around this? Wondering if we can provide any suggestions on it if you can share the frontend code. Thanks!

Hey, So I have created the following page redirect that is not protected by the middleware it imports aws-amplify/auth/enable-oauth-listener it is working perfectly fine but when i am putting it on the protected resource(Home Page) it doesn't work


"use client";
import 'aws-amplify/auth/enable-oauth-listener';
import { useRouter } from "next/navigation";

import { useAppSelector } from '@/app/redux/store';
import { useEffect } from 'react';
import { HOME} from '@/app/utils/client/consts';
import { getCurrentUser } from "aws-amplify/auth";
function Redirect() {

    const router = useRouter();
    const username = useAppSelector((state:any)=>state.AuthReducer.value.username);
    let currentUser;
    useEffect(() => {
        async function goToHome() {
            currentUser = await getCurrentUser();
            router.push(HOME);   
        }
        goToHome();

    }, []);

    return (
        <div>
            {currentUser}
        </div>
    );
  };
  export default Redirect;

The currentUser = await getCurrentUser(); returns the user correctly.

AtharvArolkar commented 2 months ago

Can you share please more on how did call the Redirect. Im facing the same issue. It'll be helpful to me