Cannot call aws API Gateway via ajax - ajax

I am using aws APi gateway and api gateway custom authorizer. The code that I have for api gateway custom authorizer is as follows:
console.log('Loading function');
exports.handler = (event, context, callback) => {
var token = event.authorizationToken;
// Call oauth provider, crack jwt token, etc.
// In this example, the token is treated as the status for simplicity.
switch (token.toLowerCase()) {
case 'allow':
callback(null, generatePolicy('user', 'Allow', event.methodArn));
break;
case 'deny':
callback(null, generatePolicy('user', 'Deny', event.methodArn));
break;
case 'unauthorized':
callback("Unauthorized"); // Return a 401 Unauthorized response
break;
default:
callback("Error: Invalid token");
}
};
var generatePolicy = function(principalId, effect, resource) {
var authResponse = {};
authResponse.principalId = principalId;
if (effect && resource) {
var policyDocument = {};
policyDocument.Version = '2012-10-17'; // default version
policyDocument.Statement = [];
var statementOne = {};
statementOne.Action = 'execute-api:Invoke'; // default action
statementOne.Effect = effect;
statementOne.Resource = resource;
policyDocument.Statement[0] = statementOne;
authResponse.policyDocument = policyDocument;
}
// Can optionally return a context object of your choosing.
authResponse.context = {};
authResponse.context.stringKey = "stringval";
authResponse.context.numberKey = 123;
authResponse.context.booleanKey = true;
return authResponse;
as you can see it is just a simple mock up example provided in aws website.
Then I configured my get method in API gateway using this authorizer. Also in method execution I added a custom hedear called authorizationToken which will be used by authorizer.
When I use the postman everything is good:
However when I try to call it via ajax as follows I get the following error:
XMLHttpRequest cannot load https://590vv3bkda.execute-api.us-east-1.amazonaws.com/hamedstg/tjresource/story. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 401.
Here is my ajax call:
$.ajax(
'https://590vv3bkda.execute-api.us-east-1.amazonaws.com/xxxxxxx',
{
method : 'GET',
headers : {
'authorizationToken' : 'allow'
},
beforeSend : function(xhr) {
xhr.setRequestHeader('authorizationToken', 'allow');
}
}).then(function(data) {
console.log(data);
});
Also it is noteworthy that I enabled CORS on the api in aws.
Can anyone help?

Did you add any methods or resources since enabling CORS? If so, then run the CORS wizard again and redeploy to your stage.
Also, make sure that the OPTIONS method on your resource does not require/use the customer authorizer. OPTIONS needs to be open to all as the browser will call it on your behalf for pre-flight CORS checks in some cases.
There is also a known issue that when an API Gateway call fails for any reason, the CORS headers are not set and thus you'll get that "No 'Access-Control-Allow-Origin' header is present" error, when the root cause is something entirely different. Try turning on developer logging on your browser, get the exact request sent to the API (it may be an OPTIONS method) and try the same request as a test invoke from the API Gateway console. That will let you look at the output and the logs to determine if there is another issue.

Related

How to integrate AWS API Gateway API Key with Axios | CORS Error

I have built an API in AWS API Gateway. I have written the endpoints to perform basic CRUD operations as well. I am making a call to those endpoints using axios from my React frontend. The APIs in turn call AWS Lambda functions to interact with DynamoDB.
Since DynamoDB contains sensitive user data, I wish to secure it with an API key.
As per the steps mentioned here and here.
Now in order to make an API call I had the following code. Please note that I have swapped in the real values with dummy values for explanation purposes.
src/config/api.js
const customHeaders = {
"X-Api-Key": "thisIsADummyStringForExplanation",
"Content-Type": "application/json",
};
const axiosInstance = axios.create({
baseURL: "https://this.is.a.dummy.base.url/v0",
headers: customHeaders,
});
const Aws_Api_Gateway_GET = (uri) => {
return axiosInstance({
method: "get",
url: `${uri}`,
timeout: 2000,
});
};
export { Aws_Api_Gateway_GET };
Following is the Code that I wrote in order to make a GET request at the API endpoint
Aws_Api_Gateway_GET("/my-resource")
.then((res) => {
console.log(res);
})
.catch((err) => {
console.error(err);
});
THE ISSUE
This code throws CORS Error. I can assure that I have enabled CORS on the API Gateway by selecting the Enable CORS option for each and every resource.
Following is the error
Access to XMLHttpRequest at 'https://this.is.a.dummy.base.url/v0/my-resource' from origin 'http://localhost:3000' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
But when I try the same using Postman, it works.
Can someone please help me get rid of the CORS Error ?
Thanks in advance.

Access to fetch at '' from origin '' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource

I have this api (method get) that is connected to a lambda function that does a simple select from a database, if i test the endpoint with postman with a null body it does work (if i understood, postman is not under the same CORS policy), as well as typing the endpoint on the browser.
But when i try to do a fetch from a simple js, i get the error :
Access to fetch at '...' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
I enabled CORS in API Gateway, both with the Enable CORS option
and with the Enable API Gateway CORS when creating a new resource
If i test my endpoint with gateway, i also get that Allow-content-allow-origin : * is in my response header :
What should i do to fix this problem?
here is the JS fetch :
console.log("pre fetch");
Show();
console.log("post fetch");
function Show(){
fetch("...").then(onResponse);//.then(onJson);
}
function onResponse(response){
console.log(response);
return response.json();
}
I removed the onJson to avoid confusion, but even with that in its the same problem.
Try to include that in your function too, like this,
I hope this would work:
const headers = {'Content-Type':'application/json',
'Access-Control-Allow-Origin':'*',
'Access-Control-Allow-Methods':'POST,PATCH,OPTIONS'}
const response = {
statusCode: 200,
headers:headers,
body: JSON.stringify(X),
};
return response;
Here X is the response that you want to return.
If you are using Node.js you needs to install cors.
npm install cors.
After installing cors, include it in the page where you are using fetch function as shown below;
const cors = require('cors');
app.use(cors());
and the error will be solved.
I made a video on how to fix this.
You need to go into the Lambda function and add special code:
original (does NOT work):
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
new one, that works:
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
headers: {
"Access-Control-Allow-Headers" : "Content-Type",
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "OPTIONS,POST,GET"
},
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
You can find this solution in here: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html
Only you need to replace the:
"Access-Control-Allow-Origin": "https://www.example.com",
with
"Access-Control-Allow-Origin": "*",
Special thanks to user, KnowledgeGainer
ALSO, you need to enable CORS on Gateway API side, just follow instruction from here: https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html

Adding multiple headers to graphql client (apollo-boost)

const client = new ApolloClient({
uri,
onError: (e: any) => {
console.log('error: ', e); // Failed to fetch
console.log(e.operation.getContext()); // it does show it has x-abc-id
},
request: operation => {
const headers: { [x: string]: string } = {};
const accessToken = AuthService.getUser()?.accessToken;
const activeClientId = UserService.getActiveClientId();
headers['x-abc-id'] = activeClientId;
if (accessToken) headers['Authorization'] = `Bearer ${accessToken}`;
operation.setContext({ headers });
}
});
The problem here is when i just add Authorization header it makes the POST call and shows the expected error.
But when i add x-abc-id header which is also expected by backend it only makes OPTIONS call (no post call)
P.S. On postman adding both headers works completely fine.
Found what the issue was, thought to share if it help.
Postman does not perform OPTIONS call before sending request to backend.
In OPTIONS call, 👇represents what client call contains: [authorization, content-type, x-abc-id]
BUT what does server expects: 👇
Just authorization, content-type
So it's a calls headers mismatch (nothing related to Apollo).
x-abc-id header explicitly has to be allowed in CORS configuration on backend.
Thanks to Pooria Atarzadeh

Calling rest server from mobile app

Following on from https://lists.hyperledger.org/g/composer/message/91
I have adapted the methodology described by Caroline Church in my IOS app.
Again I can authenticate with google but still get a 401 authorization error when POSTing.
I have added the withCredentials parameter to the http header in my POST request.
does the rest server pass back the token in cookie ? I don't receive anything back from the rest server.
where does the withCredentials get the credentials from ?
COMPOSER_PROVIDERS as follows
COMPOSER_PROVIDERS='{
"google": {
"provider": "google",
"module": "passport-google-oauth2",
"clientID": "93505970627.apps.googleusercontent.com",
"clientSecret": "",
"authPath": "/auth/google",
"callbackURL": "/auth/google/callback",
"scope": "https://www.googleapis.com/auth/plus.login",
"successRedirect": "myAuth://",
"failureRedirect": "/"
}
}'
the successRedirect points back to my App. After successfully authenticating I return to the App.
Got this working now. The App first authenticates with google then exchanges the authorization code with the rest server.
The Rest server COMPOSER_PROVIDERS needs to be changed to relate back to the app.
clientID is the apps ID in google,
callbackURL and successRedirect are reversed_clientID://
The App calls http://localhost:3000/auth/google/callback with the authorization code as a parameter.
this call will fail, but an access_token cookie is written back containing the access token required for the rest server.
The user id of the logged in user is not passed back, when exchanging the code for a token with google we get back a JWT with the details of the logged in user. We need this back from the rest server as well as the token. Is there any way to get this ?
changing the COMPOSER_PROVIDERS means that the explorer interface to the Rest server no longer works.
func getRestToken(code: String) {
let tokenURL = "http://localhost:3000/auth/google/callback?code=" + code
let url = URL(string:tokenURL);
var request = URLRequest(url: url!);
request.httpMethod = "GET";
request.setValue("localhost:3000", forHTTPHeaderField: "Host");
request.setValue("text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8", forHTTPHeaderField: "Accept");
request.setValue("1", forHTTPHeaderField: "Upgrade-Insecure-Requests");
request.httpShouldHandleCookies = true;
request.httpShouldUsePipelining = true;
let session = URLSession.init(configuration: .default);
session.configuration.httpCookieAcceptPolicy = .always;
session.configuration.httpShouldSetCookies=true;
session.configuration.httpCookieStorage = HTTPCookieStorage.shared;
let task = session.dataTask(with: request) { (data, response, error) in
var authCookie: HTTPCookie? = nil;
let sharedCookieStorage = HTTPCookieStorage.shared.cookies;
// test for access_token
for cookie in sharedCookieStorage! {
if cookie.name == "access_token"
{
print(“Received access token”)
}
}
guard error == nil else {
print("HTTP request failed \(error?.localizedDescription ?? "ERROR")")
return
}
guard let response = response as? HTTPURLResponse else {
print("Non-HTTP response")
return
}
guard let data = data else {
print("HTTP response data is empty")
return
}
if response.statusCode != 200 {
// server replied with an error
let responseText: String? = String(data: data, encoding: String.Encoding.utf8)
if response.statusCode == 401 {
// "401 Unauthorized" generally indicates there is an issue with the authorization
print("Error 401");
} else {
print("HTTP: \(response.statusCode), Response: \(responseText ?? "RESPONSE_TEXT")")
}
return
}
}
task.resume()
}
have you authorised the redirect URI in your Google OAUTH2 configuration ?
This determines where the API server redirects the user, after the user completes the authorization flow. The value must exactly match one of the redirect_uri values listed for your project in the API Console. Note that the http or https scheme, case, and trailing slash ('/') must all match.
This is an example of an Angular 5 successfully using it Angular 5, httpclient ignores set cookie in post in particular the answer at the bottom
Scope controls the set of resources and operations that an access token permits. During the access-token request, your application sends one or more values in the scope parameter.
see https://developers.google.com/identity/protocols/OAuth2
The withCredentials option is set, in order to create a cookie, to pass the authentication token, to the REST server.
Finally this resource may help you https://hackernoon.com/adding-oauth2-to-mobile-android-and-ios-clients-using-the-appauth-sdk-f8562f90ecff

SailsJS - using sails.io.js with JWT

I have implemented an AngularJS app, communicating with Sails backend through websockets, using sails.io.js.
Since the backend is basically a pure API and will be connected to from other apps as well, I'm trying to disable sessions completely and use JWT.
I have set up express-jwt and can use regular HTTP requests quite nicely, but when I send a request through sails.io.js, nothing happens at all - websocket request keeps pending on the client, and there's nothing happening on the server (with "silly" log level).
I've tried patching sails.io.js to support the query parameter, and when connecting, I send the token from Angular, but in the best case, I get a response with error message coming from express-jwt saying credentials are missing...
I've also seen some hints that socket.js in sails needs to be modified with beforeConnect, I've seen socketio-jwt, but have no idea where and how to plug that in, in Sails.
Has anyone implemented this and is using JWT with Sails and sockets? I'd appreciate any kind of hint in what direction to go :)
I realised that policy I've put in place and that was using express-jwt abstracted too much away from me, so I didn't figure out what exactly was happening. Once I looked at other examples, I've figured out that I only needed to check what's different for websocket requests than regular, and I quickly found a way around the problem.
So:
set up token signing and sending on login
Angular takes the token and saves to local storage
Create an interceptor for HTTP requests to add authorization header and token
Fix up sails.io.js to forward query parameters provided through options (as mentioned in the question)
When connecting using sails.io.js, send token as query parameter, i.e. url + '?token=' + token
In sails policy, check all combinations for token, including req.socket.handshake.query, as below:
module.exports = function (req, res, next) {
var token;
if (req.headers && req.headers.authorization) {
var parts = req.headers.authorization.split(' ');
if (parts.length == 2) {
var scheme = parts[0],
credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials;
}
} else {
return res.json(401, {err: 'Format is Authorization: Bearer [token]'});
}
} else if (req.param('token')) {
token = req.param('token');
// We delete the token from param to not mess with blueprints
delete req.query.token;
}
// If connection from socket
else if (req.socket && req.socket.handshake && req.socket.handshake.query && req.socket.handshake.query.token) {
token = req.socket.handshake.query.token;
} else {
sails.log(req.socket.handshake);
return res.json(401, {err: 'No Authorization header was found'});
}
JWTService.verifyToken(token, function (err, token) {
if (err) {
return res.json(401, {err: 'The token is not valid'});
}
sails.log('Token valid');
req.token = token;
return next();
});
};
It works well! :)

Resources