HttpOperationHandlerFactory and InputParameters issue - c#

I've written a custom OperationHandler for my WCF WebAPI project as follows:
public class AuthenticationOperationHandler : HttpOperationHandlerFactory
{
protected override Collection<HttpOperationHandler> OnCreateRequestHandlers(ServiceEndpoint endpoint, HttpOperationDescription operation)
{
var baseHandlers = base.OnCreateRequestHandlers(endpoint, operation);
if (operation.InputParameters.Where(p => p.Name.ToLower() == "username").Any() &&
operation.InputParameters.Where(p => p.Name.ToLower() == "password").Any())
{
baseHandlers.Add(new AuthenticateRequestHandler(string.Format("{0}:{1}", operation.InputParameters.Where(p => p.Name == "username").First ().Name, operation.InputParameters.Where(p => p.Name == "password").First().Name)));
}
else
{
throw new WebFaultException(HttpStatusCode.Forbidden);
}
return baseHandlers;
}
}
As well as this custom RequestHandler which is added to the pipeline:
public class AuthenticateRequestHandler : HttpOperationHandler<HttpRequestMessage, string>
{
public AuthenticateRequestHandler(string outputParameterName)
: base(outputParameterName)
{
}
public override string OnHandle(HttpRequestMessage input)
{
var stringValue = input.Content.ReadAsString();
var username = stringValue.Split(':')[0];
var password = stringValue.Split(':')[1];
var isAuthenticated = ((BocaMembershipProvider)Membership.Provider).ValidateUser(username, password);
if (!isAuthenticated)
{
throw new WebFaultException(HttpStatusCode.Forbidden);
}
return stringValue;
}
}
and this is my API Implementation:
[ServiceContract]
public class CompanyService
{
[WebInvoke(UriTemplate = "", Method = "POST")]
public bool Post(string username, string password)
{
return true;
}
}
My configuration in Global.asax file is
public static void RegisterRoutes(RouteCollection routes)
{
var config = HttpHostConfiguration.Create().SetOperationHandlerFactory(new AuthenticationOperationHandler());
routes.MapServiceRoute<AuthenticationService>("login", config);
routes.MapServiceRoute<CompanyService>("companies", config);
}
When trying to send a POST request to /companies I receive the following error message:
The HttpOperationHandlerFactory is unable to determine the input
parameter that should be associated with the request message content
for service operation 'Post'. If the operation does not expect content
in the request message use the HTTP GET method with the operation.
Otherwise, ensure that one input parameter either has it's
IsContentParameter property set to 'True' or is a type that is
assignable to one of the following: HttpContent, ObjectContent1,
HttpRequestMessage or HttpRequestMessage1.
on this line:
var baseHandlers = base.OnCreateRequestHandlers(endpoint, operation);
Any idea why this happens and how to fix this in order to force user send username/password parameters in each and every request and validate it against the Membership API afterwards?

To answer your question, your UriTemplate property is empty, which is why an exception is thrown. It should be set as follows:
UriTemplate = "&username={username}&password={password}"
There's still a bug in your code because both input parameters receive the same string, namely username=JohnDoe:password=qwerty
To solve your problem, this is a good article on how to implement HTTP basic authentication with WCF Web API.

Related

Authenticating a Callback URL in a REST API

We are testing Azure Communication Services in a new project. Specifically, we are looking at the Azure Communication Services for Calling documented here and the quick start project found here.
The general pattern to utilize the service is shown in the following code.
public string AppCallbackUrl => $"{AppBaseUrl}/api/outboundcall/callback?{EventAuthHandler.GetSecretQuerystring}"
// Defined the call with a Callback URL
var source = new CommunicationUserIdentifier(callConfiguration.SourceIdentity);
var target = new PhoneNumberIdentifier(targetPhoneNumber);
var createCallOption = new CreateCallOptions(
new Uri(AppCallbackUrl),
new List<MediaType> { MediaType.Audio },
new List<EventSubscriptionType> { EventSubscriptionType.DtmfReceived });
// Initiate the call
var call = await callClient.CreateCallConnectionAsync(
source, new List<CommunicationIdentifier>() { target }, createCallOption, reportCancellationToken)
.ConfigureAwait(false);
// Register for call back events
RegisterToCallStateChangeEvent(call.Value.CallConnectionId);
The example uses a configuration value or hardcoded secret key to authenticate the Callback Url, as shown below.
[Route("api/[controller]")]
[ApiController]
public class OutboundCallController : ControllerBase
{
[AllowAnonymous]
[HttpPost("callback")]
public async Task<IActionResult> OnIncomingRequestAsync()
{
// Validating the incoming request by using secret set in app.settings
if (EventAuthHandler.Authorize(Request))
{
...
}
else
{
return StatusCode(StatusCodes.Status401Unauthorized);
}
}
}
public class EventAuthHandler
{
private static readonly string SecretKey = "secret";
private static readonly string SecretValue;
static EventAuthHandler()
{
SecretValue = ConfigurationManager.AppSettings["SecretPlaceholder"] ?? "h3llowW0rld";
}
public static bool Authorize(HttpRequest request)
{
if (request.QueryString.Value != null)
{
var keyValuePair = HttpUtility.ParseQueryString(request.QueryString.Value);
return !string.IsNullOrEmpty(keyValuePair[SecretKey]) && keyValuePair[SecretKey].Equals(SecretValue);
}
return false;
}
public static string GetSecretQuerystring => $"{SecretKey}={HttpUtility.UrlEncode(SecretValue)}";
}
Is there a better way to do this in a production environment? How can I incorporate ASP.NET Core authentication with a Callback?

How do I create Basic Authentication in Web Api?

I have the following .cs in order to create some basic authentication in my api. This works fine,but it appears only one time, when i run it for the first time.How do I make it appear again (in every run)?
namespace CMob
{
public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
public override void OnAuthorization(HttpActionContext actionContext)
{
var authHeader = actionContext.Request.Headers.Authorization;
if (authHeader != null)
{
var authenticationToken = actionContext.Request.Headers.Authorization.Parameter;
var decodedAuthenticationToken = Encoding.UTF8.GetString(Convert.FromBase64String(authenticationToken));
var usernamePasswordArray = decodedAuthenticationToken.Split(':');
var userName = usernamePasswordArray[0];
var password = usernamePasswordArray[1];
var isValid = userName == "chrysa" && password == "1234";
if (isValid)
{
var principal = new GenericPrincipal(new GenericIdentity(userName), null);
Thread.CurrentPrincipal = principal;
return;
}
}
HandleUnathorized(actionContext);
}
private static void HandleUnathorized(HttpActionContext actionContext)
{
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
actionContext.Response.Headers.Add("WWW-Authenticate", "Basic Scheme='Data' location = 'http://localhost:");
}
}
}ยจ
My controller
public class DController : ApiController
{
[BasicAuthentication]
[Route("api/D")]
public IEnumerable<D> Get()
{
using (CM_DataEntities entities = new CM_DataEntities())
{
return entities.Ds.ToList();
}
}
}
Thanks!
"To unauthenticated requests, the server should return a response whose header contains a HTTP 401 Unauthorized status[4] and a WWW-Authenticate field.[5]"
You should refer to https://en.wikipedia.org/wiki/Basic_access_authentication.
I am quite sure you can find the answer you're looking for over there.
Basically, The browser provides authentication, and you have absolutely no control over it.
You have to declare the attribute in WebApiConfig.cs :
config.Filters.Add(new BasicAuthenticationAttribute());
And you have to decorate your Controllers and or Actions :
public class MyController : ApiController
{
[BasicAuthentication]
public string Get()
{
return "Hello";
}
}
It actually depends on what behavior you want to define.
If you wish to use your authentication filter for your whole API, you can add it to the global filter list this way (in WebApiConfig.cs) :
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new BasicAuthenticationAttribute());
}
If you desire to restrict all methods of a controller, decorate it this way :
[BasicAuthentication]
public class RestrictedController : ApiController
{
//Your controller definition
}
Of course you can use it on a single method, this way :
[BasicAuthentication]
public JsonResult GetJsonDataAsAuthenticatedUser()
{
//your method definition
}
You can specify a method which require no authentication with AllowAnonymous decoration :
[BasicAuthentication]
public class RestrictedController : ApiController
{
[AllowAnonymous]
public IActionResult Authenticate()
{
//Your authentication entry point
}
}
You can refer to this link

Forward values from custom HttpControllerSelector and HttpActionSelector to its descriptor

I have a MVC 5 Web API which returns a custom response in case of unexpected exceptions or if the controller or action were not found. Essentially, I've done exactly as shown there: http://weblogs.asp.net/imranbaloch/handling-http-404-error-in-asp-net-web-api Everything's working like a charm.
The problem is: I'd like to submit the error code from SelectController() and SelectAction() to my ErrorController. This way I would not have duplicate code and all the logic would be in the controller.
Unfortunately, I do not find any possible way to submit the error code to my controller. All the examples are redirecting to a specific error action (e.g. ErrorController.NotFound404) I'd like to redirect to ErrorController.Main and do all the magic there.
Another issue with the custom ApiControllerActionSelector is that the Request property is null in the ErrorController. This problem does not exist with the custom DefaultHttpControllerSelector.
Any ideas?
Best regards,
Carsten
Fortunately, I was able to find the solution myself. Let me show you how I got it up and running.
The custom controller and action selector are forwarding the requested language and the current HTTP response code:
public class CustomDefaultHttpControllerSelector: DefaultHttpControllerSelector
{
public CustomDefaultHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
HttpControllerDescriptor descriptor = null;
try
{
descriptor = base.SelectController(request);
}
catch (HttpResponseException e)
{
var routeValues = request.GetRouteData().Values;
routeValues.Clear();
routeValues["controller"] = "Error";
routeValues["action"] = "Main";
routeValues["code"] = e.Response.StatusCode;
routeValues["language"] = request.Headers?.AcceptLanguage?.FirstOrDefault()?.Value ?? "en";
descriptor = base.SelectController(request);
}
return descriptor;
}
}
public class CustomControllerActionSelector: ApiControllerActionSelector
{
public CustomControllerActionSelector()
{
}
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
HttpActionDescriptor descriptor = null;
try
{
descriptor = base.SelectAction(controllerContext);
}
catch (HttpResponseException e)
{
var routeData = controllerContext.RouteData;
routeData.Values.Clear();
routeData.Values["action"] = "Main";
routeData.Values["code"] = e.Response.StatusCode;
routeData.Values["language"] = controllerContext.Request?.Headers?.AcceptLanguage?.FirstOrDefault()?.Value ?? "en";
IHttpController httpController = new ErrorController();
controllerContext.Controller = httpController;
controllerContext.ControllerDescriptor = new HttpControllerDescriptor(controllerContext.Configuration, "Error", httpController.GetType());
descriptor = base.SelectAction(controllerContext);
}
return descriptor;
}
}
Two important changes:
1.1. The list of route values needs to be cleared. Otherwise it tries to find an action in the ErrorController which maps to this list of values.
1.2. The code and language were added.
The ErrorController itself:
[RoutePrefix("error")]
public class ErrorController: BaseController
{
[HttpGet, HttpPost, HttpPut, HttpDelete, HttpHead, HttpOptions, AcceptVerbs("PATCH")]
[Route("{code}/{language}")]
public HttpResponseMessage Main(string code, string language)
{
HttpStatusCode parsedCode;
var responseMessage = new HttpResponseMessage();
if (!Enum.TryParse(code, true, out parsedCode))
{
parsedCode = HttpStatusCode.InternalServerError;
}
responseMessage.StatusCode = parsedCode;
...
}
}
I've removed the route mapping routes.MapHttpRoute(...). No matter what I've entered in the browser, it never called Handle404.
HTTP status 400 (bad request) was not covered, yet. This could be easily achieved by using the ValidationModelAttribute as described on http://www.asp.net/web-api/overview/formats-and-model-binding/model-validation-in-aspnet-web-api (section "Handling Validation Errors").
Maybe this will help someone...

Custom authorization IActionResults in aspnet-5 mvc-6

In ASP.NET 4 MVC5, I had this class that allowed me to return custom responses for unauthenticated responses to JSON endpoints. Here it is.
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (IsAjax(filterContext))
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
success = false,
error = "You must be signed in."
}
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
private bool IsAjax(AuthorizationContext filterContext)
{
return filterContext.ActionDescriptor.GetFilterAttributes(true).OfType<AjaxAttribute>().FirstOrDefault() !=
null;
}
}
However, in MVC6, the new AuthorizeAttribute is no overrides for creating custom IActionResult results. How do I do this in MVC6?
A good point has been made by #blowdart in his comment about whether returning 401/403 should be the expected behaviour. In any case, I have tried a different approach for doing what the OP was asking, modifying the behavior of the default MVC authorization filters so that we return a json when user is unauthorized.
First thing I did was creating a new IAsyncAuthorizationFilter that will format the unauthorized result as a json for ajax request. It will basically:
Wrap an existing filter
Execute the wrapped filter
In case the user is unauthorized by the wrapped filter, return a json for ajax requests
This would be the CustomJsonAuthorizationFilter class:
public class CustomJsonAuthorizationFilter : IAsyncAuthorizationFilter
{
private AuthorizeFilter wrappedFilter;
public CustomJsonAuthorizationFilter(AuthorizeFilter wrappedFilter)
{
this.wrappedFilter = wrappedFilter;
}
public async Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
{
await this.wrappedFilter.OnAuthorizationAsync(context);
if(context.Result != null && IsAjaxRequest(context))
{
context.Result = new JsonResult(new
{
success = false,
error = "You must be signed in."
});
}
return;
}
//This could be an extension method of the HttpContext/HttpRequest
private bool IsAjaxRequest(Microsoft.AspNet.Mvc.Filters.AuthorizationContext filterContext)
{
return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest";
}
}
Then I have created an IApplicationModelProvider in order to wrap all existing AuthorizeFilter with the new custom filter. The AuthroizeFilter is added by AuthorizationApplicationModelProvider, but the new provider will be run after the default one since the order of the default provider is -990.
public class CustomFilterApplicationModelProvider : IApplicationModelProvider
{
public int Order
{
get { return 0; }
}
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{
//Do nothing
}
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
this.ReplaceFilters(context.Result.Filters);
foreach(var controller in context.Result.Controllers)
{
this.ReplaceFilters(controller.Filters);
foreach (var action in controller.Actions)
{
this.ReplaceFilters(action.Filters);
}
}
}
private void ReplaceFilters(IList<IFilterMetadata> filters)
{
var authorizationFilters = filters.OfType<AuthorizeFilter>().ToList();
foreach (var filter in authorizationFilters)
{
filters.Remove(filter);
filters.Add(new CustomJsonAuthorizationFilter(filter));
}
}
}
Finally, update ConfigureServices in startup with the new application model provider:
services.TryAddEnumerable(
ServiceDescriptor.Transient<IApplicationModelProvider, CustomFilterApplicationModelProvider>());
I finally figured it out after looking at the source.
public class CustomCookieAuthenticationEvents : CookieAuthenticationEvents
{
Func<CookieRedirectContext, Task> _old;
public CustomCookieAuthenticationEvents()
{
_old = OnRedirectToLogin;
OnRedirectToLogin = OnCustomRedirectToLogin;
}
public Task OnCustomRedirectToLogin(CookieRedirectContext context)
{
var actionContext = context.HttpContext.RequestServices.GetRequiredService<IActionContextAccessor>();
if (actionContext.ActionContext == null)
return _old(context);
if (actionContext.ActionContext.ActionDescriptor.FilterDescriptors.Any(x => x.Filter is AjaxAttribute))
{
// this is an ajax request, return custom JSON telling user that they must be authenticated.
var serializerSettings = context
.HttpContext
.RequestServices
.GetRequiredService<IOptions<MvcJsonOptions>>()
.Value
.SerializerSettings;
context.Response.ContentType = "application/json";
using (var writer = new HttpResponseStreamWriter(context.Response.Body, Encoding.UTF8))
{
using (var jsonWriter = new JsonTextWriter(writer))
{
jsonWriter.CloseOutput = false;
var jsonSerializer = JsonSerializer.Create(serializerSettings);
jsonSerializer.Serialize(jsonWriter, new
{
success = false,
error = "You must be signed in."
});
}
}
return Task.FromResult(0);
}
else
{
// this is a normal request to an endpoint that is secured.
// do what ASP.NET used to do.
return _old(context);
}
}
}
Then, use this event class as follows:
services.Configure<IdentityOptions>(options =>
{
options.Cookies.ApplicationCookie.Events = new CustomCookieAuthenticationEvents();
});
ASP.NET 5 sure made simple things harder to do. Granted though, I can now customize things at a more granular level without effecting other pieces. Also, the source code is amazingly easy to read/understand. I am pretty happy having the confidence that any issue I am having can easily be identified as a bug or resolved by looking at the source.
Cheers to the future!

How to re-route request from one route to another in ASP.NET Web API?

I'm building dynamic routing system in Web API service.
For example, service might have several routes (I don't know exact count and prefixes on application start). I want to create some custom route that will decide which route to choose (some kind of internal redirection to another route), initialize it on the first request and pass the request to it.
I've tried to inherit from HttpRoute and override GetRouteData, but for some reason after changing the route in IHttpRouteData server returns me 406 Not Accepted status code. Simple example with only one route for substitution:
public static void Register(HttpConfiguration config) // on Application_Start
{
WebApiConfig.Configuration.Routes.Add("InitRoute", new LazyInitializationRoute("myservice/{*params}"));
}
public class LazyInitializationRoute : HttpRoute
{
private static bool _initialized = false;
public LazyInitializationRoute(string routeTemplate)
: base(routeTemplate) { }
public override IHttpRouteData GetRouteData(string virtualPathRoot, HttpRequestMessage request)
{
IHttpRouteData baseRouteData = base.GetRouteData(virtualPathRoot, request);
if (baseRouteData == null) return null;
if (!_initialized)
{
WebApiConfig.Configuration.Routes.MapHttpRoute("MyRoute", "myservice");
_initialized = true;
}
IHttpRoute odataRoute = WebApiConfig.Configuration.Routes["MyRoute"];
var values = baseRouteData.Values;
IHttpRouteData routeData = values == null ? new HttpRouteData(odataRoute) : new HttpRouteData(odataRoute, new HttpRouteValueDictionary(values));
return routeData;
}
}
Please help. How can I achieve desired behavior?

Categories

Resources