Why am I getting 400 and 405 exceptions with my WCF RESTful client application with PUT and POST? - asp.net

I am building an application that has a two server setup - a "services" server and a "front end" server. The "services" server has a WCF service. The "front end" server has a traditional ASP.NET web forms application that accesses the WCF service. I can get the GET requests to work fine. However I can't seem to get any PUT or POST requests to work. When I try to issue a POST I get a 400 exception - Bad Request. When I try to issue a PUT I get a 405 exception - Method Not Allowed. I feel like I have everything set up correctly - but obviously not. Here is the code for my WCF service:
Service.svc:
<%# ServiceHost Language="C#" Debug="true" Service="TestSvc" Factory="System.ServiceModel.Activation.WebServiceHostFactory" %>
TestSvc.cs:
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceContract]
public class TestSvc
{
[WebGet(UriTemplate="/")]
[OperationContract]
public Users GetUsers()
{ ...code to get all users from database }
[WebInvoke(UriTemplate = "/", Method = "POST")]
[OperationContract]
public void AddUser(User user)
{ ...code to add user to database }
[WebInvoke(UriTemplate = "/{id}", Method = "PUT")]
[OperationContract]
public void UpdateUser(string id, User user)
{ ...code to update user in database }
[WebGet(UriTemplate = "/{id}")]
[OperationContract]
public User GetUser(string id)
{ ...code to get a single user from database }
}
(In addition I have classes for the User and Users entities)
Then on the front end I have this interface:
[ServiceContract]
interface ITestService
{
[WebGet(UriTemplate = "/")]
[OperationContract]
Users GetUsers();
[WebInvoke(UriTemplate = "/", Method = "POST")]
[OperationContract]
void AddUser(User user);
[WebInvoke(UriTemplate = "/{id}", Method = "PUT")]
[OperationContract]
void UpdateUser(string id, User user);
[WebGet(UriTemplate = "/{id}")]
[OperationContract]
User GetUser(string id);
}
And then I have this helper class:
public class ServiceClient
{
WebChannelFactory<ITestService> cf;
ITestService channel;
public ServiceClient()
{
cf = new WebChannelFactory<ITestService>(new Uri("http://myserver/service.svc"));
channel = cf.CreateChannel();
}
public Users GetUsers()
{ return channel.GetUsers(); }
public User GetUser(string id)
{ return channel.GetUser(id); }
public void AddUser(User user)
{ channel.AddUser(user); }
public void UpdateUser(string id, User user)
{ channel.UpdateUser(id, user); }
}
And finally here is what the code behind looks like on my page that is trying to do an update of a User object.
protected void btnSave_Click(object sender, EventArgs e)
{
User _user = new User(Convert.ToInt32(txtID.Value), txtFirstName.Text, txtLastName.Text, Convert.ToInt32(txtAge.Text), chkIsRegistered.Checked);
ServiceClient _client = new ServiceClient();
_client.UpdateUser(txtID.Value, _user);
Response.Redirect("~/ViewUsers.aspx");
}
When I run the front end project and try to do an update I get the following error:
The remote server returned an unexpected response: (405) Method Not Allowed.
Any ideas? Thanks, Corey

How much data are you sending? I have had trouble with WCF services when sending more than 64k at a time (with the default configuration, you can increase it). POST would typically return a 400 in this case, but I don't know what PUT would return, since my service doesn't use PUT.

I solved part of the problem. I deleted the WebDav module in IIS for the site and then the PUT began to work. (I think it has to do with WebDav handling the PUT and DELETE verbs) However the POST verb is not working. It is returning a 400 Bad Request error. I will try solving that in a separate question.

Related

Retrieving custom user roles after login - Blazor WASM

I have a Blazor WebAssembly (WASM) app that authenticates users using Okta. After they successfully log in via Okta, I want to authorize the user by calling an API that I wrote to retrieve that users roles and other general user info that we store. This call to get user info must also include the access token retrieved from the Okta log in.
The authentication piece with Okta works fine.
I'm not sure how to correctly call our API to get user info/roles as part of the login process, so that the roles can be added as claims BEFORE being redirected to any other page.
The Okta log in piece is set up using the RemoteAuthenticatorView and added in Program.Main as:
builder.Services.AddOidcAuthentication(options =>
{
options.ProviderOptions.Authority = builder.Configuration.GetValue<string>("Okta:Authority");
options.ProviderOptions.ClientId = builder.Configuration.GetValue<string>("Okta:ClientId");
options.ProviderOptions.ResponseType = "code";
});
What I've tried so far:
Using the OnLogInSucceeded event callback of the RemoteAuthenticatorView. This doesn't work because the user will be redirected to the page they tried to access before the api call completes. Therefore if the page has any Authorize(Roles="Admin") type of restrictions on it, those roles haven't been populated yet.
Using a factory that inherits from AccountClaimsPrincipalFactory. This seems like the correct way, however I'm getting runtime errors anytime I inject certain classes or services into my factory. I think I've narrowed it down to being an issue with an injected service using the IHttpClientFactory. Here's my factory code:
public class ClaimsPrincipalFactory : AccountClaimsPrincipalFactory
{
private IUserService userService { get; set; }
public ClaimsPrincipalFactory(
IAccessTokenProviderAccessor accessor,
IUserService userService
)
: base(accessor)
{
this.userService = userService;
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account, RemoteAuthenticationUserOptions options)
{
var user = await base.CreateUserAsync(account, options);
var userInfo = await userService.UserInfo();
var identity = user.Identity as ClaimsIdentity;
if (userInfo != null)
{
foreach (var role in userInfo.UserRoles)
{
identity.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, role.ApplicationRole.Name));
}
}
return user;
}
}
Here is the constructor of my UserService:
public UserService(IHttpClientFactory clientFactory)
{
http = clientFactory.CreateClient("BlazorClient.ServerApi");
}
The CreateClient line causes this runtime error:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: ValueFactory attempted to access the Value property of this instance.
System.InvalidOperationException: ValueFactory attempted to access the Value property of this instance.
at System.Lazy`1[[Microsoft.Extensions.Http.ActiveHandlerTrackingEntry, Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ViaFactory(LazyThreadSafetyMode mode)
at System.Lazy`1[[Microsoft.Extensions.Http.ActiveHandlerTrackingEntry, Microsoft.Extensions.Http, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].ExecutionAndPublication(LazyHelper executionAndPublication, Boolean useDefaultConstructor)
Here is how the httpFactory is set up in my Program file:
builder.Services
.AddHttpClient("BlazorClient.ServerApi", client => client.BaseAddress = new Uri(builder.Configuration.GetValue<string>("ServerApi:BaseAddress")))
.AddHttpMessageHandler<CorsRequestAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("BlazorClient.ServerApi"));
Here is how the Factory is added in Program:
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<ClaimsPrincipalFactory>();
What is the correct way of doing this? I've been stuck on this issue for literally days and it doesn't seem like it should be this hard (and so hard to find documented info on it).
I was strugling with the same issue and based on your code snippet I might solved it.
What I did is to pass a HttpClientFactory to the generator of the CustomUserFactory, then in the CreateUser func I can create my userService with this factory.
Hope it's an ok solution and helps you as well.
public class CustomUserFactory : AccountClaimsPrincipalFactory<CustomUserAccount>
{
private IUserService _userService { get; set; }
private IHttpClientFactory _httpClientFactory { get; set; }
public CustomUserFactory(IAccessTokenProviderAccessor accessor, IHttpClientFactory httpClientFactory)
: base(accessor)
{
_httpClientFactory = httpClientFactory;
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(CustomUserAccount account, RemoteAuthenticationUserOptions options)
{
var initialUser = await base.CreateUserAsync(account, options);
_userService = new UserService(_httpClientFactory);
...

Exclude Controller from Middleware

I have wrote a Middleware which checks if Authorization Token is included in the header and based on that request are executed or returns error if token is missing. Now it is working fine for other Controllers.
But What should I do for Login/Registration Controller which don't required Authorization headers. How can I configure my Middleware to ignore these.
Current Implementation of MiddleWare to Check Headers for Authorization Token.
public class AuthorizationHeaderValidator
{
private readonly RequestDelegate _next;
private readonly ILogger<AuthorizationHeaderValidator> _logger;
public AuthorizationHeaderValidator(RequestDelegate next, ILogger<AuthorizationHeaderValidator> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
StringValues authorizationHeader;
Console.WriteLine(context.Request.Path.Value);
if (context.Request.Headers.TryGetValue("Authorization", out authorizationHeader))
{
await _next(context);
}
else
{
_logger.LogError("Request Failed: Authorization Header missing!!!");
context.Response.StatusCode = 403;
var failureResponse = new FailureResponseModel()
{
Result = false,
ResultDetails = "Authorization header not present in request",
Uri = context.Request.Path.ToUriComponent().ToString(),
Timestamp = DateTime.Now.ToString("s", CultureInfo.InvariantCulture),
Error = new Error()
{
Code = 108,
Description = "Authorization header not present in request",
Resolve = "Send Request with authorization header to avoid this error."
}
};
string responseString = JsonConvert.SerializeObject(failureResponse);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(responseString);
return;
}
}
}
This is not a complete answer but only directions. Please post your code once you finish this task for next generations.
It seems you need a Filter and not Middlware as Middleware don't have access to rout data. Create new authorization filter by inheriting from Attribute and implementing IAuthorizationFilter or IAsyncAuthorizationFilter. There is only one method to implement
public void OnAuthorization(AuthorizationFilterContext context)
{
}
or
public Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
}
Decorate controllers and/or actions that you want to exclude from this logic with AllowAnonymousAttribute. Inside your OnAuthorization method check if current action or controller has AllowAnonymousAttribute and if it is return without setting Result on AuthorizationFilterContext. Otherwise execute the logic from you original Middleware and set Result property. Setting Result will short-circuit the remainder of the filter pipeline.
Then register your filter globally:
services.AddMvc(options =>
{
options.Filters.Add(new CustomAuthorizeFilter());
});
Not sure why you need middleware to validate if the Authorization header is present. It's difficult to exclude the controllers this way as all requests will go through this middleware before they hit the MVC pipeline.
[Authorize] attribute will do the job for you, given that you have some form of authentication integrated. If you need to exclude the controllers which don't require authorization, you can simply add [AllowAnonymous] at the controller level or at the action method level. Please see the code snippet below from the Microsoft Docs
[Authorize]
public class AccountController : Controller
{
[AllowAnonymous]
public ActionResult Login()
{
}
public ActionResult Logout()
{
}
}
If you must use a middleware, you can consider using it as an MVC filter, which means that it will be scoped to the MVC pipeline. For more details, please see this link. However, that will still not solve the problem to exclude the controllers without adding some convoluted logic, which can be quite complicated.
I have solved my problem by Implementing PipeLine
public class AuthorizationMiddlewarePipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
applicationBuilder.UseMiddleware<AuthorizationHeaderValidator>();
}
}
And than I am using it like this on either Controller Scope or Method scope
[MiddlewareFilter(typeof(AuthorizationMiddlewarePipeline))]

Spring MVC session resource is getting shared by logged users

I have deployed a web application using Apache tomcat 8, java 8 and centos server in production.
When i tested the system by 5-6 users concurrently in office network everything seemed ok. But in client network, one users info is getting by another user(session attributes are shared/mixup). For example, if user A logs in, after a while his name is showing user B, who is logged in from different computer. If user presses Ctrl+R then his/her previous session restores for a while.
N.B. this scenario never happens other than that client network. They are using specific proxy. Other than proxy, this scenario does not happen.
I have a simple LoginController class without defining any scope. Some code snippets are below:
1. Login GET method:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public String getLogin(#ModelAttribute LoginForm loginForm)
{
return "login";
}
2. Login POST method:
#RequestMapping("/login", RequestMethod.POST)
public String Login(#ModelAttribute LoginForm loginForm, HttpSession session)
{
User dbUser = this.userService.getUser(loginForm.getUserID());
if (dbUser != null)
{
if(passwordCheckedSuccess(dbUser.getPassword(), loginForm.getPassword()))
{
session.setAttribute("userName", dbUser.getUserName());
session.setAttribute("userId", dbUser.getUserId()); // primary key of user class
return "dashboard";
}
else
{
return "login";
}
}
}
3. I have created a loginIntercptor class to filter secured pages:
public class LoginInterceptor extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
if (!request.getRequestURI().endsWith("/login"))
{
if (request.getSession().getAttribute("userId") == null)
{
response.sendRedirect(request.getContextPath() + "/login");
return false;
}
}
return true;
}
}
I am not using spring security.
Please suggest a way to get rid of it. Thanks.

Not able to access SpringContext after integrate Atmosphere framework with spring MVC

As I mentioned in the title that After integrating Atmosphere framework with Spring MVC, I want to access current LoggedIn User by the help of SecurityContext(Or Authentication), But gives me null when I send the ajax request from the JSP page(client side).
Is there any problem with my code or some other problem. Below are my MyController.java and JSP files
MyController.Java
#Controller
public class MyController {
#Autowired
PartyManagementService partyManagementService;
#RequestMapping(value = "/websockets", method = RequestMethod.POST)
#ResponseBody
public String post(final AtmosphereResource event, #RequestBody String message)
throws JsonGenerationException, JsonMappingException, IOException {
User user = (User)SecurityContextHolder.getContext().
getAuthentication().getPrincipal();
String username = user.getUsername();
Person person = partyManagementService.getPersonFromUsername(username);
logger.info("Received message to broadcast: {}", message);
final int noClients = event.getBroadcaster().getAtmosphereResources().size();
event.getBroadcaster().broadcast(message);
return " message successfully send ";
}
}
home.jsp
In my home jsp, I provide ajax request which pushes the message to the server.

using WCF Callback and asp.net to implement publish/subscribe pattern

This is my first web application with WCF. So please guide me as a new guy.
I'm trying to use WCF Callback to implement publish/subscribe pattern. I would like to send the message from UserA to UserB or UserA to every client at the moment. I got an example from here .
In my app, I use ASP.NET as a client to connect WCF service instead and I found a problem when I subscribe to WCF service.
The WCF service does not hold any other clients object. So when I call GetAllClients(_guid), it will return only 1 client which is itself.
Here is the code in ASP.NET page (I put every control inside updatePanel)
public partial class _Default : System.Web.UI.Page, AlertServiceCallback
{
private AlertServiceClient _client;
private Guid _guid = Guid.NewGuid();
protected void Page_Load(object sender, EventArgs e)
{
InstanceContext context = new InstanceContext(this);
_client = new AlertServiceClient(context);
_client.RegisterClient(_guid);
}
protected void btnGetClients_Click(object sender, EventArgs e)
{
//Try to retrive all active clients
Client[] cs = _client.GetAllClients(_guid);
List<Client> list = new List<Client>(cs);
//Bind to dropDownList to display all active clients
ddlClients.DataSource = list;
ddlClients.DataBind();
}
#region "CallBack"
public void OnRegister(string message)
{
throw new NotImplementedException();
}
public void OnMessageSending(string message)
{
throw new NotImplementedException();
}
#endregion
}
Here is the IService and Service on WCF respectively.
[ServiceContract(Name = "AlertService",Namespace = "http://www.testWcf.com/",
CallbackContract = typeof(IAlertCallBack),SessionMode = SessionMode.Required)]
public interface IAlertService
{
[OperationContract(IsOneWay = true)]
void RegisterClient(Guid id, string name);
[OperationContract(IsOneWay = false)]
List<Client> GetAllClients(Guid id);
[OperationContract(IsOneWay = true)]
void SendMessage(Guid fromId, Guid toId, string message);
}
public interface IAlertCallBack
{
[OperationContract(IsOneWay = true)]
void OnRegister(string message);
[OperationContract(IsOneWay = true)]
void OnMessageSending(string message);
}
public class AlertService : IAlertService
{
private object locker = new object();
private Dictionary<Client, IAlertCallBack> clients = new Dictionary<Client, IAlertCallBack>();
public AlertService() { }
public void RegisterClient(Guid guid)
{
IAlertCallBack callback = OperationContext.Current.GetCallbackChannel<IAlertCallBack>();
//---prevent multiple clients adding at the same time---
lock (locker)
{
clients.Add(new Client { Id = guid, Name = name }, callback);
}
}
public List<Client> GetAllClients(Guid guid)
{
//---get all the clients in dictionary---
return (from c in clients
where c.Key.Id != guid
select c.Key).ToList();
}
...
}
Questions are:
Is it possible to implement this publish/subscribe with ASP.NET and WCF callback? (I already tried on window app and it worked fine)
If it is possible, how can I keep all clients that is connecting to WCF Service so I can use those GuId to call callback method.
Thank you.
I don't know why you don't get list of clients - you should but there are much worse problems with your code.
You can't use WCF callback in ASP.NET application. It cannot work because page lives only to serve single HTTP request - it means it most probably lives only for fraction of second and so also your registration. Even If you will be able to get list of clients you will not be able to call OnRegister or OnMessageSending because there will be no proxy listening for these calls.
Even if you force proxy to live after request processing it will still notify only code behind, not your pages rendered in client browser.
Another problem is that it can work only with net.pipe or net.tcp binding. It will not work with wsDualHttp. It is very problematic to open multiple duplex clients from the same machine when using wsDualHttp.
You are doing it completely wrong. What you need is AJAX polling from client browser to asp.net which will call simple service in your chat system.

Resources