Call Play 2 REST API with AngularJS (CORS Problems) - ajax

I am developing an AngularJS application calling a REST API developed with Play Framework 2.2.0.
I have a problem related to Cross-domain ajax calls as the Angular application and the Play one will not be hosted on the same domain.
Here is the JS call in my Angular service :
$http
.post("http://localhost:9001/category/list", { langCode: 'fr-FR' })
.success(function(data, status, headers, config) {
callback(data.items);
})
.error(function(data, status, headers, config) {
console.log("Error Data : " + data);
console.log("Error Status : " + status);
});
Here is the route in my Play app :
POST /category/list controllers.catalog.ProductCategoryController.list()
If I don't send any data in the request, everything works fine
If I send data, I have Ajax errors concerning ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_HEADERS
The only workaround I have is the following :
Intercept all requests in Global class and add the headers
#Override
public Action onRequest(Request request, Method method) {
return new Action.Simple() {
#Override
public Promise<SimpleResult> call(Context ctx) throws Throwable {
Logger.debug("Intercepting request and applying CORS headers...");
ctx.response().setHeader(Controller.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
ctx.response().setHeader(Controller.ACCESS_CONTROL_ALLOW_HEADERS, "Content-Type");
return delegate.call(ctx);
}
};
}
Add another route with OPTIONS in routes
OPTIONS /category/list controllers.catalog.ProductCategoryController.list()
Is there a way of making the integration simpler than that ?

There's no CORS support out of the box in play; that's a situation I'd like to see changed, but for now you've identified a wart.
The good news is that you can manage a global workaround if you are OK having one CORS setting for all of your resources. It can be done in a couple of ways, one of which you identified. My inclination would be to go with a low level OPTIONS route.
Something like:
OPTIONS /*path controllers.Application.options()
From there, your handler definition can be something like:
Ok("").withHeaders(
"ACCESS_CONTROL_ALLOW_METHODS" -> "GET, POST, PUT, PATCH",
"ACCESS_CONTROL_ALLOW_HEADERS"->"Content-Type",
"ACCESS_CONTROL_ALLOW_ORIGIN" -> "*"
)
It's not super clean, but until Play adds something a bit more workable, I think it's your best option over making tons of OPTIONS routes (again, assuming you're comfortable with a global CORS setting)

You have to enable CORS support to your Play web server. The following url do have plenty of how-to for configurating server enabling the cross origin support:
http://enable-cors.org/server.html

Related

Cloudfront as a reverse proxy for backend

I have tried to put in place a CloudFront distribution that would forward requests using a CloudFront function to our external API GW (not the AWS one). However this creates an issue with CORS. I can make the CORS request working, however what I am trying to replace is a backend for frontend pattern that we have in place currently using an Apache server and single origin cookie.
function handler(event) {
var request = event.request;
var headers = request.headers;
var apigwurl = 'https://gatewayendpoint/'
if (request.uri.startsWith('/api')) {
request.uri = request.uri.replace('/gw/', '');
var response = {
statusCode: 302,
statusDescription: 'Found',
headers: {
"location": { "value": apigwurl+request.uri}
}
}
return response;
}
return request;
}
In essence what I am trying to do is replace in the most effective way the following rewrite function
RewriteRule ^/api/(.*)$ https://api.backend.com/$1 [P,L]
Once the first request is done, the single cookie is set and used for authentication purposes to make calls to the BFF layer (Backend for Frontend).
NGINX would be a good fit for a reverse proxy for backend and can be used as an API gateway.
I would suggest to use a solution/technology combination which is widely used. That way, you will not make your life difficult with rare technology combination and find better technical support and resources online.

How should I set net core api cors?

I am coding an unofficial twitter api for myself. Then I send a get to this api using the console screen in my browser with the following method.
function httpGet(theUrl)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.open( "GET", theUrl, false ); // false for synchronous request
xmlHttp.send( null );
return xmlHttp.responseText;
}
such
httpGet(https://localhost:44311/WeatherForecast/alienationxs/true);
the problem is that when i do this via www.google.com it's ok and json data reaches me. but when I do it via twitter.com I get the following error.
via google.com
my cors settings on api
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddCors(options =>
options.AddDefaultPolicy(builder =>
builder.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin())); ;
services.AddMvc();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "twitterAPI", Version = "v1" });
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "twitterAPI v1"));
}
app.UseRouting();
app.UseCors(builder => builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
all i want is to reach my api via twitter.com just like google.com.
First, let's separate the flies from the cutlets.
Cross-Origin Resource Sharing (CORS) - is a separate security layer.
Content Security Policy (CSP) - is a separate security layer, it's appied before CORS. After passing through CSP yous can face with CORS if last one is breached.
As you can see from error message "... because it violates the following Content Security Policy directive ...", you faced with CSP violation therefore your CORS settings have no mean.
What's goin on.
You enter twitter.com web page and tries to execute connect request to localhost:44311 on behalf of twitter web page. But twitter's web page protected by CSP which forbid such requests:
Pay attention on 'connect-src' directive, which governs XMLHttpRequest().
The www.google.com web page does not have CSP, therefore you request on behalf of google does success.
The Twitter API does not support CORS.

Invalid character returned in IE but not in Firefox and Chrome

I'm using fetch to return a JSON payload to a React SPA. My web server backend is ASP.NET Core 2.0. I recently updated to ASP.NET Core 2.0 and for the life of me can't figure out why IE no longer works with the web application.
The fetch is pretty straight forward.
fetch(getApiPath() + url, {
credentials: 'include'
})
.then(function(response){
if (response.status === 401 && history) {
history.push("/login")
throw new Error('Redirecting to login');
} else if (response.status === 200) {
return response.json();
} else {
throw new Error(response.statusText);
}
})
.then(success)
.catch(function(e) {
console.log(e)
});
The server end is also pretty straight forward.
[Authorize]
[Route("/dashboard")]
public object Index()
{
Log.Debug("Index");
return new { dashboard = _dashboard, authenticated = HttpContext.User.Identity.IsAuthenticated };
}
The problem manifests itself in a "Invalid Character" error in IE. This works fine in Chrome and Firefox. When looking at the response body, the IE response, is in fact an invalid character while in Chrome, it is the JSON payload.
I'm a little unsure where to even start looking into why IE wouldn't receive or parse the HTTP response correctly. Any ideas?
EDIT:
Making a cross-origin request from a Webpack Dev Server running on port 10000 to a local ASP.NET Core app running on 10001. When packaged for deployment, both the React App and the ASP.NET Core App run on 10000.
Headers between the two requests.
IE Request
IE Response
Chrome
Updated the endpoint to return an IActionResult and explicitly returning JSON. Same result. I've also realized it doesn't work in Edge either.
[Authorize]
[Route("/dashboard")]
public IActionResult Index()
{
return Json(
new { dashboard = _dashboard, authenticated = HttpContext.User.Identity.IsAuthenticated }
);
}
Without additional info I suspect the issue is related to ASP.Net's content negotiation and the fact your method return type is object. Don't use object, this is not Java :))
Before anything else, make sure fetch is sending an Accept: application/json header in IE.
I would also recommend for you to change the return type to IActionResult (or JSONResult if you want to force JSON) for your controller methods.

CORs error when accessing Square V2 API

I'm making a client-side request out to V2 of the Square API using Vue and Axios. My Vue component is as follows:
import axios from 'axios';
export default {
mounted() {
var instance = axios.create({
baseURL: 'https://connect.squareup.com/v2/',
timeout: 1000,
headers: {
'Authorization': 'Bearer xxxxxxxxxxxxxxxxx',
'Accepts': 'application/json',
'Content-Type': 'application/json'
}
});
instance.get('catalog/list')
.then(function (response) {
console.log(response);
}) ;
}
}
However, when I make that call, I receive the following error:
Failed to load https://connect.squareup.com/v2/catalog/list: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://local-env.dev' is therefore not allowed access. The response had HTTP status code 403.
That error suggests that there is some configuration that has to happen on the Square side, but I saw no opportunity to whitelist domains, etc.
Has anyone come across this error before, regardless of service, and if so, how did you resolve?
I don't think the Square API supports being called from a browser. I used Postman to do an OPTIONS request on https://connect.squareup.com/v2/catalog/list and the response was a NOT_FOUND. The OPTIONS request is needed for proper CORS support.
Plus, if you did this, I would think your auth token would need to be sent to the client -- thus exposing it to everyone. It looks like the Square API is only designed to be called from a server. But that is just based on me skimming the docs a bit. I have no experience using their API.
When doing OAuth authorization request you are not supposed to do it from your application. Create and URL with the parameters and open it in a new browser window or tab, Something like:
const grants='MERCHANT_PROFILE_READ CUSTOMERS_READ CUSTOMERS_WRITE PAYMENTS_READ PAYMENTS_WRITE PAYMENTS_WRITE_ADDITIONAL_RECIPIENTS PAYMENTS_WRITE_IN_PERSON';
const params = new HttpParams()
.set('scope', grants)
.set('client_id', <YourSquareApplicationId>)
.set('state', '1878789');
const requestUrl = `${<squareUrl>}/oauth2/authorize?${params.toString()}`;
window.open(requestUrl, "_blank");
That new window is supposed to ask the end user to login to his account and accept or deny the request.

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