I am not sure how to send signed http request do AppSync GraphQL endpoint. There is no library for do that in AWS.
aws-amplify don't work because works only in browser, not in Lambda function.
aws-sdk for AppSync is only for admin usage, it doesn't have methods for call user side api
It is possible to make IAM signed HTTP request from AWS Lambda? (in some easy way)
i would recommend reading this article: Backend GraphQL: How to trigger an AWS AppSync mutation from AWS Lambda,
quoting the author, https://stackoverflow.com/users/1313441/adrian-hall, we've:
GraphQL is routed over HTTPS. That means we can simulate the GraphQL client libraries with a simple HTTPS POST. Since we are using IAM, we need to sign the request before we deliver it. Here is my code for this:
// ... more code here
// POST the GraphQL mutation to AWS AppSync using a signed connection
const uri = URL.parse(env.GRAPHQL_API);
const httpRequest = new AWS.HttpRequest(uri.href, env.REGION);
httpRequest.headers.host = uri.host;
httpRequest.headers['Content-Type'] = 'application/json';
httpRequest.method = 'POST';
httpRequest.body = JSON.stringify(post_body);
AWS.config.credentials.get(err => {
const signer = new AWS.Signers.V4(httpRequest, "appsync", true);
signer.addAuthorization(AWS.config.credentials, AWS.util.date.getDate());
const options = {
method: httpRequest.method,
body: httpRequest.body,
headers: httpRequest.headers
};
fetch(uri.href, options)
// ... more code here
I've been using it as a template for all my Lambda->AppSync communication!
You can use any graphql client or a sigv4 signed HTTP request. Here's how you create the signature for your request (https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html). If you attach an execution role to your lambda you can access it access key from lambda environment variables (https://docs.aws.amazon.com/lambda/latest/dg/lambda-environment-variables.html).
This question is already answered but since it came up first for me I thought I'd share another solution.
My use-case was to send a signed request to custom HTTP API hosted on AWS where cognito was used as authentication backend that only had ALLOW_USER_SRP_AUTH enabled (so no ALLOW_ADMIN_USER_PASSWORD_AUTH nor ALLOW_USER_PASSWORD_AUTH)
I ended up combining this example from AWS showing how to do cognito authentication in node:
https://www.npmjs.com/package/amazon-cognito-identity-js (Use case 4)
With the other example from AWS showing how to sign request:
https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-node
You plug in the second example into the first example by replacing this line (from first example):
//(...)
//refreshes credentials using AWS.CognitoIdentity.getCredentialsForIdentity()
AWS.config.credentials.refresh(error => {
if (error) {
console.error(error);
} else {
// Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
console.log('Successfully logged!'); // <-- replace this line
}
});
//(...)
Second example needs some tweaks to fit your requirements, things I had to change was:
HTTP method (I needed GET)
signer declaration - I had to change service (replaced es with execute-api)
In signer.addAuthorization I had to use AWS.config.credentials (already initialized by the code from first example) instead of AWS.EnvironmentCredentials('AWS')
Hope this helps someone!
Related
I want to use kinesis video streams webrtc javascript sdk for producing video stream from a web page.
The sdk readme says i need to supply accessKeyId and secrectAccessKey
signalingClient = new KVSWebRTC.SignalingClient({
channelARN,
channelEndpoint: endpointsByProtocol.WSS,
clientId,
role: KVSWebRTC.Role.VIEWER,
region,
credentials: {
accessKeyId,
secretAccessKey,
},
systemClockOffset: kinesisVideoClient.config.systemClockOffset,
});
Is there a way to make this more secure and avoid supplying the secret access key inside the javascript code?
Doesn't it mean anyone viewing my web page source can take these credentials from the web page and use them to access the signaling channel?
Can I use amplify-js Auth class to use the signaling client with an authenticated user?
Turns out I can use credentials inside the backend, and send a presigned link to the client using the class SigV4RequestSigner.
There's no need to supply credentials on the client side.
Found it in the documentation:
This is a useful class to use in a NodeJS backend to sign requests and send them back to a client so that the client does not need to have AWS credentials.
When creating the SignalingClient you can either specify the credentials or a requestSigner that returns a Promise<string>, see:
https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-js/blob/master/README.md#class-signalingclient
credentials {object} Must be provided unless a requestSigner is provided.
Be aware that when not using credentials in the browser you will also need to run the KinesisVideoSignalingChannels related code on the server side, because this class does not supports request signer.
For Kinesis, one of the possibilities is to implement in your NodeJS backend a function for signing your URLs.
const endpointsByProtocol = getSignalingChannelEndpointResponse.ResourceEndpointList.reduce((endpoints, endpoint) => {
endpoints[endpoint.Protocol] = endpoint.ResourceEndpoint;
return endpoints;
}, {});
console.log('[VIEWER] Endpoints: ', endpointsByProtocol);
const region = "us-west-2";
const credentials = {
accessKeyId: "XAXAXAXAXAX",
secretAccessKey: "SECRETSECRET"
};
const queryParams = {
'X-Amz-ChannelARN': channelARN,
'X-Amz-ClientId': formValues.clientId
}
const signer = new SigV4RequestSigner(region, credentials);
const url = await signer.getSignedURL(endpointsByProtocol.WSS, queryParams);
console.log(url);
I made a simple IAM authenticated API that returns a random number. [GET only]
Postman call works ok:
https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-use-postman-to-call-api.html?shortFooter=true
What is a simple way of getting the postman call to plain javascript
(No npm or webpack)
Thanks heaps
You can use axios and aws4 library in javascript to make api calls and send a signed request respectively.You need to authenticate the user via Cognito and retrieve temporary credentials (access key, secret key, and session token)
let request = {
host: 'myapi.execute-api.us-west-2.amazonaws.com',
method: 'GET',
url: 'https://myapi.execute-api.us-west-2.amazonaws.com/foo/bar',
path: '/foo/bar'
}
let signedRequest = aws4.sign(request,
{
// assumes user has authenticated and we have called
// AWS.config.credentials.get to retrieve keys and
// session tokens
secretAccessKey: AWS.config.credentials.secretAccessKey,
accessKeyId: AWS.config.credentials.accessKeyId,
sessionToken: AWS.config.credentials.sessionToken
})
delete signedRequest.headers['Host']
delete signedRequest.headers['Content-Length']
let response = await axios(signedRequest)
This article might help you with the basic code to get temporary credentials from cognito and authenticate the user.
In client-side javascript, I set:
AWS.config.credentials = {
"accessKeyId": ak, // starts with "AKIA..."
"secretAccessKey": sk // something long and cryptic
};
Then eventually call
var lambda = new AWS.Lambda({apiVersion: '2015-03-31'});
var params = {
FunctionName: 'my-function-name',
InvokeArgs : my_data
};
lambda.invokeAsync(params, function(err, data) {
...
The HTML request seems to contain the correct access key:
authorization:AWS4-HMAC-SHA256 Credential=AKIA...
And in server-side node.js, I don't manually set any AWS credentials, with the understanding that setting them in the client-side is sufficient, as:
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
...
Following the request, the server's upload handler gets called as expected, but within that handler, s3.putObject() fails with an Access Denied error. Trying to debug this, I added console.log(AWS.config.credentials) to the upload handler, and Cloudwatch is showing:
accessKeyId: 'ASIA...
I don't recognize the accessKeyId that is shown, and it certainly doesn't match the one provided in the request header. Am I doing something wrong here, or is this expected behavior?
The Lambda function does not use the AWS credentials you used in your client-side JavaScript code. The credentials in your client-side code were used to issue a Lambda.invoke() command to the AWS API. In this context, the credentials you are using on the client-side only need the Lambda invoke permission.
Your Lambda function is then invoked by AWS Lambda service. The Lambda service will attach the IAM Execution Role to the invocation that you specified when you created/configured the Lambda function. That IAM Execution Role is what needs to have the appropriate S3 access.
I want to use Cognito Federated Entity (allowing signin through Google etc), to allow access to API Gateway for a web javascript application.
I managed to get the Cognito's sessionToken through signing-in with Google but I'm stuck on the API Gateway configuration for enabling the session token.
Is there a good tutorial for this entire Federated Entity authentication workflow?
Thanks!
Since you want to invoke APIs via authenticated Cognito identity, first
Amend the auth role of the identitypool to have api execute policy, you could just attach the managed policy "AmazonAPIGatewayInvokeFullAccess" to the respective role
In API gateway under respective method request, add Authorization as
"AWS_IAM"
You need to sign the request while using "IAM" auth, explained here https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html
Instead of #3, you could generate and download the SDK from the stage panel of your API gateway, and make a call to the api via sdk.
Once you obtain the cognito session, you could make a call using the sdk like below
var apigClient = apigClientFactory.newClient({
accessKey: AWSCognito.config.credentials.accessKeyId,
secretKey: AWSCognito.config.credentials.secretAccessKey,
sessionToken: AWSCognito.config.credentials.sessionToken
});
var params = {
// This is where any modeled request parameters should be added.
// The key is the parameter name, as it is defined in the API in API Gateway.
};
var body = {};
var additionalParams = {
// If there are any unmodeled query parameters or headers that must be
// sent with the request, add them here.
headers: {
'Content-Type': 'application/json'
},
queryParams: {}
};
apigClient.<resource><Method>(params, body, additionalParams)
.then(function(result) {
//
}).catch(function(err) {
//
});
Just need an example of how to call AWS Lambda from JavaScript running in a browser and display function result in JavaScript console. Incredibly, I cannot find any examples on Google or from AWS documentation.
My use case is that I have an HTML form. When the form is submitted, I want to use Lambda to process the form inputs. Assuming that the Lambda function finishes with no errors, I then want to take the user to a thank you page.
Please include a complete HTML example, not just a code snippet.
Since you need to run Lambda from the browser, you have two options you can achieve it.
Use AWS Javascript SDK, set it up with user via static configuration or Cognito with IAM Permissions to your Lambda. You can also consider subscribing your Lambda functions to SNS Topic and run the Lambda by sending a message to the topic. This SNS approach would also require you to store and retrieve the submission state via separate call.
Use AWS API Gateway to create RESTful endpoint with proper CORS configuration that you can ping from the browser using AJAX.
Both options have their pros and cons. More information about your use-case would be necessary to properly evaluate which one suits you best.
I see people have used AWS SDK for Javascript but it is not required specially since you need to create Amazon Cognito identity pool with access enabled for unauthenticated identities (Atleast for beginners like me). Below code works fine for me -
<html>
<head>
<script>
function callAwsLambdaFunction() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
document.getElementById("myDiv").innerHTML = this.responseText;
}
};
xhttp.open("GET", "https://test123.ap-south-1.amazonaws.com/dev", true);
xhttp.send();
}
</script>
<title>Hello World!</title>
</head>
<body>
<h1>Hello world!</h1>
<h1>Click below button to call API gatway and display result below!</h1>
<h1><div id="myDiv"></div></h1>
<button onclick="callAwsLambdaFunction()">Click me!</button><br>
Regards,<br/>
Aniket
</body>
</html>
Above is sample index.html that I have added to my S3 bucket and made a static site. Couple of points to note -
Make your index.html open from outside if you are using S3 for static site hosting.
Make sure you turn on CORS for your API gateway if your website domain is not same as API gateway domain. Else you might get -
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://test123.ap-south-1.amazonaws.com/dev. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
For people who see this after 2017, you can check out AWS Amplify API class. The sample code is taken from Amplify API document.
Note that
1) You have to use POST method to invoke lambda functions.
2) Make sure to add policy to invoke lambda permission for your unauthenticated(if needed) and authenticated roles.
3) User doesn't need to sign in to invoke the lambda if a permission policy is granted.
import Amplify, { API } from 'aws-amplify';
Amplify.configure({
Auth: {
// REQUIRED - Amazon Cognito Identity Pool ID
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX-1234-abcd-1234567890ab',
// REQUIRED - Amazon Cognito Region
region: 'XX-XXXX-X',
// OPTIONAL - Amazon Cognito User Pool ID
userPoolId: 'XX-XXXX-X_abcd1234',
// OPTIONAL - Amazon Cognito Web Client ID
userPoolWebClientId: 'XX-XXXX-X_abcd1234',
},
API: {
endpoints: [
{
name: "MyCustomLambdaApi",
endpoint: "https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/yourFuncName/invocations",
service: "lambda",
region: "us-east-1"
}
]
}
});
This is how you call your lambda function
let apiName = 'MyApiName'; // replace this with your api name.
let path = '/path'; //replace this with the path you have configured on your API
let myInit = {
body: {}, // replace this with attributes you need
headers: {} // OPTIONAL
}
API.post(apiName, path, myInit).then(response => {
// Add your code here
});
Invoke AWS Lambda Using No API Gateway || Using no AWS SDK
Assumption is that you've made your function url and made the necessary cross-origin settings. Both can be done on the configuration tab of the lambda function
Assumption is that the lambda is written in python, but called from javascript
1. Write the function
The payload is retrieved from the event variable
The format for the event variable is given by aws
def lambda_handler(event, context):
payload = event["rawQueryString"]
return {
"payload": payload
}
2. Use a simple fetch to your function URL
In the example, you want to send a name and password to the function
fetch('https://aws-lambda-function-url?name=jason&password=1234')
.then((response) => response.json())
.then((data) => console.log(data));
3. Result
{payload: 'name=jason&password=1234'}
I would use AWS SDK for Javascript, below the steps
Reference the js file
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.100.0.min.js"></script>
Initialize/Configure the SDK
AWS.config.update({region: 'REGION'});
AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: 'IdentityPool'});
Create the service lambda Object and so on...
you can see the next steps in this link
http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/browser-invoke-lambda-function-example.html