Closed CooperCodeComposer closed 5 months ago
I have a very related problem. It happens with Nextjs v 14.0.2 and above, it doesn't happen with 14.0.1. This happens when navigating as a signed-in user for the first time after deploying the app.
"@aws-amplify/api": "^5.4.9", "aws-amplify": "^5.3.15",
Error: No api-key configured
at e.<anonymous> (/var/task/.next/server/chunks/6453.js:112:27361)
at /var/task/.next/server/chunks/6453.js:75:10081
at Object.next (/var/task/.next/server/chunks/6453.js:75:10186)
at /var/task/.next/server/chunks/6453.js:75:9128
at new Promise (<anonymous>)
at D (/var/task/.next/server/chunks/6453.js:75:8877)
at e._headerBasedAuth (/var/task/.next/server/chunks/6453.js:112:26967)
at e.<anonymous> (/var/task/.next/server/chunks/6453.js:112:29581)
at /var/task/.next/server/chunks/6453.js:75:10081
at Object.next (/var/task/.next/server/chunks/6453.js:75:10186)
Hi @CooperCodeComposer 👋 thanks for raising this issue.
A few things:
Since it looks like you're using a route handler you don't need to use the runWithAmplifyServerContext
, that's for the req/res based client.
Have you tried logging what credentials are in the cookies? Do they contain the credentials you expect to authorize the graphql operation with?
It looks like your api's default auth mode is iam
. Is that the correct auth mode createTransaction
is expecting?
Thank you very much for the quick reply @chrisbonifacio ! I appreciate it!
Yes. I’ve tried having the cookieBasedClient in a utils files that’s imported into the route.ts
Thank you for clarifying that I don’t need this runWithAmplifyServerContext
I’ve set it up using this in the route.ts
try {
// This doesn't work. Fails with "No Credentials" error
const result = await cookieBasedClient.graphql({
query: createTransaction,
variables: {
transaction: transactionInput
},
})
…however it’s still failing with the “No Credentials” error.
// Cookie debug
const rawCookies = req.headers.get('cookie');
console.log('Raw Cookie String:', rawCookies);
const otherRawCookies = req.cookies.getAll();
console.log('Other Raw Cookies:', otherRawCookies);
console.log('cookies from next header = ', cookies().getAll());
This seems like a problem for something called “cookieBasedClient” lol.
Should the cookies in the next/header be automagically set somehow? Maybe I’m missing a step to manually put the necessary credentials in there?
If I’m using “iam” as “defaultAuthMode” does that mean that it’s expecting an idToken and an accessToken from a user session? I thought it would be looking for IAM credentials, like for an IAM role not a specific user?
I think that AppSync is expecting a temporary security credential (access key, secret key, and the security token). Any idea how this could get into the cookies?
I read somewhere (…totally not GPT4…) that:
“With AWS Amplify set up, you don't usually need to worry about manually adding these credentials to your GraphQL requests. Amplify handles the fetching and refreshing of these temporary credentials and includes them in the API calls.”
Is that true?
Thank you!
I also tried following these doc and using just the const client = generateClient(); and not the cookieBasedClient, but it was the same "No Credentials" error.
Should you just be call this once on the client side? I've tried also calling it in the route.ts , but it still didn't solve:
Amplify.configure(amplifyConfig, {
ssr: true // required when using Amplify with Next.js - it make Amplify store the auth tokens in cookies
});
On day 5 still there were no credentials.
If I call the query from the route.ts file that is called from the Stripe webhook all the cookies are empty and graphql errors with “No Credentials”.
However!
If I call the query from a route.ts called directly from the client I DO have all kinds of Cognito credentials in the cookies from next/header. … however the graphql call still errors saying “No Credentials”.
For example the cookies include things like:
{ name: 'CognitoIdentityServiceProvider.[very-long-string].accessToken', value: ‘very’-long-string, path: '/' },
(also there's the idToken, refreshToken etc).
If there’s a minor problem with the credentials would the cookieBasedClient still error with “No Credentials”?
The IAM role for authenticated users does have permissions to access both appsync graphql and the dynamo db table.
Thanks!
Hi @CooperCodeComposer Could you verify a few things for us to investigate?
cookie
header contain the auth tokensIf the cookie header is correct, then it must be that something went wrong on the server side.
If I call the query from the route.ts file that is called from the Stripe webhook all the cookies are empty and graphql errors with “No Credentials”.
The stripe webhook may create closure that disturbs Next.js's cookies()
function to run in a correct context.
Hi @HuiSF
I’ve tried to isolate the problem by making the request inside a normal api route.ts file that the client call. Instead of the web hook route.ts.
With the webhook the whole route.ts is running on the server so none of the debug shows up in Chrome (I’m triggering the hook with the Stripe CLI). I have no idea how you’d get auth creds inside that black box in order to log the transaction with graphql?
Here is a full list of the cookies sent when I trigger it inside a normal api route.ts request. It still fails with “No Credentials”. (I’ve shortened some of the long strings)
CognitoIdentityServiceProvider.6f3uavvmetc.LastAuthUser=1cf94651-dc69-495b-9734-8d09d1146a41; CognitoIdentityServiceProvider.6f3uavvmetc.accessToken=dGltZSI6MT etc… ; CognitoIdentityServiceProvider.6f3uavvmetc.idToken=eyJraW etc…; CognitoIdentityServiceProvider.6f3uavvmetc.refreshToken=eyJjd etc…; CognitoIdentityServiceProvider.6f3uavvmetc.deviceKey=us-east etc…; CognitoIdentityServiceProvider.6f3uavvmetc.deviceGroupKey=-Bw etc…; CognitoIdentityServiceProvider.6f3uavvmetc.randomPasswordKey=Sa etc…; CognitoIdentityServiceProvider.6f3uavvmetc.signInDetails={%2 etc…; CognitoIdentityServiceProvider.6f3uavvmetc.clockDrift=-1275; __stripe_sid=235 etc…
Alas this is not enough to satisfy the beast’s insatiable lust for credentials!!! Isn’t it normally just idToken and accessToken required? AppSync is setup with AWS_IAM as the primary auth mode.
It’s like following a shoe string through a swimming pool of spaghetti. But the AWS console is showing that the IAM role (I think it’s using) has these permissions:
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "mobileanalytics:PutEvents", "cognito-sync:", "cognito-identity:", "appsync:GraphQL" ], "Effect": "Allow", "Resource": [ "" ] }, { "Effect": "Allow", "Action": [ "dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:UpdateItem", "dynamodb:DeleteItem", "dynamodb:Query", "dynamodb:Scan" ], "Resource": [ "" ] } ] }
Sorry I’m not sure exactly what you mean by “Could you request your implementation here?” Here’s how I’m calling graphql now:
try { const result = await cookieBasedClient.graphql({ query: createTransaction, variables: { transaction: transactionInput }, }) console.log("createTransaction result transactionId = ", result.data.createTransaction?.transactionId);
} catch (error) { console.log("createTransaction failed with error: ", error);
}
Thank you for you help!
It's day 8. I have realized that I wasn't passing in the identityPoolId to the Cognito Auth amplifyConfig. It's not erroring with "No Credentials", but it's still not working. I get this "Unknown error" now:
get transaction failed with error: {
data: {},
errors: [
UnauthorizedException: Unknown error
at buildRestApiServiceError (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-rest/dist/esm/utils/serviceError.mjs:17:26)
at parseRestApiServiceError (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-rest/dist/esm/utils/serviceError.mjs:30:26)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async job (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-rest/dist/esm/utils/createCancellableOperation.mjs:31:23)
at async GraphQLAPIClass._graphql (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-graphql/dist/esm/internals/InternalGraphQLAPI.mjs:234:44)
at async runWithAmplifyServerContext (webpack-internal:///(rsc)/./node_modules/aws-amplify/dist/esm/adapterCore/runWithAmplifyServerContext.mjs:23:24)
at async GET (webpack-internal:///(rsc)/./app/api/price/route.ts:57:24)
at async /Users/acooper/Documents/DOCS Work/DEV WebProjects/projectname/front-end/node_modules/next/dist/compiled/next-server/app-route.runtime.dev.js:6:63251 {
recoverySuggestion: `If you're calling an Amplify-generated API, make sure to set the "authMode" in generateClient({ authMode: '...' }) to the backend authorization rule's auth provider ('apiKey', 'userPool', 'iam', 'oidc', 'lambda')`
}
]
}
So I modified the cookie client to specifically tell it to use "iam" (even though that's meant to be the default):
try {
const result = await cookieBasedClient.graphql({
query: getTransaction,
variables: { transactionId: "1234" },
authMode: 'iam'
});
console.log("getTransaction result transactionId = ", result.data.getTransaction?.transactionId);
} catch (error) {
console.log("get transaction failed with error: ", error);
}
I also explicitly tell getTransaction to use "iam" (although I'm not sure if this is needed?):
type Query {
getCognitoUser: CognitoUser
getTransaction(transactionId: String!): Transaction @aws_iam
listTransactions: [Transaction]
}
I've tested my queries in Lambda + Appsync and they're working ok. Cognito just won't let me sleep.
Thx
Hi @CooperCodeComposer Apologies I'm a bit lost with your set up. So you are using IAM role to authentication, does that mean this GraphQL API call is not tight to a specific user session (i.e. doesn't depend on the auth tokens for a specific user)?
@CooperCodeComposer, can you also share your schema please? Curious how your authorization rules are set up.
Thank you! @cwomack @HuiSF
I think I’m getting a lot closer. I’ve made a successful graphql query by switching to using “userPool” as the auth method everywhere.
I think that IAM auth is maybe just for authorizing access from AWS service to AWS service?
This was how my user sign up/in calls were successfully working. But I think that’s because I never directly called the graphql API within the app to do this. I think that’s AWS -> AWS access. ??
Goals
(As mentioned) within that stripe web hook the cookies are always null. I’m not sure that it’s possible for that server side webhook to ever get user pool credential?
Do you think I should change the queries within the web hook to be accessed via “API_KEY” and pass that in as an environment variable?
There’s this documentation that talks about modifying the schema to specify different access levels: https://aws.amazon.com/blogs/mobile/graphql-security-appsync-amplify/
So I tried this:
type Query {
getCognitoUser: CognitoUser
getTransaction(transactionId: String!): Transaction @aws_iam
listTransactions: [Transaction]
}
type Transaction {
transactionId: String!
userId: String!
amount: Float!
currency: String!
timestamp: String!
status: String!
paymentIntentId: String!
priceId: String!
}
etc...
I’m using $pulumi up It seems like it did make changes when I made that change to the schema.
But it seems like the client really responds to the authMode passed to it. Like this:
try { const result = await cookieBasedClient.graphql({ query: getTransaction, variables: { transactionId: "1234" }, authMode: 'userPool' // 'iam' }); console.log("getTransaction result transactionId = ", result.data.getTransaction?.transactionId);
} catch (error) { console.log("get transaction failed with error: ", error); }
…at no point in the setup have I run $amplify add auth …it’s confusing as to what pulumi has setup and whether I should run $amplify add auth ? to setup an unauth IAM role? ? Although In IAM there are amplify auth and unauth roles for the app... but the only have access to Cognito? Pulumi has setup a auth role for the app with all the bells and whistles access.
I’ve read this doc, but I can’t get any of the syntax to work in my schema. Is this the way? https://docs.amplify.aws/nextjs/build-a-backend/graphqlapi/customize-authorization-rules/
Let’s say I try this in my schema:
type Transaction @model @auth(rules: [{ allow: public }]) { transactionId: String! userId: String! amount: Float! currency: String! timestamp: String! status: String! paymentIntentId: String! priceId: String! }
I get this on $pulumi up
error: aws:appsync:GraphQLApi (appsync): error: 1 error occurred:
...it's dev-ops rocket science trying to debug why and where any of this is going wrong based on these errors.
“When you run amplify add auth, the Amplify CLI generates scoped down IAM policies for the "Unauthenticated role" in Cognito identity pool automatically.”
This makes me think you could use IAM for unauthenticated users? Although that web hook route.ts still wouldn’t have any cookies so I don’t see the graphql client working using iam or the userPools.
I’m going to try setting authMode to api keys for that call in the web hook to see if that can work.
For ref here’s how my config looks right now:
const amplifyConfig: ResourcesConfig = {
Auth: {
Cognito: {
userPoolClientId: config.USER_POOL_CLIENT_ID,
userPoolId: config.USER_POOL_ID,
identityPoolId: config.IDENTITY_POOL_ID,
loginWith: { // Optional
oauth: {
domain: 'https://myapp.auth.us-east-1.amazoncognito.com',
scopes: ['email', 'openid', 'aws.cognito.signin.user.admin'],
redirectSignIn: [config.REDIRECT_SIGN_IN],
redirectSignOut: [config.REDIRECT_SIGN_OUT],
responseType: 'code',
},
username: true, // note: username is their email
email: false, // Optional
phone: false, // Optional
}
}
},
API: {
GraphQL: {
endpoint: config.GRAPHQL_ENDPOINT,
defaultAuthMode: 'userPool',
region: config.REGION, // Optional
}
},
};
export default amplifyConfig;
Thank you for you help!
Hey @CooperCodeComposer, thanks for providing more details. From your description, your use case for making the query doesn't rely on a user session that's created on the client side. In this case, could you try the following?
generateServerClientUsingReqRes
to create the GQL client, for example in src/amplifyUtils.ts
import { createServerRunner } from '@aws-amplify/adapter-nextjs';
import { generateServerClientUsingReqRes } from '@aws-amplify/adapter-nextjs/api';
import amplifyConfig from '@/amplifyconfiguration.json';
export const { runWithAmplifyServerContext } = createServerRunner({
config: amplifyConfig
});
export const reqResBasedClient = generateServerClientUsingReqRes({
config: amplifyConfig
});
runWithAmplifyServerContext
and reqResBasedClient
to make the query calltry {
const result = await runWithAmplifyServerContext({
nextServerContext: null, // <-- NOTE here, see the explantation below
operation: (contextSpec) => {
return reqResBasedClient.graphql(
contextSpec,
{
query: getTransaction,
variables: { transactionId: "1234" },
authMode: 'iam',
}
);
}
});
console.log("getTransaction result transactionId = ",
result.data.getTransaction?.transactionId);
} catch (error) {
console.log("get transaction failed with error: ", error);
}
Note that in the above code example, we are setting nextServerContext
as null. This specifies that this server context is not meant to correlate with a user session that's sent from the client. So this context will attempt to get authentication details from the IAM provider. Similar to ApiKey, if you are using it, you should do the same.
Thanks @HuiSF
With that code I'm getting the "No Credentials" error.
If my AWS AppSync API says it's using IAM for auth what else do I need to verify to debug if there's an issue with the IAM setup? It feels like something is wrong in IAM because all my calls on the graphql client have always failed when trying to use that.
I would have thought that if the graphql client is has an endpoint set isn't it internally checking within AWS that AppSync has IAM attached and then saying "cool you have credentials". But ney. It's saying "No Credentials".
There aren't any settings with in the AppSync console (that I can see) to even tweak the IAM settings.
I've also tried for a while using and apiKey auth. It doesn't seem like a great approach. + that too was failing.
Not sure why I can't seem to specify access levels individually in my schema file as the AWS docs seem to suggest? (as mentioned it errors when I $pulumi up).
Thanks!
I checked the resources created by Amplify CLI, and if I specify using IAM as an authentication provider for my AppSync endpoint, it generates corresponding IAM roles and policies for unauthenticated and authenticated use cases (details see this document). Since you are using a third-party tool to set up the resource, it may be worth checking if everything is set up correctly.
There aren't any settings with in the AppSync console (that I can see) to even tweak the IAM settings.
If you go to AppSync console, choose settings
from the left nav bar, you can view authorization provider settings on the page. By looking the resource configuration you provided, I'd expect to see something similar to this:
Default authorization mode
is Amazon Cognito User PoolHi there, i have the same problem.
I solved by the moment using apiKey, but if you find any solution, please share.
I test from client-side and server-side, and i get errors like: "No Credentials: Credentials should not be empty", "'Not Authorized to access....".
Hi @jojemapa how did you set up the Amplify resource? Using a 3rd-party tool, too, or Amplify CLI?
Hi, i use Amplkify CLI.
amplify add auth amplidy add api
in my schema i just put allow: private and provider: iam.
The last part amplify add the iam permission to appsync in the auth role.
mmm, i finally i get that error, i still searching for a fix.
Thanks @jojemapa, were you able to make a mutation/query successfully using IAM
on the client side at all?
@jojemapa Thanks for the info. I've also only been able to get it to work using the api key. It's tough! I'm setting up a support group for developers with IAM issues. The only problem....you need IAM credentials to enter. Nooooooooooooooo! jk ;^)
If I use the api key method, I can't individually set queries to use user pools. + IAM hasn't worked for anything. I think I'm stuck rotating api keys like it's 1692 for a while.
Hi there guys, I've test again in a "new simple test app" using Nextjs.
I've configure the Auth and API using CLI.
Here some screenshots of my commands :
amplify add auth
amplify add api
This is my schema:
type Todo @model @auth(rules: [{ allow: private, provider: iam }]) {
id: ID!
content: String!
}
This is my code from client-side:
try {
const client = generateClient(); // By default is authMode is IAM
const result = await client.graphql({
query: mutations.createTodo,
variables: {
input: {
content: "Content",
},
},
});
console.log(result.data);
} catch (error) {
console.log("Error :(", error);
}
In client-side its works OK. The problem is server-side.
And this is my code from server-side:
Attempt 1: Usuing generateServerClientUsingCookies
const client = generateServerClientUsingCookies({
config: config,
cookies,
authMode: "iam",
});
const result = await client.graphql({
query: mutations.createTodo,
variables: {
input: {
content: "Content",
},
},
authMode: "iam",
});
The error is:
{
data: {},
errors: [
UnauthorizedException: Unknown error
at buildRestApiError (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-rest/dist/esm/utils/serviceError.mjs:87:26)
at parseRestApiServiceError (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-rest/dist/esm/utils/serviceError.mjs:30:16)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async job (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-rest/dist/esm/utils/createCancellableOperation.mjs:31:23)
at async GraphQLAPIClass._graphql (webpack-internal:///(rsc)/./node_modules/@aws-amplify/api-graphql/dist/esm/internals/InternalGraphQLAPI.mjs:234:44)
at async runWithAmplifyServerContext (webpack-internal:///(rsc)/./node_modules/aws-amplify/dist/esm/adapterCore/runWithAmplifyServerContext.mjs:23:24)
at async test (webpack-internal:///(rsc)/./app/data.ts:19:20)
at async Page (webpack-internal:///(rsc)/./app/cosa/page.tsx:15:18) {
recoverySuggestion: `If you're calling an Amplify-generated API, make sure to set the "authMode" in generateClient({ authMode: '...' }) to the backend authorization rule's auth provider ('apiKey', 'userPool', 'iam', 'oidc', 'lambda')`
}
]
}
Attempt 2: Using generateServerClientUsingReqRes
const reqResBasedClient = generateServerClientUsingReqRes({
config: config,
authMode: "iam",
});
const { runWithAmplifyServerContext } = createServerRunner({
config: config,
});
const result = await runWithAmplifyServerContext({
nextServerContext: null,
operation: async (contextSpec) => {
const request = await reqResBasedClient.graphql(contextSpec, {
query: mutations.createTodo,
variables: {
input: {
content: "Content",
},
},
authMode: "iam",
});
return request.data.createTodo;
},
});
The error is:
NotAuthorizedException: Unauthenticated access is not supported for this identity pool.
Attempt 3: Using generateServerClientUsingCookies + runWithAmplifyServerContext
const result = await runWithAmplifyServerContext({
nextServerContext: { cookies },
operation: async (contextSpec) => {
const request = await cookiesBasedClient.graphql({
query: mutations.createTodo,
variables: {
input: {
content: "Content",
},
},
authMode: "iam",
});
return request.data.createTodo;
},
});
Same error as the Attempt 1.
Another test using just userPool like the following works OK in client and server side:
type PrivateTodo @model @auth(rules: [{ allow: private }]) {} type AdminsTodo @model @auth(rules: [{ allow: groups, groups: ["Admin"] }]) {}
I think that the problem is using IAM provider in server-side
Thanks @jojemapa for providing the set up. I will run some tests and dig, will get back to you.
Based on the testing with the stack that @jojemapa provided, this is a bug that using iam
auth mode is not working as expected on the server side. We are currently working on a fix.
We have a tagged release that you can use to test whether it resolves your issue. Please note these are not meant for production, only for verification.
Install these packages and versions in your project and let us know if it fixes the issue for you.
npm i aws-amplify@6.0.16-iam-auth-server.ebba1a4.0 @aws-amplify/adapter-nextjs@1.0.16-
iam-auth-server.ebba1a4.0
Yes, it works nice @chrisbonifacio . Thanks.
I've test that in server-side and it works OK.
const client = generateServerClientUsingCookies({
config: config,
cookies,
// authMode: "iam", Default is configured to IAM
});
const mutationTest = await client.graphql({
query: mutations.createTodo,
variables: {
input: {
content: "Content",
},
},
// authMode: "iam", Default is configured to IAM
});
const queryTest = await client.graphql({
query: queries.listTodo,
variables: {},
// authMode: "iam", Default is configured to IAM
});
Thank you everyone for reporting this issue, your patience is appreciated! Closing this issue as the fix was just released. Please upgrade to v6.0.17.
Before opening, please confirm:
JavaScript Framework
Next.js
Amplify APIs
Authentication, GraphQL API
Amplify Version
v6
Amplify Categories
api
Backend
Amplify CLI
Environment information
Describe the bug
I'm trying to setup a route.ts to listen for Stripe webhooks then make a graphql request to log details of the transaction in dynamo db. I've tried many different approaches following fragments of the Amplify documentation, although I can't find any docs that seem to follow this exact use case? Some of the docs refer to the old NextApiResponse as opposed to the NextResponse in the App Router approach.
It sounds like "cookies" from next/header is suppose to include the auth information, but how should this be passed in the graphql query? My cookies are always nil in this ssr route. I'm setting the "ssr" to true when I configure amplify in my layout.tsx.
Help greatly appreciated! Should it be possible to use Amplify with Next 14?
Expected behavior
The most basic functionality of a graphql query to work and be documented.
Reproduction steps
I can use the Stripe CLI tool to trigger the payment_intent.succeeded. My code executes but the graphql query fails with the error "No Credentials".
Code Snippet
Log output
aws-exports.js
No response
Manual configuration
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