This has me stumped.
I have a cognito pool with a resource server, one custom scope and one app client (clientid and secret).
I have my (pre-request) script set up in Postman like this
const clientId = pm.environment.get("cognitoClientId");
const clientSecret = pm.environment.get("cognitoClientSecret");
const grantType = "client_credentials";
const tokenUrl = pm.environment.get("cognitoAuthTokenURL");
const postRequest = {
url: tokenUrl,
method: 'POST',
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: {
mode: 'urlencoded',
urlencoded: [
{
key: "grant_type",
value: grantType
},
{
key: "client_id",
value: clientId
},
{
key: "client_secret",
value: clientSecret
},
{
key: "scope",
value: "myResourceServer/myResource.list"
}
]
}
};
pm.sendRequest(
postRequest
,(error, response) => {
console.log("Callback fired");
console.log(error);
console.log(response);
}
);
It fails on the response body with the following error:
{"error":"invalid_grant"}
From the AWS Documentation I get
Refresh token has been revoked
Which makes no sense as I am not requesting a refresh token
What am I doing wrong here, or where should I be looking?
Related
5.1.1 Legal: Privacy - Data Collection and Storage
Apps that offer Sign in with Apple should use the Sign in with Apple REST API to revoke user tokens.
REST API to revoke user tokens doc https://developer.apple.com/documentation/sign_in_with_apple/generate_and_validate_tokens/
I am getting "invalid client" or "invalid_grant" in the error response.
const client_secret = await getClientSecret();
const token = await authToken(authCode,client_secret);
await revokeToken(refreshToken, client_secret);
async function authToken(authCode, client_secret) {
const data = querystring.stringify({
code: authCode,
client_id: "****",
client_secret: client_secret,
grant_type: "authorization_code",
});
var config = {
method: "post",
url: "https://appleid.apple.com/auth/token",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
};
try {
const response = await axios.post(config.url, data, {
headers: config.headers,
});
return response.data.refresh_token
} catch (err) {
return err.response.data.error;
}
}
async function getClientSecret() {
const privateKey = fs.readFileSync("key/path");
return jwt.sign(
{
iss: "****",
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 360000,
aud: "https://appleid.apple.com",
sub: "****",
},
privateKey,
{
algorithm: "ES256",
header: {
alg: "ES256",
kid: "***",
},
}
);
}
I am following this guide for signing HTTP requests to an Amazon OpenSearch Service using Node.js (version 3 of the AWS SDK for JavaScript).
When I copy the exact sample code and export my AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY of my authorised user, the PUT index/type/id request to add an item is successful:
201 Created
Response body: {"_index":"products","_type":"_doc","_id":"2","_version":1,"result":"created","_shards":{"total":2,"successful":2,"failed":0},"_seq_no":0,"_primary_term":1}
However, when I change the request to instead GET /_search endpoint, I get:
403 Forbidden
Response body: {"message":"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."}
The user is fully authorised against the index:
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::**:user/aws-elasticbeanstalk-ec2-user"
},
"Action": "es:*",
"Resource": "arn:aws:es:ap-southeast-2:**:domain/mydomain/*"
},
How do I rectify my signature?
Here is my modified code from the above link:
const { HttpRequest } = require('#aws-sdk/protocol-http')
const { defaultProvider } = require('#aws-sdk/credential-provider-node')
const { SignatureV4 } = require('#aws-sdk/signature-v4')
const { NodeHttpHandler } = require('#aws-sdk/node-http-handler')
const { Sha256 } = require('#aws-crypto/sha256-browser')
const region = ''
const domain = ''
const index = 'products'
const type = '_search'
const createBody = (query) => ({
query: {
multi_match: {
query,
type: 'phrase',
fields: [
'tags',
'name',
'category',
'maker'
]
}
},
highlight: {
pre_tags: [''],
post_tags: [''],
fields: {
tags: {},
name: {},
category: {},
maker: {}
}
}
})
searchIndex('sh').then(() => process.exit())
async function searchIndex (query) {
const request = new HttpRequest({
body: JSON.stringify(createBody(query)),
headers: {
'Content-Type': 'application/json',
host: domain
},
hostname: domain,
method: 'GET',
path: index + '/' + type
})
const signer = new SignatureV4({
credentials: defaultProvider(),
region: region,
service: 'es',
sha256: Sha256
})
const signedRequest = await signer.sign(request)
const client = new NodeHttpHandler()
const { response } = await client.handle(signedRequest)
console.log(response.statusCode + ' ' + response.body.statusMessage)
let responseBody = ''
return new Promise((resolve) => {
response.body.on('data', (chunk) => {
responseBody += chunk
})
response.body.on('end', () => {
console.log('Response body: ' + responseBody)
resolve(responseBody)
})
}, (error) => {
console.log('Error: ' + error)
})
}
i also had this issue, using the same tutorial
reading the docs on request body searches, i found it states the following:
Note The _search API accepts HTTP GET and POST for request body
searches, but not all HTTP clients support adding a request body to a
GET request. POST is the more universal choice.
changing my method to POST solved the issue for me
I tried to automate my postman test. In first test I should get access token to execute the API call.
So I write pre test below. But I am getting 'There was an error in evaluating the Pre-request Script:Error: No data, empty input at 1:1 ^'. I tried commenting last section but it's not worked.
How should I automate this ?
const tokenUrl = 'https://xxxxxxxxxxxx/token';
const clientId = 'xxxxxxxxxxxxxxxx';
const clientSecret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
const getTokenRequest = {
method: 'POST',
url: tokenUrl,
body: {
mode: 'formdata',
formdata: [
{ key: 'grant_type', value: 'Client Credentials' },
{ key: 'client_id', value: clientId },
{ key: 'client_secret', value: clientSecret }
]
}
};
pm.sendRequest(getTokenRequest, (err, response) => {
const jsonResponse = response.json();
const newAccessToken = jsonResponse.access_token;
pm.variables.set('access_token', newAccessToken);
});
Hello i use Electron JS for a desktop app which is related to a cloud plateform from which in needto get a list of Patients.
As far as now i can get it but with a static AccessToken. I really struggled to get it dynamic, please help.
Here is my code :
This is my configuration file where i specify Cognito Parameters :
export default {
s3: {
REGION: 'YOUR_S3_UPLOADS_BUCKET_REGION',
BUCKET: 'YOUR_S3_UPLOADS_BUCKET_NAME',
},
apiGateway: {
REGION: 'YOUR_API_GATEWAY_REGION',
URL: 'YOUR_API_GATEWAY_URL',
},
cognito: {
REGION: 'eu-west-1',
USER_POOL_ID: 'eu-west-1_P0Jcr7nig',
APP_CLIENT_ID: '4m1utu56hjm835dshts9jg63ou',
IDENTITY_POOL_ID: 'YOUR_IDENTITY_POOL_ID',
authenticationFlowType: 'USER_PASSWORD_AUTH',
AUTHENTICATION_FLOW_TYPE: 'USER_PASSWORD_AUTH',
},
API: {
endpoints: [
{
name: 'PatientsList',
endpoint: 'https://uo992r7huf.execute-api.eu-west-1.amazonaws.com/Stage/patients',
//endpoint: 'https://uo992r7huf.execute-api.eu-west-1.amazonaws.com/Stage',
},
],
},
};
Auth.signIn({
username: process.env.username,
password: process.env.password,
}).then().catch(err => {
console.log(err)});
In another file this is my getaccesstoken function which i export to the main
function getAccessToken() {
const poolData = {
UserPoolId : COGNITO_USER_POOL_ID,
ClientId : COGNITO_CLIENT_ID,
};
const userPool = new CognitoUserPool(poolData);
var authenticationData = {
Username : process.env.username, // your username here
Password : process.env.password, // your password here,
authenticationFlowType: process.env.AUTHENTICATION_FLOW_TYPE,
Pool : userPool
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(
authenticationData);
var cognitoUser = new CognitoUser(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
},
onFailure: function(err) {
console.log(err);
},
});
}
And finally here is how i get the data in main :
The declarations :
const { Auth } = require('./cognitoAuth');
const theAccessToken = require('./cognitoAuth');
The code :
//Get Data From Cloud ECS
const API_URL = 'https://uo992r7huf.execute-api.eu-west-1.amazonaws.com/Stage/patients';
const headers = {
"Content-Type": "application/json",
//Authorization: theAccessToken.getAccessToken()
Authorization: "eyJraWQiOiJBbE1DZnBCTHYyVUlNazhXSG4xaTk4RG1QNlFmcFpSSjFaSW1qcVVFZnVBPSIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiI4OWYyZGMxZi1iMTI3LTQzM2QtODJhYS1iMjNkNWJhNzY5NGEiLCJjb2duaXRvOmdyb3VwcyI6WyJkb2N0b3IiXSwiZXZlbnRfaWQiOiI1OTM0ZmIwNC0yYTUzLTQ2NmQtYTU1Ni0zNTM3M2RhZmU1Y2UiLCJ0b2tlbl91c2UiOiJhY2Nlc3MiLCJzY29wZSI6ImF3cy5jb2duaXRvLnNpZ25pbi51c2VyLmFkbWluIiwiYXV0aF90aW1lIjoxNTk1NDI2NjQ2LCJpc3MiOiJodHRwczpcL1wvY29nbml0by1pZHAuZXUtd2VzdC0xLmFtYXpvbmF3cy5jb21cL2V1LXdlc3QtMV9QMEpjcjduaWciLCJleHAiOjE1OTcxNTUxMDUsImlhdCI6MTU5NzE1MTUwNSwianRpIjoiNGRkN2U5ZGUtYmQ2YS00NTg4LWIzZDAtMTVjMWM1NWQxY2Y2IiwiY2xpZW50X2lkIjoiNG0xdXR1NTZoam04MzVkc2h0czlqZzYzb3UiLCJ1c2VybmFtZSI6Ijg5ZjJkYzFmLWIxMjctNDMzZC04MmFhLWIyM2Q1YmE3Njk0YSJ9.LYvzPRBxvKw2P3gHwV8NhYPg_EB3F7ZK2F5HpRWHtBHksr6D4N5Fw56ZVupkRCxVJSq0f93DdljI7BBcBnp9d_hpLzmJLTfBhA3t870omTxalTqpGXN_SvsZmuwcfCX-awn1Um6x_-fhq3zcfPkB9FBljbtwPN-kvCc-Iynei9anVxXI686nIbkfbYfnuRnHrbY0vg8FtyDIDBMv277FPoJ96NwPD4mJvNBxQHi_KfWxQ1DmLiAC6c_l2jP_wawAPBv788CjD_8OlKBbjAHinGEkaL1K9vjI5MhNPyTA5ym1IaWar7Jr8RkUDzQGvqEUPKoOUe9PswmOOxLBjehMgQ"
};
//console.log('Token Value:', theAccessToken.getAccessToken());
const getPatients = async(API_URL) => {
try {
//get data from cloud specifiying get method and headers which contain token
const response = await fetch(API_URL,{
method: 'GET', headers: headers}
);
var listPatients = await response.json();
listPatients.items.forEach(patient => {
//Checking what i got
console.log(patient);
});
} catch(err) {
console.log("Error" + err);
}
};
getPatients(API_URL);
Now when i make it dynamic by specifying theAccessToken.getAccessToken
I get this error, USER_SRP is not enabled even if specify it, when i asked team told me the cloud service doesn't want to enable it.
So how can i get this access token please?
For a desktop app it is recommended to do these 2 things, according to security guidance:
Use Authorization Code Flow (PKCE)
Login via the system browser, so that the app never sees the user's password
I have a couple of Electron code samples that use Cognito, which you can easily run - maybe start here:
First desktop app
In a Lambda, I would like to sign my AppSync endpoint with aws-signature-v4 in order to use it for a mutation.
The URL generated seems to be ok but it gives me the following error when I try it:
{
"errors" : [ {
"errorType" : "InvalidSignatureException",
"message" : "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details. etc...
} ]
}
Here is my lambda function
import { Context, Callback } from 'aws-lambda';
import { GraphQLClient } from 'graphql-request';
const v4 = require('aws-signature-v4');
export async function handle(event: any, context: Context, callback: Callback) {
context.callbackWaitsForEmptyEventLoop = false;
const url = v4.createPresignedURL(
'POST',
'xxxxxxxxxxxxxxxxx.appsync-api.eu-west-1.amazonaws.com',
'/graphql',
'appsync',
'UNSIGNED-PAYLOAD',
{
key: 'yyyyyyyyyyyyyyyyyyyy',
secret: 'zzzzzzzzzzzzzzzzzzzzz',
region: 'eu-west-1'
}
);
const mutation = `{
FAKEviewProduct(title: "Inception") {
productId
}
}`;
const client = new GraphQLClient(url, {
headers: {
'Content-Type': 'application/graphql',
action: 'GetDataSource',
version: '2017-07-25'
}
});
try {
await client.request(mutation, { productId: 'jfsjfksldjfsdkjfsl' });
} catch (err) {
console.log(err);
callback(Error());
}
callback(null, {});
}
I got my key and secret by creating a new user and Allowing him appsync:GraphQL action.
What am I doing wrong?
This is how I trigger an AppSync mutation using by making a simple HTTP-request, using axios.
const AWS = require('aws-sdk');
const axios = require('axios');
exports.handler = async (event) => {
let result.data = await updateDb(event);
return result.data;
};
function updateDb({ owner, thingName, key }){
let req = new AWS.HttpRequest('https://xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com/graphql', 'eu-central-1');
req.method = 'POST';
req.headers.host = 'xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com';
req.headers['Content-Type'] = 'multipart/form-data';
req.body = JSON.stringify({
"query":"mutation ($input: UpdateUsersCamsInput!) { updateUsersCams(input: $input){ latestImage uid name } }",
"variables": {
"input": {
"uid": owner,
"name": thingName,
"latestImage": key
}
}
});
let signer = new AWS.Signers.V4(req, 'appsync', true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
return axios({
method: 'post',
url: 'https://xxxxxxxxxxx.appsync-api.eu-central-1.amazonaws.com/graphql',
data: req.body,
headers: req.headers
});
}
Make sure to give the IAM-role your Lambda function is running as, permissions for appsync:GraphQL.
Adding an answer here because I had difficulty getting the accepted answer to work and I found an issue on the AWS SDK GitHub issues that said it's not recommended to use the AWS.Signers.V4 object in production. This is how I got it to work using the popular aws4 npm module that is recommended later on in the issue linked above.
const axios = require('axios');
const aws4 = require('aws4');
const query = `
query Query {
todos {
id,
title
}
}`
const sigOptions = {
method: 'POST',
host: 'xxxxxxxxxx.appsync-api.eu-west.amazonaws.com',
region: 'eu-west-1',
path: 'graphql',
body: JSON.stringify({
query
}),
service: 'appsync'
};
const creds = {
// AWS access tokens
}
axios({
url: 'https://xxxxxxxxxx.appsync-api.eu-west/graphql',
method: 'post',
headers: aws4.sign(sigOptions, creds).headers,
data: {
query
}
}).then(res => res.data))
You don't need to construct a pre-signed URL to call an AWS AppSync endpoint. Set the authentication mode on the AppSync endpoint to AWS_IAM, grant permissions to your Lambda execution role, and then follow the steps in the "Building a JavaScript Client" tutorial to invoke a mutation or query.