Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No federated jwt in function handler #420

Open
Z-Shang opened this issue Jan 7, 2025 · 5 comments
Open

No federated jwt in function handler #420

Z-Shang opened this issue Jan 7, 2025 · 5 comments

Comments

@Z-Shang
Copy link

Z-Shang commented Jan 7, 2025

Environment information

Binaries:
  Node: 20.3.1 - ~/.nvm/versions/node/v20.3.1/bin/node
  Yarn: 1.22.21 - /usr/bin/yarn
  npm: 9.6.7 - ~/.nvm/versions/node/v20.3.1/bin/npm
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/auth-construct: 1.3.2
  @aws-amplify/backend: 1.5.1
  @aws-amplify/backend-auth: 1.2.0
  @aws-amplify/backend-cli: 1.3.0
  @aws-amplify/backend-data: 1.1.5
  @aws-amplify/backend-deployer: 1.1.5
  @aws-amplify/backend-function: 1.7.1
  @aws-amplify/backend-output-schemas: 1.4.0
  @aws-amplify/backend-output-storage: 1.1.2
  @aws-amplify/backend-secret: 1.1.4
  @aws-amplify/backend-storage: 1.2.1
  @aws-amplify/cli-core: 1.1.3
  @aws-amplify/client-config: 1.5.0
  @aws-amplify/deployed-backend-client: 1.4.2
  @aws-amplify/form-generator: 1.0.3
  @aws-amplify/model-generator: 1.0.8
  @aws-amplify/platform-core: 1.1.0
  @aws-amplify/plugin-types: 1.3.0
  @aws-amplify/sandbox: 1.2.3
  @aws-amplify/schema-generator: 1.2.4
  aws-amplify: 6.11.0
  aws-cdk: 2.163.1
  aws-cdk-lib: 2.163.1
  typescript: 5.6.3
AWS environment variables:
  AWS_STS_REGIONAL_ENDPOINTS = regional
  AWS_NODEJS_CONNECTION_REUSE_ENABLED = 1
  AWS_SDK_LOAD_CONFIG = 1
No CDK environment variables

Describe the bug

I have a simple data schema where a data model Room that can have a list of active users, the authorization for Room is allow.authenticated()
I have made a mutation for Room namely joinRoom using function handler
On the client side, I'm hosting a Next.JS app with the Authenticator from @aws-amplify/ui-react for authentication
When calling the mutation from the client side with the generated client, I'm getting the following error:

{
    "path": [
        "joinRoom"
    ],
    "data": null,
    "errorType": "Lambda:Unhandled",
    "errorInfo": null,
    "locations": [
        {
            "line": 2,
            "column": 3,
            "sourceName": null
        }
    ],
    "message": "No federated jwt"
}

Reproduction steps

My data resource.ts is like:

const joinRoomHandler = defineFunction({
  entry: "./room-handler/joinRoom.ts",
});

const schema = a.schema({
...,
    Room: a
      .model({
        roomId: a.id().required(),
        activeUsers: a.id().array(),
      })
      .identifier(["roomId"])
      .authorization((allow) => [allow.authenticated()]),
    roomUpdateTyp: a.customType({
      roomId: a.id(),
      userId: a.id(),
    }),
    joinRoom: a
      .mutation()
      .arguments({
        roomId: a.id().required(),
        userId: a.id().required(),
      })
      .returns(a.ref("roomUpdateTyp"))
      .authorization((allow) => [allow.authenticated()])
      .handler(a.handler.function(joinRoomHandler)),
...
})
...

and my joinRoom.ts looks like:

import { Amplify } from "aws-amplify";
import type { Schema } from "../resource";
import { generateClient } from "@aws-amplify/api";
import output from "../../../amplify_outputs.json";

Amplify.configure(output);

const client = generateClient<Schema>();

export const handler: Schema["joinRoom"]["functionHandler"] = async (
  event: {
    arguments: {
      roomId: string;
      userId: string;
    };
  },
  context: any
) => {
  const roomId = event.arguments.roomId;
  const userId = event.arguments.userId;

  const room = await client.models.Room.get({ roomId });
  if (room.errors) {
    throw new Error("Error getting room: " + room.errors);
  }
  if (room.data?.activeUsers?.includes(userId)) {
    throw new Error("User already in room");
  }
  const updatedRoom = await client.models.Room.update({
    roomId,
    activeUsers: Array.from(
      new Set([...(room.data?.activeUsers || []), userId])
    ),
  });
  if (updatedRoom.errors) {
    throw new Error("Error updating room: " + updatedRoom.errors);
  }
  return {
    roomId,
    userId,
  };
};

On the client side, the mutation is invoked like:

import { data, type Schema } from "@/amplify/data/resource";
import { generateClient } from "aws-amplify/data";

const client = generateClient<Schema>();

...
await client.mutations.joinRoom({...});
@ykethan
Copy link
Member

ykethan commented Jan 7, 2025

Hey,👋 thanks for raising this! I'm going to transfer this over to our API repository for better assistance 🙂

@ykethan ykethan transferred this issue from aws-amplify/amplify-backend Jan 7, 2025
@ykethan ykethan added the Gen 2 label Jan 7, 2025
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Jan 7, 2025

Hi @Z-Shang 👋 thanks for raising this issue. It looks like you might just have to update your amplify packages:

npm install @aws-amplify/backend@latest @aws-amplify/backend-cli@latest

and to make sure the data-schema package is also up to date:

npm update @aws-amplify/data-schema

Lastly, please check out this page in our docs on accessing the GraphQL API from a Lambda function:
https://docs.amplify.aws/react/build-a-backend/data/customize-authz/grant-lambda-function-access-to-api/#access-the-api-using-aws-amplify

We recently released support for better DX when using client.models. Previously it was required to deploy twice for changes to apply to the Lambda function but now we read the schema from an S3 bucket so no extra work should be required for the schema to be up to date on subsequent deployments.

@chrisbonifacio chrisbonifacio self-assigned this Jan 7, 2025
@Z-Shang
Copy link
Author

Z-Shang commented Jan 8, 2025

Hi @chrisbonifacio :D Thanks for your response!
I did update everything:

Binaries:
  Node: 22.13.0 - ~/.nvm/versions/node/v22.13.0/bin/node
  Yarn: 1.22.21 - /usr/bin/yarn
  npm: 10.9.2 - ~/.nvm/versions/node/v22.13.0/bin/npm
  pnpm: undefined - undefined
NPM Packages:
  @aws-amplify/auth-construct: 1.5.1
  @aws-amplify/backend: 1.12.0
  @aws-amplify/backend-auth: 1.4.2
  @aws-amplify/backend-cli: 1.4.6
  @aws-amplify/backend-data: 1.4.0
  @aws-amplify/backend-deployer: 1.1.13
  @aws-amplify/backend-function: 1.11.0
  @aws-amplify/backend-output-schemas: 1.4.0
  @aws-amplify/backend-output-storage: 1.1.4
  @aws-amplify/backend-secret: 1.1.4
  @aws-amplify/backend-storage: 1.2.4
  @aws-amplify/cli-core: 1.2.1
  @aws-amplify/client-config: 1.5.5
  @aws-amplify/deployed-backend-client: 1.5.0
  @aws-amplify/form-generator: 1.0.3
  @aws-amplify/model-generator: 1.0.12
  @aws-amplify/platform-core: 1.5.0
  @aws-amplify/plugin-types: 1.7.0
  @aws-amplify/sandbox: 1.2.9
  @aws-amplify/schema-generator: 1.2.6
  aws-amplify: 6.11.0
  aws-cdk: 2.174.1
  aws-cdk-lib: 2.174.1
  typescript: 5.6.3
No AWS environment variables
No CDK environment variables

And weirdly my next.js middleware stopped working :(
I have my logics that requires user authentication wrapped within a <Authenticator> component which used to work, and after updating the libraries the same API calls that arrive in the middleware start to appear as unauthorized.
My middleware looks like:

import { NextResponse, NextRequest } from "next/server";
import { fetchAuthSession } from "@aws-amplify/auth/server";
import { runWithAmplifyServerContext } from "@/utils/amplify-utils";

export async function middleware(request: NextRequest) {
  const response = NextResponse.next();

  const authenticated = await runWithAmplifyServerContext({
    nextServerContext: { request, response },
    operation: async (contextSpec) => {
      console.log("ContextSpec:", contextSpec);
      try {
        const session = await fetchAuthSession(contextSpec, {});
        console.log("Session:", session);
        return session.tokens !== undefined;
      } catch (error) {
        console.error("Auth error:", error);
        return false;
      }
    },
  });

  if (!authenticated) {
    console.log("Redirecting to login");
    return NextResponse.redirect(new URL("/login", request.url));
  }

  return response;
}

My client side code looks like:

const getUserAttrs = async () => {
  const res = await fetch("/api/user", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "no-cache",
    },
    cache: "no-store",
    credentials: "include",
  });
  const data = await res.json();
  return data.user;
};

I did some logging that shows the authenticator has produced a valid AuthUser object and I could observe cookies being set in my browser:
image

I wonder if there was any breaking change and how should I migrate my authentication flow to the latest version?

@stocaaro stocaaro transferred this issue from aws-amplify/amplify-category-api Jan 8, 2025
@chrisbonifacio
Copy link
Member

chrisbonifacio commented Jan 9, 2025

Hi @Z-Shang, I'm having a little bit of trouble understanding exactly what the middleware logic is supposed to do in the example you shared. It looks like it's simply calling fetchAuthSession and returning the tokens.

Can you confirm that the console log of the session is showing anything? Is the "No federated jwt" error coming from the middleware? Are there other middleware examples of API calls being unauthorized?

Also, it looks like your aws-amplify package is still on v6.11.0. Can you try upgrading that as well? I'm not sure why the middleware authentication logic would be affected after upgrading the other packages.

Lastly, this is our docs example on how to authenticate in middleware, which seems very close to what you're doing. The only noteworthy difference I can tell is that you are passing an empty object to fetchAuthSession and our example doesn't.

@chrisbonifacio
Copy link
Member

chrisbonifacio commented Jan 10, 2025

Hey @Z-Shang 👋

the import { fetchAuthSession } from "@aws-amplify/auth/server" should be aws-amplify/auth/server

can you inspect the request cookie header you are sending to your API /api/user endpoint? it should have access token, id token and refresh token.

Lastly, was that session.tokens returned as undefined or did the server side fetchAuthSession call throw an error?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants