How to handle CORS error with an angular app and a java backend? - ajax

I have an angular App that tries to send ajax requests to a java backend (API built with Jersey).
Here is the client request :
let settings = {
"url": "http://localhost:8080/simulator/config",
"method": "POST",
"timeout": 0,
"headers": {
"Content-Type": "application/json"
},
"data": JSON.stringify({
"fromDate": fromDate,
"tsIntervalTag": tsIntervalTag,
"tsIntervalDevice": tsIntervalDevice,
"devicePerMinute": devicePerMinute,
"tagPerMinute": tagPerMinute,
"quantityOfTags": quantityOfTags,
"quantityOfDevices": quantityOfDevices
}),
};
$.ajax(settings).done(function (response) {
console.log(response);
});
And here is the java backend request handler:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response postIt(String body) {
try {
//do stuff
return Response.ok(body, MediaType.APPLICATION_JSON).header("Access-Control-Allow-Origin", "*").build();
} catch (Exception e) {
return Response.serverError().entity(e).header("Access-Control-Allow-Origin", "*").build();
}
}
As you can see, as nearly all answers about CORS state, the Access-Control-Allow-Origin header is set to * wildcard, so from my understanding, the response should get back to the browser.
But the browser console returns the following:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://localhost:8080/simulator/config.
(Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://localhost:8080/simulator/config.
(Reason: CORS request did not succeed).
So what did I miss ?

It seems like with Jersey, it is better to use filters to set CORS. (At least in my scenario where all requests should be accepted from anywhere (so far)).
So I created the following class (in its own java file):
package com.example;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;
#Provider
public class CORSFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
System.out.println("FILTER HERE ");
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*"); // Allow Access from everywhere
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
headers.add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type");
}
}
And that's is basically it. All requests will go through this and get the correct headers. I do not know why setting the headers in method directly did not work though.

Related

two-legged oauth2 : how to call google drive rest API without specific API library

I have created an app in the Google Developer's Console, then created OAuth2 credentials. I have a client_id and client_secret. Now, I want to use these to obtain an access token for two-legged calls into the Google Drive API. I am using Google's oauth2 client in java:
import com.google.api.client.auth.oauth2.ClientCredentialsTokenRequest;
import com.google.api.client.auth.oauth2.ClientParametersAuthentication;
import com.google.api.client.auth.oauth2.TokenResponse;
...
public void oauth2Test() {
String clientId = "...";
String clientSecret = "...";
ClientCredentialsTokenRequest request = new ClientCredentialsTokenRequest(
new NetHttpTransport(),
new JacksonFactory(),
new GenericUrl("https://accounts.google.com/o/oauth2/auth"));
request.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret));
TokenResponse response;
try {
response = request.execute();
} catch (Exception ex) {
ex.printStackTrace();
}
}
However, I get a "400 Bad Request" with message
"Required parameter is missing: response_type".
What is the correct way to obtain an access token in the two-legged request model? Note: I only have the client_id and client_secret, I do not have the full API token.
EDIT: My original question was imprecise. While I prefer to start only with client_id and client_secret, that is not necessary. It is OK to use google-specific APIs to obtain access tokens and it is OK to use GoogleCredential. What is necessary is that I am able to use whatever access token(s) are obtained from the authorization process in a generic REST call. In other words, given google app credentials, which can be {client_id,client_secret}, or a google service account key in either JSON or P12 format, how do I obtain access token(s) and how are they used in the REST API call -- do I set the Authorization header or something else?
The first answer points out that client_credential isn't supported, which I've verified. But I still need a path to get the bearer token, so that I can use it in REST calls without specific Google client API libraries. So I started with code that works, but uses the Google libraries. It requires a JSON credential file.
InputStream is = getClass().getResourceAsStream("JSONCredFile");
GoogleCredential credential = GoogleCredential.fromStream(is).createScoped(scopes);
Drive service = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName("My app")
.build();
FileList result = service.files().list().setPageSize(10)
.setFields("nextPageToken, files(id, name)")
.execute();
By hooking up an SSLSocket proxy to the credential (details omitted), I was able to trace the outbound communication:
POST /token HTTP/1.1
Accept-Encoding: gzip
User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: oauth2.googleapis.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive
Content-Length: 771
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=<lots of encoded stuff>
The reply is a gzip-encoded bearer token, which is used in the API call:
GET /drive/v3/files?fields=nextPageToken,%20files(id,%20name)&pageSize=10 HTTP/1.1
Accept-Encoding: gzip
Authorization: Bearer ya29.c.Eln_BSgrx0afa85mdMstW5jzEvM5dotWpctSXl-DE1jeO2mmu1h0FErr_EZO05YnC-B1yz30IBwOyFXoWr_wwKxlZk08R6eZldNU-EAfrQ1yNftymn_Qqc_pfg
Clearly this is the JWT profile of oauth2. But now what? Somehow I need to get the bearer token without actually making the API call through the specific library. The Google OAuth2 libraries don't seem to support this request type, at least I don't see a "JWT" flavor of TokenRequest. I can cook up the OAuth2 call directly, or create a subclass of TokenRequest that supports JWT?
Any better ideas?
Google doesn't support grant_type=client_credentials which is how you'd do 2LO with an OAuth client ID and secret.
You can use service accounts to do 2LO: https://developers.google.com/identity/protocols/OAuth2ServiceAccount
OK, I finally figured out how to make the JWT, send the OAuth2 request, and extract the access token. For easier integration with the google OAuth2 client, I subclassed TokenRequest:
import com.google.api.client.auth.oauth2.TokenRequest;
import com.google.api.client.auth.oauth2.TokenResponse;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import java.util.Collection;
import java.util.stream.Collectors;
import org.joda.time.DateTime;
/**
* #author jlilley
*/
public class JWTTokenRequest extends TokenRequest {
private String serviceKeyJson;
private boolean doRsa = true;
public JWTTokenRequest(HttpTransport transport, JsonFactory jsonFactory, GenericUrl tokenServerUrl) {
super(transport, jsonFactory, tokenServerUrl, "urn:ietf:params:oauth:grant-type:jwt-bearer");
}
#Override
public JWTTokenRequest setTokenServerUrl(GenericUrl tokenServerUrl) {
return (JWTTokenRequest)super.setTokenServerUrl(tokenServerUrl);
}
public JWTTokenRequest setServiceKey(String json) throws Exception {
this.serviceKeyJson = json;
return this;
}
public JWTTokenRequest setServiceKey(InputStream is) throws Exception {
try (BufferedReader buffer = new BufferedReader(new InputStreamReader(is))) {
return setServiceKey(buffer.lines().collect(Collectors.joining("\n")));
}
}
#Override
public JWTTokenRequest setScopes(Collection<String> scopes) {
return (JWTTokenRequest) super.setScopes(scopes);
}
#Override
public JWTTokenRequest set(String fieldName, Object value) {
return (JWTTokenRequest) super.set(fieldName, value);
}
/**
* Create a JWT for the given project id, signed with the given RSA key.
*/
private String signJwtRsa(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("RSA");
return jwtBuilder.signWith(SignatureAlgorithm.RS256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Create a JWT for the given project id, signed with the given ES key.
*/
private String signJwtEs(JwtBuilder jwtBuilder, PKCS8EncodedKeySpec spec) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
return jwtBuilder.signWith(SignatureAlgorithm.ES256, kf.generatePrivate(spec)).compact();
} catch (Exception ex) {
throw new RuntimeException("Error signing JWT", ex);
}
}
/**
* Finalize the JWT and set it in the assertion property of the web service call
* #throws java.io.IOException
*/
void makeAssertion() {
JsonParser parser = new JsonParser();
JsonObject jsonDoc = (JsonObject) parser.parse(serviceKeyJson);
String pkStr = jsonDoc.get("private_key").getAsString()
.replace("\n", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "");
byte[] pkBytes = Base64.getDecoder().decode(pkStr);
DateTime now = new DateTime();
JwtBuilder jwtBuilder = Jwts.builder()
.setIssuedAt(now.toDate())
.setExpiration(now.plusMinutes(60).toDate())
.setAudience(getTokenServerUrl().toString())
.setIssuer(jsonDoc.get("client_email").getAsString());
if (getScopes() != null) {
jwtBuilder = jwtBuilder.claim("scope", getScopes());
}
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkBytes);
String pkId = jsonDoc.get("private_key_id").getAsString();
jwtBuilder.setHeaderParam("kid", pkId);
jwtBuilder.setHeaderParam("typ", "JWT");
set("assertion", doRsa ? signJwtRsa(jwtBuilder, spec) : signJwtEs(jwtBuilder, spec));
}
/**
* Finalize the JWT, set it in the assertion property of the web service call, and make the token request.
*/
#Override
public TokenResponse execute() throws IOException {
makeAssertion();
return super.execute();
}
}
Give this, I can set up the token request from the service account JSON key file, execute() it, and fetch the resulting access token. Note that token renewal responsibility is up to the caller.

Connect to spring backend error

I want try do a Post request from my frontend (Angular 2) to my backend (Spring). But I can't.
The error:
GET http://localhost:8080/loginPost 405 ()
Failed to load http://localhost:8080/loginPost: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://192.168.0.190:4200' is therefore not allowed access. The response had HTTP status code 405.
My Angular Service:
import {Injectable} from "#angular/core";
import { Headers, Http } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import { Login } from'../data-login/models/login.model';
#Injectable()
export class LoginService{
private loginUrl = 'http://localhost:8080/loginPost'; // URL to web API
private headers = new Headers({'Content-Type': 'application/json'});
constructor(private http: Http){}
loginQuery(login: Login){
console.log(login.id);
return this.http.request(this.loginUrl,JSON.stringify(login));
}
}
My Spring Backend Code:
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
#RestController
public class LoginProvider {
#CrossOrigin(origins = "http://192.168.0.190:4200")
#PostMapping(value="/loginPost", consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public ResponseEntity<?> verifyLogin(#RequestBody String maoe){
System.out.println(maoe);
return new ResponseEntity<>(HttpStatus.OK);
}
}
I need to read the Json sent by my frontend, checking and responding with OK, no problems. But I can not read Json. I'm trying to store it in the variable "maoe"
You are trying to do send a GET request to a resource that accepts only POST requests. That is why you are getting a 405 response. Change either your rest service or angular http service to have both matching request types.

CORS Preflight request - Custom auth token in header - Spring and Angular 4

I'm having trouble requesting a simple GET on my local server with a token as a header.
My browser keep sending preflight request on simple GET request.
I tried with postman / curl and i don't have any issue.
Here's the actual code :
Server (Spring) :
#Override
public void doFilter(final ServletRequest req, final ServletResponse res,
final FilterChain chain) throws IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods",
"POST, GET, PUT, OPTIONS, DELETE, PATCH");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, x-auth-token, Content-Type");
response.setHeader("Access-Control-Expose-Headers", "x-auth-token");
response.setHeader("Access-Control-Allow-Credentials", "true");
final HttpServletRequest request = (HttpServletRequest) req;
if (request.getMethod().equals("OPTIONS")) {
try {
response.getWriter().print("OK");
response.getWriter().flush();
} catch (IOException e) {
e.printStackTrace();
}
} else {
chain.doFilter(request, response);
}
}
So my x-auth-token here is a jwt-like token.
In my Angular code, I simply add my token as a x-auth-token like this :
getAll() {
return this.http.get('http://127.0.0.1:8080/module', this.jwt()).map((response: Response) => response.json());
}
private jwt() {
// create authorization header with jwt token
let token = localStorage.getItem('user');
let obj = JSON.parse(token)
console.log(obj.token.token)
if (obj.token) {
let headers = new Headers({
'x-auth-token': obj.token.token,
'Content-Type': "application/json"
});
return new RequestOptions({ headers: headers });
}
}
Note that my authentication / signup routes work fine, i'm only having trouble communicating my x-auth-token header to my Spring server.
Both my webserver are running locally : Spring on 8080 and Angular on 8000.
Any help would be greatly appreciated,
Thank you,
First, postman and curl are not good ways to test for CORS, since they don't enforce the same origin policies as standard browsers do. They will work even in cases that will fail in the browser.
Second, the reason your request is pre-flighted by the browser is that it is not a simple request, because of the x-auth-token header and the application/json Content-Type. Only application/x-www-form-urlencoded
multipart/form-data and text/plain content-types are allowed in simple requests.
I would assume that the problem is in the OPTION response. Make sure that it includes all the relevant headers. It might be possible it is failing or that you are loosing the added headers because you are handling it differently than other responses.

Angular2 JWT Authorization header missing in http response (CORS support added)

I'm trying to do an authorization using JWT (Json Web Token). On front-end I'm using Angular2, on back-end Spring REST api. I've added CORS filter on back-end side.
In http request I'm sending username and password and expecting token in 'Authorization' header of response. Everything works fine when I use Postman, I receive all headers including 'Authorization'.
Also, when I record the traffic in Chrome Console (while doing user login through the form) 'Authorization' header is present in response, so obviously it returns back to the browser. But when I list headers in my angular app, there is just few headers in array:
// auth.service.ts
login(username, password): Observable<boolean> {
// call remote service to authenticate user
return this.http.post(this.authUrl, JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
console.log("Authorization header: " + response.headers.get('Authorization'));
console.log("all headers: " + response.headers.keys());
// TODO - login successful if there's a jwt token in the response
});
}
Result of those 2 console output is:
Authorization header: null
all headers: Pragma,Cache-Control,Expires
Here is the screenshot from Google Chrome console where you can see that all needed headers are present on client side:
Server-side token generation (Spring Boot):
public void addAuthentication(HttpServletResponse response, String username) throws UnsupportedEncodingException {
// Token generation
String JWT = Jwts.builder()
.setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, secret.getBytes("UTF-8"))
.compact();
response.addHeader(headerString, tokenPrefix + " " + JWT);
}
Does anyone has some useful advice?
Thanks
SOLUTION:
public class CORSFilter implements Filter{
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
response.setHeader("Access-Control-Expose-Headers", "Authorization");
chain.doFilter(req, response);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
#Override
public void destroy() {
// TODO Auto-generated method stub
}
}
Authorization header is present but since it's a custom header the browser will not allow you to access it directly even though chrome will see it.
To solve that you need one more important header in you cors config.
Access-Control-Expose-Headers: Authorization, CustomHeader2, e.tc
Make sure to expose any other custom headers that you want your app to have access to.

ClientAbortException when executing angular 2 post in tomcat

After completing the quickstart of angular 2 app i tried to execute a post to a rest web service deployed in another server, which is tomcat.
I have added an OPTIONS method to allow all origins. After returning from the getOptions() method it enters the getTestResponse method and a
ClientAbortException occurs.
Sample code :
return this._http.post(url, body, options)
.map(res => res.json());
Web service code :
#OPTIONS
#Path("samplePath")
public Response getOptions() {
return Response.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT")
.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding")
.build();
}
#POST
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public TestResponse getTestResponse(TestRequest testRequest) {
//somehow get response from database
//
}
Do you have any idea why this exception could occur?
It's because here you add headers just to your option request.
But actually POST is failing.
To fix issue, best is to add a CORS filter to your application which will be applied to all requests.
CorsFilterApi
import org.jboss.resteasy.plugins.interceptors.CorsFilter;
#ApplicationPath(RestApplication.ROOT_PATH)
public class RestApplication extends Application {
public static final String ROOT_PATH = "/resources";
private Set<Object> singletons = new HashSet<>();
...
#Override
public Set<Object> getSingletons() {
CorsFilter corsFilter = new CorsFilter();
corsFilter.getAllowedOrigins().add("*");
corsFilter.setAllowCredentials(true);
corsFilter.setAllowedHeaders("origin, content-type, accept, authorization");
corsFilter.setAllowedMethods("GET, POST, DELETE, PUT, OPTIONS, HEAD");
singletons.add(corsFilter);
return singletons;
}
}
EDIT:
Solution 2 apply headers just to your post request and
make sure that your #OPTION REQUEST IS NOT CONTAINING #PATH
because then it's not same request in your case:
#OPTIONS
public Response getOptions() {
return Response.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT")
.header("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding")
.build();
}
#POST
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public Response getTestResponse(TestRequest testRequest) {
return Response.ok()
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Headers", "origin, content-type, accept, authorization")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS").build();
}

Resources