I'm trying to know how to use cookies with AWS-Lambda with the serverless framework as per this blogpost
and following is my serverless.yml code
functions:
hello:
handler: handler.hello
events:
- http:
path: /post
method: post
cors:
origin : 'https://xyz.netlify.app'
and Lambda function as per following
"use strict";
const cookie = require("cookie");
module.exports.hello = async (event) => {
const body = JSON.parse(event.body);
const name = body.name;
const value = body.value;
return {
statusCode: 200,
headers: {
"Access-Control-Allow-Origin": "https://xyz.netlify.app",
"Access-Control-Allow-Credentials": true,
"Set-Cookie": cookie.serialize(name, value, {
expires: new Date(new Date().getTime() + 10 * 1000),
}),
},
body: JSON.stringify(
{
input: event,
},
null,
2
),
};
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
};
As you can notice, I already have configured the code to avoid any cors issue.
While try to send a post request as per following,
const name = document.getElementById('name')
const value = document.getElementById('value')
const post_btn = document.getElementById('post_btn')
post_btn.addEventListener('click', () => {
console.log(name.value, value.value)
const post_url = 'https://abcdxyz59t9.execute-api.ap-south-1.amazonaws.com/dev/post'
const user = {
name: name.value,
value: value.value
};
// request options
const options = {
method: 'POST',
body: JSON.stringify(user),
headers: {
'Content-Type': 'application/json'
}
}
// send POST request
fetch(post_url, options)
.then(res => res.json())
.then(res => console.log(res));
})
I do get a Set-Cookie header like below
But the cookie doesn't get saved in the browser.
That's not the case when I directly try to hit a get request with that URL without the cors in the browser. Can anyone please tell me what to do?
Related
It's my first question on Stackoverflow, normally I always found a solution here before Asking but not this time 😄
I use integration lambda mode, not the proxy mode.
I am able to set a cookie from serverless but I don't want to have the cookie in the body of my response.
I do this in the serverless.yml to set the cookie:
login:
handler: functions/auth/login.handler
events:
- http:
path: login
method: post
cors: true
integration: lambda
response:
headers:
Set-Cookie: integration.response.body.body
statusCodes:
200:
pattern: '' # Default response method
400:
pattern: '.*"statusCode": 400,.*'
template: $input.path("$.errorMessage")
401:
pattern: '.*"statusCode": 401,.*'
template: $input.path("$.errorMessage")
500:
pattern: '.*"statusCode": 500,.*'
template: $input.path("$.errorMessage")
here is the code of my lambda function:
/* login and return the jwt token infos and set the auth cookie */
import { ok, internalServerError, unAuthorized } from '../../lib/response.js';
import { login } from '../../lib/token.js';
import { logger } from '../../lib/logger.js';
import { authLambda } from '../../constants/constant.js';
export const handler = async (event) => {
logger.info('START', authLambda.login);
let responseObject;
let date = new Date();
date.setTime(+ date + (86400000)); // 24h
try {
const token = await login(event.body);
if (token) {
let cookie = 'auth='+token
responseObject = ok(cookie);
} else {
responseObject = unAuthorized();
}
logger.info('END => ', authLambda.login, responseObject);
return responseObject;
} catch (e) {
logger.error('ERROR', authLambda.login, e);
return new Error(internalServerError(e));
}
};
So, I have this as the response of the request:
{
"message": "Ok",
"statusCode": 200,
"body": "auth=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImJpbmN6YWttYXJ0aW40QGdtYWlsLmNvbSIsImlkIjoiOTUwNzI1YjAtODJlMy00Nzc0LWIwZDEtMWMxMTcyM2UzMTY3Iiwicm9sZSI6IkVuZ2luZWVyIiwiaWF0IjoxNjcwNDk0NjYzLCJleHAiOjE2NzA1ODEwNjN9.Y7X4AiVV84rCFLvuGTHbcyKMfZhpZuq3iWERzkHRZS0"
}
But I only want to have this:
{
"message": "Ok",
"statusCode": 200
}
Thanks 🙂
I implemented a twitter login authorizer and I put an API route as the callback.
The Lambda function evoked on that route is the following:
const loginTwitterCallback = async (e, context) => {
const fetch = (...args) =>
import("node-fetch").then(({ default: fetch }) => fetch(...args));
const state = e.queryStringParameters.state;
const code = e.queryStringParameters.code;
try {
await fetch(
"https://api.twitter.com/2/oauth2/token?code=" +
code +
"&grant_type=authorization_code&client_id=" +
process.env.TWITTER_CLIENT_ID +
"&code_verifier=jwqoijoiw&redirect_uri=" + MY REDIRECT URI,
{
method: "POST",
headers: {
"Content-type": "application/x-www-form-urlencoded",
},
}
)
.then((res) => {
return res.json();
})
.then(async (data) => {
const accessToken = data.access_token;
return {
headers: {
Location:
"http://127.0.0.1:3000/auth/social?type=twitter&access_token=" +
encodeURIComponent(accessToken),
},
body: null,
statusCode: 302,
};
});
} catch (err) {
console.log(err);
}
};
Basically the user should be re-routed to the front-end where another POST request will be made to the API which will make a request to the Twitter API with the Bearer token and update the database.
The point is, I'm not being redirected to the front-end in the first place and I don't understand how to fix it.
Thanks a lot in advance.
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.
I am using the aws-sdk to get a pre-signed url for S3. I have the function wrapped in a lambda.
const aws = require('aws-sdk');
module.exports = CreateRecord => {
CreateRecord.controllers.createSignature = (event, context, callback) => {
const s3 = new aws.S3({
signatureVersion: 'v4',
});
const params = {
Bucket: 'random-test-bucket002',
Key: 'test-key',
Expires: 100
};
s3.getSignedUrl('putObject', params, function(err, signedUrl) {
let response;
if (err) {
response = {
statusCode: 500,
headers: {
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
error: 'Did not receive signed url'
}),
};
} else {
response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Origin': '*', // Required for CORS support to work
},
body: JSON.stringify({
message: `Url successfully created`,
signedUrl,
})
};
}
callback(null, response);
});
};
};
This code works perfectly fine and I get back my pre-signed url. When I run this code on my front end:
postImage(uuid) {
const getSignature = 'https://xyz.execute-api.us-east-1.amazonaws.com/dev/v1/createSignature';
axios.get(getSignature)
.then(res => {
const signatureUrl = res.data.signedUrl;
// I have a blob that I store in file
// uuid is passed from another function
const file = new File([this.state.cover], uuid);
axios.post(signatureUrl, file)
.then(s3Res => {
console.log(s3Res);
});
});
}
The error I keep getting is: The request signature we calculated does not match the signature you provided. Check your key and signing method. I tried messing around with a few content-type headers but that did nothing. Can I pass the pre-signed url to a function in the aws-sdk? I've looked at a lot of posts on this but can't seem to resolve the issue.
When using pre-signed PutObject URLs for uploads to S3, you should upload files using the HTTP PUT method, rather than the HTTP POST method. You can POST objects to S3 but that's designed for browser-based uploads.
So just to be clear I have spent several hours googling things and none of these work. This is not a "low effort post".
This is an example of the code I have been trying. It doesn't work. Neither does doing response like this response.headers=[{Location:"foo"}] or response.headers=[{location:"foo"}] or the other eight ways I've tried it.
exports.handler = (event, context, callback) => {
if(request.uri === "/") {
var response = {
statusCode: 301,
headers: {
"location" : [{
key: "Location",
value: "foo"
}]
},
body: null
};
callback(null, response);
}
I've tried the following links:
http://blog.ryangreen.ca/2016/01/04/how-to-http-redirects-with-api-gateway-and-lambda/
http://blog.rowanudell.com/redirects-in-serverless/
https://kennbrodhagen.net/2016/04/02/how-to-return-302-using-api-gateway-lambda/
Python AWS Lambda 301 redirect
http://www.davekonopka.com/2016/serverless-aws-lambda-api-gateway.html
You mentioned the link to this example in your question; it should work with Lambda Proxy Integration:
'use strict';
exports.handler = function(event, context, callback) {
var response = {
statusCode: 301,
headers: {
"Location" : "http://example.com"
},
body: null
};
callback(null, response);
};
source: http://blog.ryangreen.ca/2016/01/04/how-to-http-redirects-with-api-gateway-and-lambda/
Update:
Else, try using this example from this page of example functions:
'use strict';
exports.handler = (event, context, callback) => {
/*
* Generate HTTP redirect response with 302 status code and Location header.
*/
const response = {
status: '302',
statusDescription: 'Found',
headers: {
location: [{
key: 'Location',
value: 'http://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html',
}],
},
};
callback(null, response);
};