Here is my csrf and cors handler of my vertx application
#Log4j2
public class CsrfVerticle extends AbstractVerticle {
private final Set<HttpMethod> httpMethodSet =
new HashSet<>(Arrays.asList(GET, POST, OPTIONS, PUT, DELETE, HEAD));
private final Set<String> headerSet = new HashSet<>(
Arrays.asList("Content-Type", "Authorization", "Origin", "Accept", "X-Requested-With",
"Cookie", "X-XSRF-TOKEN"));
private Connection dbConnection;
private WebClient webClient;
private Vertx vertx;
public void start() throws Exception {
super.start();
HttpServer httpServer = TestService.vertx.createHttpServer();
Router router = Router.router(TestService.vertx);
SessionStore store = LocalSessionStore.create(vertx);
SessionHandler sessionHandler = SessionHandler.create(store)
.setCookieSameSite(CookieSameSite.STRICT)
.setCookieHttpOnlyFlag(false);
router.route().handler(LoggerHandler.create());
if (TestService.serviceConfiguration.isEnableCSRF()) {
router.route()
.handler(CorsHandler.create("*").allowedMethods(httpMethodSet).allowedHeaders(headerSet)
.allowCredentials(true).addOrigin(TestService.serviceConfiguration.getFrontendUrl()));
router.route().handler(
CSRFHandler.create(vertx, csrfSecret()).setCookieHttpOnly(false))
.handler(sessionHandler);
} else {
router.route()
.handler(CorsHandler.create("*").allowedMethods(httpMethodSet).allowedHeaders(headerSet)
.allowCredentials(true)).handler(sessionHandler);
}
dbConnection = createConnection(TestService.serviceConfiguration.getJdbcConfig());
TestAuth testAuth = new TestAuth(TestService.serviceConfiguration.getUsername(),
TestService.serviceConfiguration.getPassword());
AuthenticationHandler basicAuthHandler = BasicAuthHandler.create(testAuth);
router.route("/student/*").handler(basicAuthHandler);
router.route("/student/add").method(HttpMethod.POST).handler(this::handleAddUser);
router.route("/student/get").method(HttpMethod.GET).handler(this::handleGetUser);
router.route("/student/delete").method(HttpMethod.DELETE)
.handler(this::handleDeleteUser);
router.route("/student/update").method(HttpMethod.PUT).handler(this::handleUpdateUser);
httpServer.requestHandler(router).listen(TestService.serviceConfiguration.getPort());
log.info("Console Server Verticle Started Successfully. Listening to {} port",
TestService.serviceConfiguration.getPort());
}
I am able to receive cookies in browser and send it back along with updated X-XSRF-TOKEN attached to the header
Everything works fine in my local but when deploying in VM I get the below error for all post requests
ctx.fail(403, new IllegalArgumentException("Token signature does not match"));
from csrf handler of vertx.
Here are the frontend code to add x-xsrf-token when sending requests to backend
createXsrfHeader(headers: HttpHeaders) {
let xsrfToken = Cookies.get('XSRF-TOKEN')
let sessionToken = Cookies.get('vertx-web.session')
if(xsrfToken)
headers = headers.append('X-XSRF-TOKEN', xsrfToken);
// if(xsrfToken && sessionToken)
// headers = headers.append('Cookie', `XSRF-TOKEN=${xsrfToken}; vertx-web.session=${sessionToken}`);
return headers;
}
[Adding header to post request]
callPostRequest(subUrl: string,reqData: any) {
let headers = new HttpHeaders();
headers = this.createAuthorizationHeader(headers);
headers = this.createXsrfHeader(headers);
return this.http.post<any>(this.basicApiUrl+subUrl, reqData, {
headers: headers,
withCredentials : true
}).pipe(map(resData => {
// console.log(resData);
return resData;
}));
}
[Adding header to put request]
callPutRequest(subUrl: string,reqData: any) {
let headers = new HttpHeaders();
headers = this.createAuthorizationHeader(headers);
headers = this.createXsrfHeader(headers);
return this.http.put<any>(this.basicApiUrl+subUrl, reqData,{
headers: headers,
withCredentials : true
}).pipe(map(resData => {
// console.log(resData);
return resData;
}));
}
[Adding header to delete request]
callDeleteRequest(subUrl: string,reqData?: any) {
let headers = new HttpHeaders();
headers = this.createAuthorizationHeader(headers);
headers = this.createXsrfHeader(headers);
return this.http.delete<any>(this.basicApiUrl+subUrl, {
headers: headers,
withCredentials : true
}).pipe(map(resData => {
// console.log(resData);
return resData;
}));
}
Is there any ways to solve it.
I believe your problem is here:
router.route() // <--- HERE
.handler(
CSRFHandler.create(vertx, csrfSecret())
.setCookieHttpOnly(false))
.handler(sessionHandler);
You are telling the application to create a new CSRF token for each request that is happening, instead of being specific of which end points are really form-based endpoints.
Imagine the following, your form is on /student/form your browser may request:
/student/form (new CSRF token: OK)
/images/some-image-in-the-html.png (new CSRF token: probably Wrong)
/css/styles.css (new CSRF token: probably Wrong)
...
Now the issue is that the 1st call did correctly generated a token, but the following 2+ will generate new tokens too and these won't match the 1st so your tokens are always misaligned.
You probably need to be more specific with the resources you want to protect, from your code I am assuming that you probably want something like:
router.route(""/student/*") // <--- Froms are always here under
.handler(
CSRFHandler.create(vertx, csrfSecret())
.setCookieHttpOnly(false))
.handler(sessionHandler);
Be careful if calling other endpoints would affect the forms too. Note that you can add multiple handlers per route, so you can be more explicit with:
router.route(""/student/add")
// 1st always CSRF checks
.handler(
CSRFHandler.create(vertx, csrfSecret())
.setCookieHttpOnly(false))
// and now we the handler that will handle the form data
.handler(this::handleAddUser)
Related
I received X-Forwarded-Host and X-Forwarded-Proto in my controller endpoints, and the endpoint has a reactive pipeline to call a ReactiveFeignClient class.
These headers should be propagated to my client requests, but as I see it, it has not. I have no Principal in this pipeline, because the endpoints needs no auth, so I cannot use ReactiveSecurityContextHolder.withAuthentication(user)
I already added a WebFilter to read headers from request:
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange).subscriberContext((context) -> {
ServerHttpRequest request = exchange.getRequest();
Map<String, String> headers = (Map)request.getHeaders().toSingleValueMap().entrySet().stream().filter((entry) -> {
return ((String)entry.getKey()).equalsIgnoreCase(this.authLibConfig.getXForwardedHostHeader()) || ((String)entry.getKey()).equalsIgnoreCase(this.authLibConfig.getXForwardedProtoHeader());
}).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
System.out.println("Adding all headers now: ");
context.put("headers_to_propagate", headers);
return context;
});
}
But I don't know where in the config of client can I retrieve them from the Context and put into requests in client.
Now I do this:(
#Bean
public ReactiveHttpRequestInterceptor forwardingHeadersInterceptor(ReactiveFeignUtils reactiveFeignUtils) {
return reactiveFeignUtils::mutateRequestHeadersForNoAuthRequests;
}
And:
public Mono<ReactiveHttpRequest> mutateRequestHeadersForNoAuthRequests(ReactiveHttpRequest reactiveHttpRequest) {
return Mono.subscriberContext().doOnNext((context) -> {
System.out.println("Current context: " + context.toString());
if (context.hasKey("headers_to_propagate")) {
System.out.println("Getting all host headers: ");
reactiveHttpRequest.headers().putAll((Map)context.get("headers_to_propagate"));
}
}).thenReturn(reactiveHttpRequest);
}
But no headers are forwarded.
I ended up creating a customized class implementing Authentication and add these fields as metadata property to it; because even though this endpoint requires no auth, headers related to member id and other auth info are received, so I can construct an Authentication principal.
Actually as I see, working with this object is the only way.
I've written multiple REST Endpoints in my Controller [GET, POST and PUT].
GET and POST calls are working fine. But when I try to hit PUT request from Postman, my java controller is not able to receive that request. There is no error message. Response body is empty. Response code in Postman is 200, OK.
Here is my PUT Endpoint which is not able to get request from Postman:
#PutMapping(value = "/devRegistration")
public Object deviceRegistration(HttpServletRequest httpRequest,
#RequestBody(required = false) Map<String, Object> jsonBody ){
ResponseEntity<Object> response = null;
System.out.println("jsonBody = "+jsonBody);
devService.deviceRegistration(httpRequest, jsonBody);
return response;
}
Here is my GET Endpoint which is working fine:
#GetMapping(value = "/checkRegistration")
public void checkRegistration(HttpServletRequest httpRequest,
#RequestParam("appId") String appId, #RequestParam("offset") String offset ){
Map<String, Object> jsonBody = new HashMap<String, Object>();
jsonBody.put("appId", appId);
jsonBody.put("offset", offset);
service.checkRegistration(httpRequest, jsonBody);
}
Postman URL with Headers and Body for PUT request is :
https://localhost:8443/api/device/devRegistration
In Body:
app_Id : some value
offset : some value
Headers are:
RequestHeader
Content-Type
Authorization
MobileHeader
SessionToken
But, System.out.println() is not being executed in PUT Endpoint.
Let me know, if any other info. is required.
I found the answer.
Actually, I need to pass the params in the Body as raw form instead of form-data in Postman.
It worked now.
I am unable to get values filled in the map after making a web client call and using the response of the previous Mono.Here is the code I have tried.The value of parameters.size() comes out to zero.Not able to get the reason as to why the value is not filled.I basically want to return age ( and not Mono object)
from this method.Using block gives an error block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-3.
Map<String, String> parameters = new HashMap<String,String>();
Mono<Person> obj = webClient
.post()
.uri("dummy url")
.accept(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML)
.retrieve()
.bodyToMono(Person.class)
.flatMap(resp -> {
parameters.put("name", resp.getName());
parameters.put("age", resp.getAge());
return Mono.just(new Person(resp.getName(),resp.getAge()));
}
);
System.out.println(parameters.size());
Please suggest where I am wrong and solution to fix the same.
Since this is about collecting and using a token of some sort collected from a previous HTTP call, your best bet is to delegate all that to an ExchangeFilterFunction.
An ExchangeFilterFunction is a filter that is executed on the client side for each outgoing request. Here is a very, very naïve implementation of such a filter:
class TokenFilterFunction implements ExchangeFilterFunction {
private final AtomicReference<String> token = new AtomicReference<>();
#Override
public Mono<ClientResponse> filter(ClientRequest req, ExchangeFunction next) {
if (this.token.get() == null) {
return fetchToken(next).then(sendRequest(req, next));
}
else {
return sendRequest(req, next);
}
}
private Mono<ClientResponse> sendRequest(ClientRequest req, ExchangeFunction next) {
ClientRequest request = ClientRequest.from(req)
.header("Token", this.token.get()).build();
return next.exchange(request);
}
private Mono<Void> fetchToken(ExchangeFunction next) {
ClientRequest tokenRequest = ClientRequest.create(HttpMethod.GET,
URI.create("https://example.com/token")).build();
return next.exchange(tokenRequest).doOnNext(res -> {
this.token.set(res.headers().header("Token").get(0));
}).then();
}
}
This could automatically call the token endpoint to fetch a token when needed and directly chain with the request you asked in the first place. Again, such an implementation should be much more complex than that, handling domains, errors, and more.
If you're using some authentication technology, such a filter might be implemented already in Spring Security in a much, much better way.
You can configure it on your client during the building phase, like:
WebClient webClient = WebClient.builder().filter(new TokenFilterFunction()).build();
I am writing a REST client in C#.Net using RestSharp. There are two API calls - one is "Auth" call and second is "getKey" call. The "Auth" call throws back a "Auth token"in the response, and I'd like to parse that token from the response, and pass it as an header to the second "getkey" call. Please advise with examples
I have given some sample to achieve your scenario. Please use the below example and do the modification as per your requirement.
RestUtils Class:
Add the Request Header, If your application is expected some additional headers.
class RestUtils
{
private static readonly RestClient _restClient = new RestClient();
public static void SetBaseURL(String host)
{
_restClient.BaseUrl = new Uri(host);
}
public static string GetResponse(String endpoint, String token)
{
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader("Accept", "application/json");
request.AddHeader("Authorization", token);
IRestResponse response = _restClient.Execute(request);
return response.Content;
}
public static string GetToken(String endpoint)
{
var request = new RestRequest(endpoint, Method.GET);
request.AddHeader("Accept", "application/json");
IRestResponse response = _restClient.Execute(request);
return response.Content;
}
}
TestClass:
In your test class you can add the below steps and you can get the result as expected. First two lines will be executed and give the authentication token as output. So, you can use the retrieved token in the subsequent lines for other API. In another way, you can create one property class and set the retrieved token value .So, that you can access the token from various class.
//Specify the Base URI of your Token Specific API
RestUtils.SetBaseURL("https://login.microsoftonline.com/");
//Specify the End Point of your Token Specific API
String token = RestUtils.GetToken("/oauth2/token");
//Specify the Base URI of your actual Test API
RestUtils.SetBaseURL("XXXXXXX");
String response = RestUtils.GetResponse(token);
I have an AngularJS app which is trying to auth with my Web Api. I receive the below error during the first call to my server if the user does not exist in my database, but does not happen on subsequent calls to the same method once the user exists in my db. (relevant code at the bottom)
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:1378' is therefore not allowed access. The response had HTTP status code 500.
The flow of the logic is:
AngularJS auths with Facebook when the user clicks login
App does an $http.post to my server for auth/login passing their credentials
Server polls Facebook API for user details
If user exists, update their profile and auth 'em
Else, create new membership user, update with FB details, and auth 'em
The only thing that's different if they don't exist in the database (which is when the defect occurs) is that the login method asynchronously calls a createUser method then returns data. No additional external calls are made.
API startup method enabling CORS:
public void Configuration(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
var cors = new EnableCorsAttribute("*","*","*");
config.EnableCors(cors);
ConfigureOAuth(app);
app_start.WebApiConfig.Register(config);
app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
app.UseWebApi(config);
}
API Controller:
[Route("Login")]
[HttpPost]
[AllowAnonymous]
public async Task<FacebookUserModel> Login(FacebookUserRequest user)
{
FacebookUserModel fbUser = new FacebookUserModel();
// Build FacebookUser object
try {
// Grab basic user details
string profileRequestUri = "https://graph.facebook.com/" + user.fbID + "?access_token=" + user.access_token;
HttpWebRequest profileRequest = (HttpWebRequest)WebRequest.Create(profileRequestUri);
profileRequest.Method = WebRequestMethods.Http.Get;
profileRequest.Accept = "application/json";
HttpWebResponse profileResponse = (HttpWebResponse)profileRequest.GetResponse();
Stream profileResponseStream = profileResponse.GetResponseStream();
StreamReader profileStreamReader = new StreamReader(profileResponseStream);
fbUser = JsonConvert.DeserializeObject<FacebookUserModel>(profileStreamReader.ReadToEnd());
} catch (Exception) ...
try {
// Grab profile picture
string pictureRequestUri = "https://graph.facebook.com/" + user.fbID + "/picture";
HttpWebRequest pictureRequest = (HttpWebRequest)WebRequest.Create(pictureRequestUri);
pictureRequest.Method = WebRequestMethods.Http.Get;
HttpWebResponse pictureResponse = (HttpWebResponse)pictureRequest.GetResponse();
fbUser.profilePictureUri = pictureResponse.ResponseUri.ToString();
} catch (Exception) ...
// If user exists, change password to new token and return)
if(userExists)
{
try {
IdentityUser identityUser = _repo.FindUser(ID, pass).Result;
FacebookUserModel dbUser = db.FacebookUserObjects.First(u => u.identityUserID == identityUser.Id);
db.Entry(dbUser).CurrentValues.SetValues(fbUser);
db.SaveChangesAsync();
fbUser.identityUserID = identityUser.Id;
return fbUser;
}
catch (Exception e)
{ return null; }
}
// Else, create the new user using same scheme
else
{
UserModel newUser = new UserModel
{
UserName = ID,
Password = pass,
ConfirmPassword = pass
};
// Create user in Identity & linked Facebook record
createUser(newUser, fbUser);
return fbUser;
}
}
private async void createUser(UserModel newUser, FacebookUserModel fbUser)
{
IdentityResult result = await _repo.RegisterUser(newUser);
var identityUser = await _repo.FindUser(newUser.UserName, newUser.Password);
fbUser.identityUserID = identityUser.Id;
db.FacebookUserObjects.Add(fbUser);
db.SaveChangesAsync();
}
AngularJS calls to my server:
var _login = function (fbID, fbToken) {
$http.post(serviceBase + 'auth/login', { "fbID": fbID, "access_token": fbToken }).then(function (response) {
var data = "grant_type=password&username=" + fbID + "&password=" + pass;
$http.post(serviceBase + 'auth/token', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } })
.success(function (tokenResponse) {
authServiceFactory.bearerToken = tokenResponse.access_token;
})
.error(function (err) {
console.log("token error:", err);
});
authServiceFactory.userObject = response.data;
window.localStorage['userObject'] = JSON.stringify(authServiceFactory.userObject);
})
};
Why would I get the No 'Access-Control-Allow-Origin' error only on the first call, but not subsequent ones?
Update
I have a workaround in place that works, but I don't really like. The issue only arose when calling a second method from my login controller, so if I moved that code up into the login controller instead of a secondary method it works without the CORS error. This really bothers me though and is inefficient, I'd love to know a better way around it.
if you're working with angularjs you might want to check out satellizer. It makes the auth process really simple and has some awesome built in window popup control.
As far as the Access-Control-Allow-Origin calls it could be happening because you explicitly set headers on the one call and the other ones are falling back to the default http provider? Check out $http and see if providing those defaults might work around it.