I am testing on localhost, and using module CORS
strange ... POST ajax call to get access_token ( using module node-oauth2-server ) succeed
but then trying to access resources, ajax calls are failing ( Origin ...not allowed by Access-Control-Allow-Origin)
I guess there is something wrong in my routing ...
var express = require('express'),
cors = require('cors'),
http = require('http'),
https = require('https'),
fs = require('fs'),
oauthserver = require('node-oauth2-server'),
categories = require('./models/category'),
products = require('./models/product');
var server_options = {
key: fs.readFileSync('/etc/ssl/self-signed/server.key'),
cert: fs.readFileSync('/etc/ssl/self-signed/server.crt')
};
var app = express();
//app.use(require('cors')());
app.use(require('browser-logger')());
app.use(cors()); // automatically supports pre-flighting
app.use(app.router);
app.configure(function() {
var oauth = oauthserver({
model: require('./models/oauth_mongodb'),
// See below for specification
grants: ['password', 'refresh_token'],
accessTokenLifetime: 2592000, // 30 days
debug: true
});
app.use(express.bodyParser()); // REQUIRED
app.use(oauth.handler());
app.use(oauth.errorHandler());
});
// categories
app.get('/categories/:id', categories.findById);
app.get('/categories', categories.findAll);
// products
app.get('/categories/:id/products', products.findByCategory);
app.get('/products/:id', products.findById);
app.get('/products', products.findAll);
https.createServer(server_options,app).listen(8000, 'node_ssl_server.local');
http.createServer(app).listen(3000, 'node_ssl_server.local');
Just a guess, but I think your ajax calls are probably originating from:
http://localhost:3000/
and pointing at
https://localhost:8000/
You may need to do a browser refresh to avoid the cross domain error.
Related
I'm working on a project where we currently use Cognito User pools for auth., but after some research we found that if we want more fine-grained access-control we should use an Identity pool instead.
The theory is simple : first we create an Identity Pool that uses the Cognito user pool as Auth provider. Then in API Gateway we set up our Lambda to use Authorizer: AWS_IAM. To access it, User now has to :
Sign in to User pool, which gives user a JWT Token.
Exchange that JWT Token with the Identity pool for temporary AWS Credentials.
Use those new credentials to sign API request to the protected Lambda.
Steps 1 and 2 work fine, with a test user we manage to get the JWT Token and successfully exchange it for AWS credentials. They look like this (modified for security reasons):
awsAccessKey: ASIAZFDXSW29NWI3QZ01
awsSecretKey: B+DrYdPMFGbDd1VRLSPV387uHT715zs7IsvdNnDk
awsSessionToken: IQoJb3JpZ2luX2VjEA8aCWV1LXdlc3QtMyJHMEUCIQC4kHasZrfnaMezJkcPtDD8YizZlKESas/a5N9juG/wIQIgShWaOIgIc4X9Xrtlc+wiGuSC1AQNncwoac2vFkpJ3gkqxAQIWBAAGgw2NTI5NTE0MDE0MDIiDDuTZ1aGOpVffl3+XCqhBDmjCS3+1vSsMqV1GxZ96WMoIoEC1DMffPrBhc+NnBf94eMOI4g03M5gAm3uKAVCBkKO713TsQMaf4GOqqNemFC8LcJpKNrEQb+c+kJqqf7VWeWxveuGuPdHl1dmD2/lIc8giY0+q4Wgtbgs6i0/gR5HzdPfantrElu+cRNrn/wIq4Akf+aARUm14XsIgq7/1fT9aKSHpTgrnTLHeXLKOyf/lZ947XdH71IHDZXBUdwdPikJP/Rikwill6RRTVw7kGNOoacagCmmK7CD6uh9h0OnoW3Qw5df+zX5Z8U7U55AyQfEyzeB7bW3KH65yJn6sopegxIIFfcG2CLIvtb5cZYImAz/4BdnppYpsrEgLPUTvRAXn6KUa5sXgc5Vd7tJeRo5qpYckrR2qfbebsU+0361BCYK2HxGJqsUyt1GVsEoAosxofpn/61mYJXqfeR0ifCAgL7OMOquvlaUVXhHmnhWnUSIOUQ+XtRc+DxUDjwn5RPD7QTwLHIat7d4BI4gZJPAcMT9gZrBVO/iN88lk5R0M5LBzFwd5jiUW46H/G755I4e5ZHaT1I37TY3tbcObIFGVVNz5iHDpK/NePTJevKTshe8cYxXczOQgos4J/RsNpqouO9qRgT9JDyXjU3Etyxqm9RzbLYgV3fl5WwZl5ofVmrBsy3adq+088qEz5b9cogPgDggA/nQaPv7nAZHT8u0ct/hw230pmXUDGCutjOML2G6ZYGOoUCy+BitAN0SZOYWlbZlYomIGKMNQuXjV4z+S9CEW8VunqW4Rgl7rTba6xbI0DdX9upYEczeln6pTl+2UPEDYf6usayFfMsGDvJXesqC5EOtWco1Z8tem/wDQIH7ZbioQHZ7UJDd5ntUAruFveY7sXmKsQbtah/RB5W5HLYy19hCmyGpYMnVXxR0FcNGImsweNcprtw9MmQqy2SUK9V6Rwn1yIE6svfAT3NVyzp9ILbP/qSQLGHNhm4CNd8+EJZZa9rcmCbQiQ+iBJ8FW+AmRSCC4LiB1dhuH1KsFo88DyNhYdVf3py8XV4CDR7l+UyuZMrIQsERwx9JzwVBjfv9COT948mvyGTY
The issue is the signing. Our Lambda is behind a CloudFront proxy + API Gateway. Requests to e.g john.dev.project.io are forwarded to the 'real' API origin at api.dev.project.io.
Using Postman and setting AWS Signature, the request doesn't work and gives following error :
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.\n\nThe Canonical String for this request should have been\n'................................................................................................................................................................................................................................................................'\n\nThe String-to-Sign should have been\n'............................................................................'\n
We found however, that by overriding the Host header to the real origin of the API, request now works fine :
So it seems that since the custom URL we use and the original API URL are different, signatures don't match. The problem is that by default browsers don't allow you to override Host header for security reasons, so our front-end signed requests always fail.
Maybe the proxy is also modifying other headers before forwarding to origin, which would also invalidate the signature from my understanding...
Any help appreciated in solving this issue!
I was facing a similar issue when trying to make a signed request to an API Gateway endpoint behind an Akamai proxy.
The trick to solve it was indeed to generate a request as if you were sending it directly to the API Gateway URL, sign that request using sigv4 and then send that signed request to the proxy endpoint instead.
I've put together a simple NodeJS code to exemplify how to do this:
const AWS = require("aws-sdk");
const { HttpRequest } = require("#aws-sdk/protocol-http");
const { SignatureV4 } = require("#aws-sdk/signature-v4");
const { NodeHttpHandler } = require("#aws-sdk/node-http-handler");
const { Sha256 } = require("#aws-crypto/sha256-browser");
const REGION = "ca-central-1";
const PROXY_DOMAIN = "proxy.domain.com";
const PROXY_PATH = "/proxypath";
const API_GATEWAY_DOMAIN = "API-ID.execute-api.ca-central-1.amazonaws.com";
const API_GATEWAY_PATH = "/apigateway/path";
const IDENTITY_ID = "{{identity-pool-region}}:{{identity-pool-id}}";
const POOL_REGION = "{{identity-pool-region}}";
const REQUEST_BODY = { test: "test" };
const METHOD = "POST";
const udpatedSignedRequestExample = async () => {
try {
const BODY = JSON.stringify(REQUEST_BODY);
const request = new HttpRequest({
body: BODY,
headers: {
"Content-Type": "application/json",
host: API_GATEWAY_DOMAIN,
},
hostname: API_GATEWAY_DOMAIN,
port: 443,
method: METHOD,
path: API_GATEWAY_PATH,
});
console.log("request", request);
const credentials = await getCredentials();
console.log(credentials);
const signedRequest = await signRequest(request, credentials);
console.log("signedRequest", signedRequest);
const updatedSignedRequest = updateRequest(signedRequest);
console.log("updatedSignedRequest", updatedSignedRequest);
const response = await makeSignedRequest(updatedSignedRequest);
console.log(response.statusCode + " " + response.body.statusMessage);
} catch (error) {
console.log(error);
}
};
const getCredentials = async () => {
var cognitoidentity = new AWS.CognitoIdentity({ region: POOL_REGION });
var params = {
IdentityId: IDENTITY_ID,
};
const response = await cognitoidentity
.getCredentialsForIdentity(params)
.promise();
return {
accessKeyId: response.Credentials.AccessKeyId,
secretAccessKey: response.Credentials.SecretKey,
sessionToken: response.Credentials.SessionToken,
expiration: response.Credentials.Expiration,
};
};
const signRequest = async (request, credentials) => {
const signer = new SignatureV4({
credentials: credentials,
region: REGION,
service: "execute-api",
sha256: Sha256,
});
const signedRequest = await signer.sign(request);
return signedRequest;
};
const updateRequest = (httpRequest) => {
httpRequest.hostname = PROXY_DOMAIN;
httpRequest.path = PROXY_PATH;
httpRequest.headers.host = PROXY_DOMAIN;
return httpRequest;
};
const makeSignedRequest = async (httpRequest) => {
const client = new NodeHttpHandler();
const { response } = await client.handle(httpRequest);
return response;
};
udpatedSignedRequestExample();
Hope that helps.
I am using XAMPP and opening a website hosted on PC desk from another PC in local network.
I am using Laravel Passport with CreateFreshApiToken middleware. When I am posting to my GraphQL API from PC "desk", everything works fine, but posting from another PC fails: no cookies are being sent.
Cookies are HTTP-Only. I am wondering if this is some cross domain issue, but I am using the same domain everywhere: http://desk and http://desk/graphql for my API.
Why are cookies not being sent?
Apollo Client setup
const httpLinkSecret = new HttpLink({
// You should use an absolute URL here
uri: "http://desk/graphql"
});
var csrfToken = window.csrfToken;
Vue.prototype.csrfToken = window.csrfToken;
const authMiddleware = new ApolloLink((operation, forward, error) => {
// add authorization to the headers
operation.setContext({
headers: {
"X-CSRF-TOKEN": csrfToken
}
});
return forward(operation);
});
const apolloClientSecret = new ApolloClient({
link: authMiddleware.concat(errorLink).concat(httpLinkSecret),
cache: new InMemoryCache(),
connectToDevTools: true
});
EDIT 1
I figured out that on other machines it works on Chrome (only Desktop, not mobile) but not in Edge.
EDIT 2
If I simply do
var xhr = new XMLHttpRequest();
xhr.open('POST', '/graphql', true);
xhr.onload = function () {
// Request finished. Do processing here.
};
xhr.send(null);
Then cookie header is being appended. Why does it not work with Apollo Client?
I am trying to get the authorization headers from localStorage inside my middleware. Unfortunately this doesn't work on the first page load, because it is server-rendered.
How could I fix this?
const cookieName = 'feathers-jwt';
import { ApolloClient, createNetworkInterface } from 'apollo-client';
import 'isomorphic-fetch';
const API_ENDPOINT = 'http://localhost:3000/graphql';
const networkInterface = createNetworkInterface({ uri: API_ENDPOINT });
networkInterface.use([{
applyMiddleware(req, next) {
if (!req.options.headers) {
req.options.headers = {}; // Create the header object if needed.
}
req.options.headers['authorization'] = window.localStorage.getItem(cookieName);
next();
}
}]);
const apolloClient = new ApolloClient({
networkInterface,
transportBatching: true
});
export default apolloClient;
source: http://dev.apollodata.com/core/network.html
As I understand it, when you're rendering on the server you don't have access to window and document. In apps that render on both the server and in the client, you need to build in a check to see where you are, and handle that accordingly.
You can use this snippet for the detection of where you are:
var canUseDOM = !!(
typeof window !== 'undefined' &&
window.document &&
window.document.createElement
)
Use it to check if you are running server-side or client-side. In your case I would do the following:
If you're server-side you can check the cookies in the HTTP request itself;
If you're client-side you can check your localStorage store instead.
Of course, you can always opt to server-side render your website as an anonymous not authorised user by default. But that would cause the front-end to blink in and out of authorised state and would be annoying for the user.
In your case, I'd try to find authorisation cookies from the actual cookies that are present in your HTTP request.
So I'm using express and express-http-proxy as a proxy to an API web server that requires basic authentication. Then in my app, I'll be issuing Ajax calls to these APIs. After some effort I got this working but I'm sure there's a better way out there, hence this post.
Initially I set up the express proxy as follows:
var express = require('express'),
app = express(),
proxy = require('express-http-proxy');
app.use('/apis', proxy("https://myserver", {
forwardPath: function(req, res) {
return "/apis" + require('url').parse(req.url).path;
}
}));
When calling a URL directly in the browser (not via Ajax), eg. https://myserver/apis/myapi.ashx, I would see the authentication dialog asking me for my credentials, and I could authenticate and see the result.
However, when accessing the same URL via an Ajax call in my app, I was not getting a popup. Why this difference of behavior?
So I decided I needed to add my own basic authentication middleware using request and basic-auth as follows:
var express = require('express'),
app = express(),
proxy = require('express-http-proxy'),
request = require('request'),
basicAuth = require('basic-auth');
var myAuth = function (req, res, next) {
function unauthorized(res) {
res.set('WWW-Authenticate', 'Basic realm=Rimes');
return res.sendStatus(401);
};
var user = basicAuth(req);
if (!user || !user.name || !user.pass) {
return unauthorized(res);
};
var connectUrl = 'https://'+user.name+':'+user.pass+'#myserver/apis/connect.ashx';
request.get(connectUrl, function(error, response, body) {
if (!error && response.statusCode == 200) {
return next();
} else {
return unauthorized(res);
}
});
};
app.use('/apis', proxy("https://myserver", {
forwardPath: function(req, res) {
return "/apis" + require('url').parse(req.url).path;
}
}));
This worked fine, showing me the authentication popup during the Ajax call.
The obvious disadvantage here is:
Credential verification for every API request, although there may be a way to cache valid credentials. But in its defence, this is only on the development environment.
So is there a better way of doing this? Would a different middleware package do a better job?
My app uses koa-router, and it mounts the router using koa-mount, as in:
var Router = require('koa-router');
var mount = require('koa-mount');
app = koa();
var router = new Router();
router.get('/foo', function *() { this.body = { success: true }));
app
.use(mount('/api', router.middleware()))
.use(RedisBoot)
;
The desired behavior is that a route that starts with api that isn't defined should give a 503 or something. Instead, the request falls through to the RedisBoot handler. I've tried adding additional rules at the start and end of router but for some reason they do not seem to be being called.
I notice that newer versions of koa-router supported nested routes and have some other nice features, so maybe it would be easier to get this working now without koa-mount?
Instead of mounting the router directly to the parent app, create a secondary koa app (they're not heavyweight constructs so it shouldn't be a performance issue).
In this sub-app, add the router as the first middleware, add a catch-all handler as the second middleware, then mount the sub-app to the main app under /api.
var Router = require('koa-router');
var mount = require('koa-mount');
var app = koa();
var subApp = koa();
var router = new Router();
router.get('/foo', function *() { this.body = { success: true }));
subApp
.use(router.middleware())
.use(function *() { ... throw 503 or something... })
;
app
.use(mount('/api', subApp))
.use(RedisBoot)
;