Passing String value from Angular $http post to Spring #RestController - spring-restcontroller

I have a REST api endpoint like this -
#RequestMapping(value = "/createform/custom/name", method = RequestMethod.POST)
public String nameSubmit(#RequestBody String name) {
return "you have submitted this name ***** "+name;
}
From angular service I tried to make a REST call like this -
var data = 'name='+inputName;
$http.post(uploadUrl, data, {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
}).then(
function(success){
alert(success.data);
},
function(error){
alert(error.status);
}
);
Now I always get a -1 HTTP status and control goes to error block. I also tried #RequestParam instead of #RequestBody but no luck. But if I try to access the service through curl or chrome postman , everything works fine. Only when I try through angular application I get stuck with -1 response.

It was a CORS issue. I tried the Spring proposed solutions first. But the "gs-rest-service-cors" jar was not found in Maven repository. Hence ended up writing a filter as shown here and things worked fine.

Related

Can't perform HTTP Post Action from Logic App to Asp.net Core Web API

I've built many Logic Apps. I've also integrated with the Logic App API. For some reason, a Post request to an Asp.net Core Web API won't work. It works in Postman, but I can't get Logic Apps to complete the request.
The request arrives at my Web API. I can step through it during a remote debug session. I'm using the [FromBody] decorator on the API method. All the string values in the object are null.
Logic App Headers
Accept = "application/json"
ContentType = "application/json"
ContentLength = "35"
Host = "****.centralus.logic.azure.com"
API method
[HttpPost]
[Route("CreateSomething")]
public async Task<IActionResult> CreateSomething([FromBody] MyObject object)
{
//Create something great
}
I think it might have something to do with the Headers. I noticed that the Postman request won't succeed unless I check the Host and Content-Length box in the Headers section. According to this article, Logic Apps ignores those Headers.
https://learn.microsoft.com/en-us/azure/connectors/connectors-native-http
I've built the HTTP Post Action using the API as well as configured it manually using the Logic App UI in Azure.
By the way, does anyone know the Expression that will automatically calculate the ContentLength?
UPDATE:
I finally figured this out. I had to do some Ninja coding crap to make this work. I'll post my solution tomorrow.
Does anyone know how to make this work? Thanks in advance!
When you use the Logic App API to programmatically create Logic Apps, you have to specify the Body class for when you do something like an HTTP Post. When the Body JSON displayed in the designer, it contained a single object with the objects properties. My API method could not handle this. The key was to simply post the properties in the JSON Body. To make matters worse, I'm doing two HTTP Posts in this particular Logic App. When I tried to add my object properties to the existing Body class, it caused my other HTTP Post to stop working. To overcome this, I had to create a Body2 class with the objects properties. I then had to use the following line of code to replace body2 with body before adding the JSON to the Logic App API call.
This did not work.
body = new Body()
{
object = new Object()
{
//Properties
}
}
This worked.
body2 = new Body2()
{
Type = 0,
Description = "#{items('For_each_2')?['day']?['description']}",
Locations = item.Locations,
Cold = "#{items('For_each_2')?['temperature']?['cold']?['value']}",
Hot = "#{items('For_each_2')?['temperature']?['hot']?['value']}",
Hide = 0
}
Notice I used Replace on body2.
var options = new JsonSerializerOptions { WriteIndented = true, IgnoreNullValues = true};
string jsonString = ReplaceFirst(JsonSerializer.Serialize(myApp, options), "schema", "$schema").Replace("_else", "else").Replace("_foreach", "foreach").Replace("body2", "body");

Response to preflight request doesn't pass access control check: It does not have HTTP ok status. GET working POST PUT DELETE not working

Greetings
I have one web application with following architecture:
Web api: ASP.net core 2.1 (Windows Authentication)
UI: angular 8
UI is able to get data but unable to send data.
I mean GET method is working fine but POST, PUT, DELETE options are not working .
And all the methods are working using POSTMAN.
ERROR is:
Access to XMLHttpRequest at 'http://xx.xxx.xxx.xx:xxyy/xxx/xxxxxx/Method' from origin 'http://localhost:xxxx' has been blocked by CORS policy:
Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
Any help will be appreciated .
Thanks in advance :)
That's because your API is on different domain than your SPA angular application.
Please at this at the start of your Configure method in Startup.cs
if (env.IsDevelopment())
{
app.UseCors(opts =>
{
opts.WithOrigins(new string[]
{
"http://localhost:3000",
"http://localhost:3001"
// whatever domain/port u are using
});
opts.AllowAnyHeader();
opts.AllowAnyMethod();
opts.AllowCredentials();
});
}
Please note that this will handle only CORS for local development since you'll probably have same domain in production - if not, you'll need to reconfigure this for production also.
CORS blocking is browser specific and that's why it's working in PostMan but not in browser.
This is what i use and it should work i hope for your case.
My startup.cs ConfigureServices() decorated with:
services.AddCors(feature =>
feature.AddPolicy(
"CorsPolicy",
apiPolicy => apiPolicy
//.AllowAnyOrigin()
//.WithOrigins("http://localhost:4200")
.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed(host => true)
.AllowCredentials()
));
And, Configure() method with:
app.UseCors("CorsPolicy");
Notice the SetIsOriginAllowed() and allowCreds() along with other policy settings, this works for me with POST calls to my api from my angular, which are running on two different port#s.
UPDATE:
Following the questions on the comments, adding additional information on how do we check the logged in user (windows auth) btwn api and the angular (frontend).
You can check the incoming User on a specific route that would only expect the authenticated user using the decoration [Authorize]. In my case, i would have only one method that would expect the windows user in the api:
[HttpGet("UserInfo")]
[Authorize]
public IActionResult GetUserInfo()
{
string defaultCxtUser = HttpContext?.User?.Identity?.Name;
if (defaultCxtUser != null && !string.IsNullOrEmpty(defaultCxtUser))
{
_logger.LogDebug($"START - Get Context user details for {defaultCxtUser}");
ADHelper.logger = _logger;
var userFullName = ADHelper.GetUserIdentityInfo(defaultCxtUser);
_logger.LogInformation($"Context user {defaultCxtUser} with name: {userFullName}");
var userInfo = new { Name = userFullName };
//_logger.LogDebug($"END - GetUserInfo({defaultCxtUser} for {userFullName}");
return Ok(userInfo);
}
else
return Ok(new { Name = defaultCxtUser });
}
then i would call this from my angular with the service call as,
// Get the Logged in user info
GetCurrentUserInfo(): Observable<string> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
withCredentials: true
};
// return this.http.get<string>(`${ApiPath}UserInfo`, httpOptions)
// .pipe(map(v => v as string));
return this.http.get<UserInfo>(`${ApiPath}UserInfo`, httpOptions)
.pipe(map(data => {
// console.log(data, data.Name);
return data.Name;
}))
;
}
Please see the headers with 'withCredentials: true' line that would trigger to pass the current user info, and it would be read and understood only if it has the authorize attr to read the User.Identity object in c# side. The reason we do this on a specific method is that, there should be some other parental method in the api like ApiStatus() or anything that could be, should be called first. This would ensure to also invoke the preflight check with OPTIONS that would require anonymous auth. Like in my case, getting whether the api is available and running, and some other app environment info before i get the userInfo() from my angular app.

Trouble Getting Twilio SMS Response from ASP.NET Core MVC Endpoint

When I setup request bin, I get a response that seems totally normal (see screen shot below). When I use the command
ngrok http 5000
And I send the response to my local http endpoint, ngrok reports 200OK if my POST method in the controller has no parameters. Even if I add one parameter ([FromBody] string content), I get a 400 bad request out of ngrok's console.
I'm pasting below a couple different POST method's I've tried. I've tried inheriting my controller from controllerbase and controller and get the same behavior.
[HttpPost]
public string JsonStringBody([FromBody] TwilioSmsModel twilioSmsModel)
{
return "";
}
POST: api/SmsBody
[HttpPost]
public async Task<IActionResult> PostTwilioSmsModel([FromBody] TwilioSmsModel twilioSmsModel)
public async Task<IActionResult> Post()
{
var twilioSmsModel = new TwilioSmsModel();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
_context.TwilioSmsModels.Add(twilioSmsModel);
await _context.SaveChangesAsync();
return CreatedAtAction("GetTwilioSmsModel", new { id = twilioSmsModel.SmsSid }, twilioSmsModel);
}
If there is a github example of sms notifications working with asp.net core 2.1, that would be a big help.
Twilio developer evangelist here. Most likely the error you're seeing has nothing to do with the way you're building your application, but with the fact that .NET expect some host headers to be passed in order to process your request. And why it works with requestbin.
You haven't specified any error message in your question, so this is guesswork, but try changing your ngrok commend to the following:
ngrok http 5000 -host-header="localhost:5000"
And you should stop seeing the 400 error you're getting and the requests should go through normally.
Hope this helps.

page Redirect in ASP.Net MVC + Web Api + AngularJs

I am building a ASP.Net MVC application that can work both in Web and JQuery mobile. So i am creating a seperate view for Web and JQuery mobile application. I have placed all my primary business logic services as a Web Api calls which are called by both the clients using the AngularJs which is working fine so far.
Now I was looking to introduce the security in to the application, and realized that Basic authentication is the quickest way to get going and when I looked around I found very nice posts that helped me build the same with minimal effort. Here are 3 links that I primarily used:
For the Client Side
HTTP Auth Interceptor Module : a nice way to look for 401 error and bring up the login page and after that proceed from where you left out.
Implementing basic HTTP authentication for HTTP requests in AngularJS : This is required to ensure that I am able reuse the user credentials with the subsequent requests. which is catched in the $http.
On the Server Side :
Basic Authentication with Asp.Net WebAPI
So far so good, all my WebApi calls are working as expected,
but the issue starts when I have to make calls to the MVC controllers,
if I try to [Authorize] the methods/controllers, it throws up the forms Authentication view again on MVC even though the API has already set the Authentication Header.
So I have 2 Questions:
Can We get the WebApi and MVC to share the same data in the header? in there a way in the AngularJS i can make MVC controller calls that can pass the same header information with authorization block that is set in the $http and decode it in the server side to generate my own Authentication and set the Custom.
In case the above is not possible, I was trying to make a call to a WebApi controller to redirect to a proper view which then loads the data using the bunch of WebApi calls so that user is not asked to enter the details again.
I have decorated it with the following attribute "[ActionName("MyWorkspace")] [HttpGet]"
public HttpResponseMessage GotoMyWorkspace(string data)
{
var redirectUrl = "/";
if (System.Threading.Thread.CurrentPrincipal.IsInRole("shipper"))
{
redirectUrl = "/shipper";
}
else if (System.Threading.Thread.CurrentPrincipal.IsInRole("transporter"))
{
redirectUrl = "/transporter";
}
var response = Request.CreateResponse(HttpStatusCode.MovedPermanently);
string fullyQualifiedUrl = redirectUrl;
response.Headers.Location = new Uri(fullyQualifiedUrl, UriKind.Relative);
return response;
}
and on my meny click i invoke a angular JS function
$scope.enterWorkspace = function(){
$http.get('/api/execute/Registration/MyWorkspace?data=""')
.then(
// success callback
function(response) {
console.log('redirect Route Received:', response);
},
// error callback
function(response) {
console.log('Error retrieving the Redirect path:',response);
}
);
}
i see in the chrome developer tool that it gets redirected and gets a 200 OK status but the view is not refreshed.
is there any way we can at least get this redirect to work in case its not possible to share the WebApi and MVC authentications.
EDIT
Followed Kaido's advice and found another blog that explained how to create a custom CustomBasicAuthorizeAttribute.
Now I am able to call the method on the Home controller below: decorated with '[HttpPost][CustomBasicAuthorize]'
public ActionResult MyWorkspace()
{
var redirectUrl = "/";
if (System.Threading.Thread.CurrentPrincipal.IsInRole("shipper"))
{
redirectUrl = "/shipper/";
}
else if(System.Threading.Thread.CurrentPrincipal.IsInRole("transporter"))
{
redirectUrl = "/transporter/";
}
return RedirectToLocal(redirectUrl);
}
Again, it works to an extent, i.e. to say, when the first call is made, it gets in to my method above that redirects, but when the redirected call comes back its missing the header again!
is there anything I can do to ensure the redirected call also gets the correct header set?
BTW now my menu click looks like below:
$scope.enterMyWorkspace = function(){
$http.post('/Home/MyWorkspace')
.then(
// success callback
function(response) {
console.log('redirect Route Received:', response);
},
// error callback
function(response) {
console.log('Error retrieving the Redirect path:',response);
}
);
}
this finally settles down to the following URL: http://127.0.0.1:81/Account/Login?ReturnUrl=%2fshipper%2f
Regards
Kiran
The [Authorize] attribute uses forms authentication, however it is easy to create your own
BasicAuthenticationAttribute as in your third link.
Then put [BasicAuthentication] on the MVC controllers instead of [Authorize].

Communication between AngularJS and a Jersey Webservice which are on a different domain. Can't access correct session

Lately I've been playing around with AngularJS and Java EE 6. I've build an webservice with Jersey and deployed the project on Glassfish. Because I needed some kind of authentication and an OAuth implementation or an JDBCRealm seemed overkill I decided to just create a session if the user successfully logged in.
#POST
#Path("/login")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
public Response login(LoginDAO loginData, #Context HttpServletRequest req) {
req.getSession().invalidate();
loginData.setPassword(PasswordGenerator.hash(loginData.getPassword()));
User foundUser = database.login(loginData);
if(foundUser == null) {
return Response.status(Status.CONFLICT).build();
}
req.getSession(true).setAttribute("username", foundUser.getUsername());
return Response.ok().build();
}
#GET
#Path("/ping")
public Response ping(#Context HttpServletRequest req) {
if(req.getSession().getAttribute("username") == null) {
return Response.ok("no session with an username attribute has been set").build();
}
return Response.ok(req.getSession(true).getAttribute("username")).build();
}
This seems to work alright, if I post to /login from Postman or from a basic jQuery webpage deployed on glassfish I do get the correct username back and a session has been placed. If I then send a GET request to /ping I do get the username back from which I logged in.
I've an AngularJS application deployed on a node.js webserver which needed to login. Because this server is on another port its on another domain and I had to go through the pain of enabling cors. I did this by building a container response filter which sets the response headers.
public class CrossOriginResourceSharingFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest creq, ContainerResponse cresp) {
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Origin", "http://localhost:8000");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Credentials", "true");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
cresp.getHttpHeaders().putSingle("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
return cresp;
}
}
This did made it possible for me to send different types of HTTP requests from AngularJS to Java EE 6 application deployed on glassfish.
The problem is that when I send a POST request from AngularJS to the /login method, a session is created and I do get my username back. But when I send a GET request to the /ping method I get the "no session with an username attribute has been set" notice.
I believe this has to do with cross domain prevention and that I've to set the withCredentials tag when I send a xhr request. I've been trying to do this in AngularJS but haven't found out how to do this.
function LoginCtrl($scope, $http) {
$scope.login = function() {
$http.post("glassfish:otherport/api/login", $scope.credentials).
success(function(data) {
console.log(data);
}).
error(function(data, error) {
console.log(error);
});
};
};
And in another controller:
$scope.getUsername = function() {
$http.get("glassfish:otherport/api/ping", {}).
success(function(data) {
$scope.username = data;
}).
error(function() {
$scope.username = "error";
})
}
I've tried to set withCredentials is true
$http.defaults.withCredentials = true;
This however didn't solve my problem. I also tried to send it with every request in the config parameter but this didn't solve my problem either.
Depending on the version of AngularJS you are using you might have to set it on each $http.
Since 1.2 you can do:
$http.get(url,{ withCredentials: true, ...})
From 1.1.1 you can globally configure it:
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]).
If you're using an older version of Angular, try passing a config object to $http that specifies withCredentials. That should work in versions before 1.1:
$http({withCredentials: true, ...}).get(...)
See also mruelans answer and:
https://github.com/angular/angular.js/pull/1209
http://docs.angularjs.org/api/ng.$http
https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS?redirectlocale=en-US&redirectslug=HTTP_access_control#section_5
just an update to #iwein anwser, that we can now set in config itself
config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}]).
https://github.com/angular/angular.js/pull/1209
(available only after unstable version: 1.1.1)
In 1.2 version, this doesn't work for me:
$http({withCredentials: true, ...}).get(...)
if I read the doc, the shortcut method should take the config object
$http.get(url,{ withCredentials: true, ...})
$http is a singleton, That's the only way to mix in a same application requests with and without credentials.