-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Graphql's custom-auth lambda function is not invoked. #9513
Comments
Hi @ImDhanush 👋 can you try setting the authMode of your graphql query to "AWS_LAMBDA"? example: const modules = await API.graphql({
query: listModules,
authMode: "AWS_LAMBDA",
authToken: `{jwt}`
}) Let me know if this helps |
Hi @chrisbonifacio According to this doc. The auth token size must not exceed 2048 characters. |
You mentioned it only works if the authorization header is set to Also, can you share the |
I just tried hardcoding a string on my custom lambda, didn't include Lambda logic // This is sample code. Please update this to suite your schema
exports.handler = async (event) => {
console.log(`event >`, JSON.stringify(event, null, 2));
const {
authorizationToken,
requestContext: { apiId, accountId },
} = event;
const response = {
isAuthorized: authorizationToken === "test123",
resolverContext: {
userid: "test user",
info: "contextual information A",
more_info: "contextual information B",
},
deniedFields: [
`arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`,
`Mutation.createEvent`,
],
ttlOverride: 300,
};
console.log(`response >`, JSON.stringify(response, null, 2));
return response;
}; Unauthorized API.graphql({
query: listTodos,
authMode: "AWS_LAMBDA",
authToken: "Bearer test123",
}) Authorized API.graphql({
query: listTodos,
authMode: "AWS_LAMBDA",
authToken: "test123",
}) Are you sure that the logic that verifies the jwt and determines whether |
Yes it only works when authorization header is set to The response looks like this {
"isAuthorized": true,
"deniedFields": [],
"ttlOverride": 2,
"resolverContext": {
"app": "name"
}
}
I tried sending |
what is the logic that returned I did try with a real jwt (took one from Auth0) and I did not need to include authorizationToken === "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImlXMDg3Ml8tc1hFbVA5MGEwNXdpZSJ9.eyJpc3MiOiJodHRwczovL2Rldi03dzdxNWZsNy51cy5hdXRoMC5jb20vIiwic3ViIjoiTE9iM0duRERzS2hrNzBEcFQyZTBiWXd3NHZQdWR5VlZAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vYXdzLmFtYXpvbi5jb20iLCJpYXQiOjE2NDMyMTQwMTAsImV4cCI6MTY0MzMwMDQxMCwiYXpwIjoiTE9iM0duRERzS2hrNzBEcFQyZTBiWXd3NHZQdWR5VlYiLCJndHkiOiJjbGllbnQtY3JlZGVudGlhbHMifQ.ZQfwZJot8QSgpdpudW_ogSBz1P_ssCPkCdM6WwDFoewuE99XN9LcEN64xNs0zm9uES5hrGczYwjNJXZlyCzk5NzOpM-boAKbX1qUhQrojVuj_pxWLkVReEltRC9DtojgzqntT3cpfJsr5xDxMso_Gg1NGMCHqGbCLdGJoO87X07OXp5j1ey-LTukym5sStS_vF2VUiJY0kFgNYcRG8uWgof6F6rAbzSiqNTgW_V3UxlV9s9fCb6i5BjZEst81I5xQFOpIxd7ZKfcOSUVcmYMqW4thsu5lI5y96HGnzmGT7kTRWUI1Yzj_Xy1GZWIOSmcd1a69U0jjvBWClkZ1Xuj-A", How are you verifying the token? |
Am passing the token which i get from cognito after login and verify it using the keys from |
So, the 401 "You are not authorized to make this call" error is normal for when the lambda returns However, the graphql error saying you're not authorized to access the This is the logic in my lambda authorizer for comparison // This is sample code. Please update this to suite your schema
const { CognitoJwtVerifier } = require("aws-jwt-verify");
exports.handler = async (event) => {
console.log(`event >`, JSON.stringify(event, null, 2));
const {
authorizationToken,
requestContext: { apiId, accountId },
} = event;
let isAuthorized = false;
const verifier = CognitoJwtVerifier.create({
userPoolId: "us-east-1_UrraAvHrw",
tokenUse: "access",
clientId: "7gb622j3ir3oatedu8fodci6ev",
});
try {
const payload = await verifier.verify(authorizationToken);
console.log("Token is valid. Payload: ", payload);
isAuthorized = true;
} catch (error) {
console.log("Token not valid");
}
const response = {
isAuthorized,
resolverContext: {
userid: "test user",
info: "contextual information A",
more_info: "contextual information B",
},
deniedFields: [
`arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`,
`Mutation.createEvent`,
],
ttlOverride: 300,
};
console.log(`response >`, JSON.stringify(response, null, 2));
return response;
}; Cloudwatch logs I've reached out to the team to investigate this further. Might be something I'm just missing or unaware of here. |
So, after digging into this further. It seems you were right - the requests without So, I'm not quite sure why a jwt without But, I also found that not including an auth token in your So, Amplify wouldn't have automatically made the request with its usual auth header anyway. So, I don't think this is an issue or bug, at least not with the JS library. You'll have to adjust your lambda to remove the My lambda logic const { CognitoJwtVerifier } = require("aws-jwt-verify");
exports.handler = async (event) => {
console.log(`event >`, JSON.stringify(event, null, 2));
const {
authorizationToken,
requestContext: { apiId, accountId },
} = event;
let isAuthorized = false;
const verifier = CognitoJwtVerifier.create({
userPoolId: "us-east-1_UrraAvHrw",
tokenUse: "access",
clientId: "7gb622j3ir3oatedu8fodci6ev",
});
// have to include "Bearer" in the auth header for the lambda to be invoked, but we'll remove it here to verify the token
const jwt = authorizationToken.replace(/^Bearer\s/, "");
try {
const payload = await verifier.verify(jwt);
console.log("Token is valid. Payload: ", payload);
isAuthorized = true;
} catch (error) {
console.log("Token not valid");
}
const response = {
isAuthorized,
resolverContext: {
userid: "test user",
info: "contextual information A",
more_info: "contextual information B",
},
deniedFields: [
`arn:aws:appsync:${process.env.AWS_REGION}:${accountId}:apis/${apiId}/types/Event/fields/comments`,
`Mutation.createEvent`,
],
ttlOverride: 300,
};
console.log(`response >`, JSON.stringify(response, null, 2));
return response;
}; Client-side API GraphQL query const res = await API.graphql({
query: listTodos,
authMode: "AWS_LAMBDA",
authToken: `Bearer ${(await Auth.currentSession())
.getAccessToken()
.getJwtToken()}`,
}); 🙌 |
Hey @chrisbonifacio type Module @model @auth(rules: [{ allow: custom }]) {
id: ID!
name: String
}
type Permission
@model
@auth(
rules: [
{ allow: groups, groups: ["enterprise-admins"] }
{ allow: private, operations: [read] }
]
) {
id: ID!
name: String
module: Module @hasOne
create: Boolean
read: Boolean
update: Boolean
delete: Boolean
} For the above schema i'll make a nested query like this query{
listPermissions{
items{
id
name
module{
id
name
}
}
}
} As both models have different authorization rules. output{
"data": {
"listPermissions": {
"items": [
{
"id": "d7cb9f3b-eb62-4fc6-a0a7-80426142c4cd",
"name": "editors_permission",
"module": null
}
]
}
},
"errors": [
{
"path": [
"listPermissions",
"items",
0,
"module"
],
"data": null,
"errorType": "Unauthorized",
"errorInfo": null,
"locations": [
{
"line": 6,
"column": 5,
"sourceName": null
}
],
"message": "Not Authorized to access module on type Module"
}
]
} Now only The whole query fails. |
Hey @ImDhanush apologies for the delay. So, I spoke to the AppSync team and while this is not exactly a bug, because the service is working as intended, it is something that we should document better. When the service is authorizing credentials, all it knows is the auth types configured for an API and the credentials provided. It’ll check what the credentials look like against what an auth type expects to try to infer what auth type is in use. Because a cognito token is what was provided and a cognito provider is associated with the api, cognito auth is what was inferred. So, nesting two models with different auth modes, especially lambda as we saw in this case will result in a partial result and Unauthorized error. You will have to query for the records separately so that you can set the correct |
Hey, @chrisbonifacio thank you for this. |
Hey @ImDhanush sorry for the delay. Just to want to make sure I understand your use case. Is there a reason why you're using a Lambda to verify Cognito tokens instead of utilizing the If you need that much control over the authorization model, you could potentially manage all of the logic from within the lambda and set both models to For example, I changed my schema to this type Module @model @auth(rules: [{ allow: custom }]) {
id: ID!
name: String
}
type Permission @model @auth(rules: [{ allow: custom }]) {
id: ID!
name: String
module: Module @hasOne
} Using only the custom lambda to authorize the calls, I can verify that a cognito user is making the call, check what groups they belong to, and restrict what fields, queries, mutations, etc each user can access accordingly using the Lambda example: const { CognitoJwtVerifier } = require("aws-jwt-verify");
exports.handler = async (event) => {
console.log(`event >`, JSON.stringify(event, null, 2));
const {
authorizationToken,
requestContext: { apiId, accountId },
} = event;
let response = {
isAuthorized: false,
resolverContext: {
userid: "test user",
info: "contextual information A",
more_info: "contextual information B",
},
deniedFields: [
`Mutation.createPermission`,
`Mutation.updatePermission`,
`Mutation.deletePermission`,
],
ttlOverride: 0,
};
try {
const verifier = CognitoJwtVerifier.create({
userPoolId: "us-east-1_UrraAvHrw",
tokenUse: "access",
clientId: "7gb622j3ir3oatedu8fodci6ev",
});
const jwt = authorizationToken.replace(/^Bearer\s/, "");
const payload = await verifier.verify(jwt);
console.log("Token is valid. Payload: ", payload);
if (
payload["cognito:groups"] &&
payload["cognito:groups"].includes("enterprise-admins")
) {
response.deniedFields = []; // allow enterprise-admins group to perform any action
}
response.isAuthorized = true;
} catch (error) {
console.log("Token not valid");
}
console.log(`response >`, JSON.stringify(response, null, 2));
return response;
}; listPermissions ( valid Cognito access token ) createPermission ( user does not belong to any group) createPermission (user belongs to enterprise-admins group) Let me know if this helps |
Hey @chrisbonifacio , |
@ImDhanush scratch that, this workaround is actually for a separate issue, I confused the issue that was opened on the CLI repo to be the same as this one. The workaround for this particular issue is still the same as I mentioned - adding a prefix other than "Bearer" to the token and removing it in the lambda. And each request can still only have one type of auth mode, otherwise only partial results will be returned. I opened an internal service ticket for the AppSync team and they are going to add this to their documentation. Deleting my previous comment. Apologies for the confusion! |
@thegoliathgeek Apologies for the delayed response on this one. @chrisbonifacio worked with our AppSync team and we’ve made changes to our AppSync documentation to clarify the scenario and also added the workaround section - Circumventing SigV4 and OIDC token authorization limitations https://docs.aws.amazon.com/appsync/latest/devguide/security-authz.html#aws-lambda-authorization I'll go ahead and close this out, but feel free to open a new issue if you have further questions. |
@arundna Just a heads up: we had "Bearer" in front of our OIDC tokens for lambda auth for the last 2 months as a workaround but it randomly stopped working today and started to return the error "Not authorized to perform x on type Query". Changing the prefix to anything else caused it to start working again. Is "Bearer" a reserved word for something else now in AppSync or Amplify? I don't see this documented anywhere but Bearer no longer works for us. |
We've suddenly been having the same issue as JoshuaWarejko as of the last week, spent significant time trying to diagnose the cause of the problem across each of our environments and it has turned out to be this problem, seemingly due to something being silently changed within AppSync/Amplify? |
@JoshuaWarejko @mewtlu Hello and apologies for the delay. The AppSync team reached out to me to confirm that "Bearer" is indeed a reserved prefix for OIDC now as defined by the OIDC spec. |
@chrisbonifacio Thanks for the update, good to know. |
Before opening, please confirm:
JavaScript Framework
React
Amplify APIs
GraphQL API
Amplify Categories
auth
Environment information
Describe the bug
i have setup the custom authentication with amplify on a model
I have also setup a lambda for this ,for verification of the token.
Am getting my jwt from cognito.
It seems lambda (custom auth) is not trigged.
When the header is (this is the default auth header from amplify)
Authorization: {jwt}
But when i change header
Authorization: Bearer {jwt}
Lambda is triggered when auth header used as above with Bearer.
Why is not getting trigged in the first pattern?
Amplify lib is using this
Authorization: {jwt}
by default for network calls.This is a problem when we are using different types of auths like
This auth only work when auth header is
Authorization: {jwt}
Expected behavior
Custom auth lambda should be invoked for
Authorization: {jwt}
header type.It's only invoked for
Authorization: Bearer {jwt}
.Reproduction steps
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
The text was updated successfully, but these errors were encountered: