Closed johnf closed 9 months ago
Also I think this error message is confusing.
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')`
as it can mean authMode isn't set or permission is simply denied
Hey there, @johnf 👋. I'm not sure what the shape of your config
looks like that's being imported, but can you check if the region/endpoint look proper and the value for the defaultAuthMode
is set to 'iam'
within that file? And just to confirm, did you set up all your backend resources via the CLI?
@johnf, after looking at this a little more... have an additional question or two. The code snippet you have in the config
looks valid and you can assign a default Auth mode at the client level. But are you trying to use an existing IAM role that had been set up prior to this project? How did you configure the IAM role associated with this app?
We'll support IAM roles that are vended from Cognito Identity Pools, but not any manually configured/arbitrary IAM roles.
@cwomack I wrote the below before reading your last sentence :)
I'm trying to use a manually configured IAM role. My use case is SSR and server compoents for a nexst.js app.
(NOTE: I think I've just understood what generateServerClientUsingCookies
and guessing it gets the browser to pass the creds through using cookies. However, that would require some major changes to the auth model for my app so I wanted to handle it all with backend smarts for now)
Is there any reason that amplify doesn't support IAM roles, it would mean the library could more easily be used in lambda, and this complicated part of the docs could be removed https://docs.amplify.aws/react/build-a-backend/graphqlapi/connect-from-server-runtime/#iam-authorization
Also, possibly dumb question, if iam
is using the cognito pool, how is it different from userPool
?
Below are two scripts, the first manually signs the request and works, the second uses amplify and doesn't work.
I'm calling the scripts like this
AWS_PROFILE=work ./manual.ts
AWS_PROFILE=work ./amplify.ts
The AWS profile, in my case, is SSO using wrapaws2, but I've also tested by explicitly setting AWS_ACCESS_KEY_ID etc
I've added the relevant snippets of my amplifyconfigration.json below as well. This was built using amplify G1.
From my code tracing it looks like amplify.Auth.fetchAuthSession();
is called when iam
is set and generates a noauth role from the cognito pool.
I've grepped through the amplify code base and I can't find anything in api-graphql or api-rest that tries to sign the HTTP requests with a v4 signature. I might be missing something though.
#!/usr/bin/env bun
import config from '@/amplifyconfiguration.json';
import { GraphQLClient } from 'graphql-request';
import { Sha256 } from '@aws-crypto/sha256-js';
import { defaultProvider } from '@aws-sdk/credential-provider-node';
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { HttpRequest } from '@aws-sdk/protocol-http';
const GRAPHQL_ENDPOINT = config.aws_appsync_graphqlEndpoint;
const AWS_REGION = config.aws_appsync_region;
export const signedFetch = async (url: RequestInfo | URL, options: RequestInit = {}) => {
const endpoint = url instanceof URL ? url : new URL(typeof url === 'string' ? url : url.url);
const signer = new SignatureV4({
credentials: defaultProvider(),
region: AWS_REGION,
service: 'appsync',
sha256: Sha256,
});
const headers = {
...options.headers,
'Content-Type': 'application/json',
host: endpoint.host,
} as Record<string, string>;
const requestToBeSigned = new HttpRequest({
method: options.method,
headers,
hostname: endpoint.host,
body: options.body,
path: endpoint.pathname,
});
const signed = await signer.sign(requestToBeSigned);
return fetch(url, { ...options, ...signed });
};
const client = new GraphQLClient(GRAPHQL_ENDPOINT, { fetch: signedFetch } as ConstructorParameters<
typeof GraphQLClient
>[1]);
const graphql = async ({ query, variables }: { query: string; variables: Record<string, any> }) => {
const response = await client.request(query, variables);
return response;
};
export const getUserByHandle = /* GraphQL */ `
query UserByHandle($handle: String!) {
userByHandle(handle: $handle) {
items {
id
}
}
}
`;
const test = async () => {
const response = await graphql({
query: getUserByHandle,
variables: { handle: 'johnf' },
});
console.debug(JSON.stringify(response, null, 2));
};
test();
#!/usr/bin/env bun
import { Amplify } from 'aws-amplify';
import { generateClient } from 'aws-amplify/api';
import config from '@/amplifyconfiguration.json';
Amplify.configure(config);
const client = generateClient({ authMode: 'iam' });
export const getUserByHandle = /* GraphQL */ `
query UserByHandle($handle: String!) {
userByHandle(handle: $handle) {
items {
id
}
}
}
`;
const test = async () => {
const response = await client.graphql({
query: getUserByHandle,
variables: { handle: 'johnf' },
});
console.debug(response);
};
test();
{
"aws_project_region": "ap-southeast-2",
"aws_appsync_graphqlEndpoint": "https://XXX.appsync-api.ap-southeast-2.amazonaws.com/graphql",
"aws_appsync_region": "ap-southeast-2",
"aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
"aws_cognito_identity_pool_id": "ap-southeast-2:XXX",
"aws_cognito_region": "ap-southeast-2",
"aws_user_pools_id": "ap-southeast-2_XXX",
}
Hi @johnf apologies for the delay. It looks like you're missing the ssr: true
config option when calling Amplify.configure
. Have you tried changing it to the following?
Amplify.configure(config, { ssr: true })
Also, you can set the authMode
in the request of the cookie based client itself.
const response = await graphql({
query: getUserByHandle,
variables: { handle: 'johnf' },
authMode: 'iam'
});
@chrisbonifacio I posted the wrong code above for the amplify code example above. I've fixed that now.
So I finally have it solved but let me provide some feedback.
The docs should give more detail on the differences between iam
vs userpools
.
Something like
iam
- Mention that it has nothing to do with AWS_PROFILE and how other SDKs use IAM, but that it will autogenerate credentials that make use of the noauthRoleuserPools
- uses cognito to create credentials which are then mapped to the authRoleThe SSR docs should say something to the effect that the backend is supposed to use the SSR Cookie provider so that it runs in the same context as the user. (I was treating it like my backend lambda functions which was most of why I went down this rabbit hole)
Is there a good way to mix unauthenticated users and authenticated users? e.g. in a next.js app, think of a twitter clone. I want unauthenticated users to be able to read a users tweets, but need to be logged in for DMs.
I think right now the answer is that you need to use different functions for noauth vs auth users and set the authMode to iam vs userPool.
It would be great if amplify auto fell back to iam if there were no userpool credentials in the cookie. Right now it just sends a request with no auth headers at all.
+1 on falling back to IAM if there are no userpool credentials
The original issue here seems to do with using public iam as an auth mode on the server side. We identified there was an issue in the library causing such requests to fail.
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
Updating this issue to indicate that it's a bug, not a setup issue or question. Fix is linked above, and until this is merged/released please refer to @chrisbonifacio comment above.
@chrisbonifacio When I try using the fixed modules I get
Import trace for requested module:
../../src/models/User.ts
./src/app/profiles/[handle]/page.tsx
⨯ ../../src/core/graphql.web.ts:1:0
Module not found: Can't resolve '@aws-amplify/adapter-nextjs/api'
> 1 | import { generateServerClientUsingCookies } from '@aws-amplify/adapter-nextjs/api';
2 |
3 | // import '@/core/amplify';
4 | import config from '@/amplifyconfiguration.json';
https://nextjs.org/docs/messages/module-not-found
Hi @johnf I realized the way I provided the install commands was broken because there was a newline and that might've prevented the second package, @aws-amplify/adapter-nextjs, from getting installed correctly.
This is the correct formatting, try this and confirm that it is installed. You might have to use --force
if you run into a ERESOLVE overriding peer dependency
error.
npm i aws-amplify@6.0.16-iam-auth-server.ebba1a4.0 @aws-amplify/adapter-nextjs@1.0.16-iam-auth-server.ebba1a4.0
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.
@chrisbonifacio I am still getting this issue with v6.0.20 and adapter v1.0.20
Before opening, please confirm:
JavaScript Framework
Next.js
Amplify APIs
GraphQL API
Amplify Version
v6
Amplify Categories
api
Backend
Amplify CLI
Environment information
Describe the bug
I've kicked off a new next.js app to use an existing amplify backend I have.
According to the documentation at https://docs.amplify.aws/react/build-a-backend/graphqlapi/connect-to-api/ it reads like IAM is a supported authentication mechanism.
However I get the following error
I've traced the code paths and from what I can tell when authMode is set to IAM, it simply creates credentials from the userpool. It never looks at AWS_PROFILE or even AWS_ACCESS_KEY_ID
Is IAM actually supported?
Expected behavior
Request completes successfully
Reproduction steps
As per documentation at https://docs.amplify.aws/react/build-a-backend/graphqlapi/connect-to-api/
Code Snippet
Log output
aws-exports.js
No response
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