I added a consumer to observe routing slip events, but doesn't work as expected. RoutingSlipCompleted consumer is always triggered, RoutingSlipActivityCompleted and RoutingSlipActivityFaulted consumer are never triggered. This is my consumer code.
public abstract class RoutingSlipExecuteActivityResponseProxy<TRequest, TResponse, TFaultResponse> :
IConsumer<RoutingSlipActivityCompleted>,
IConsumer<RoutingSlipActivityFaulted>,
IConsumer<RoutingSlipCompleted>
where TRequest : class
where TResponse : class
where TFaultResponse : class
{
public abstract string ActivityName { get; }
public async Task Consume(ConsumeContext<RoutingSlipActivityCompleted> context)
{
if(context.Message.ActivityName!= ActivityName)
{
return;
}
var request = context.Message.GetVariable<TRequest>("Request");
var requestId = context.Message.GetVariable<Guid>("RequestId");
Uri responseAddress = null;
if (context.Message.Variables.ContainsKey("ResponseAddress"))
responseAddress = context.Message.GetVariable<Uri>("ResponseAddress");
if (responseAddress == null)
throw new ArgumentException($"The response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");
var endpoint = await context.GetResponseEndpoint<TResponse>(responseAddress, requestId).ConfigureAwait(false);
var response = await CreateResponseMessage(context, request);
await endpoint.Send(response).ConfigureAwait(false);
}
public async Task Consume(ConsumeContext<RoutingSlipActivityFaulted> context)
{
if (context.Message.ActivityName != ActivityName)
{
return;
}
var request = context.Message.GetVariable<TRequest>("Request");
var requestId = context.Message.GetVariable<Guid>("RequestId");
Uri faultAddress = null;
if (context.Message.Variables.ContainsKey("FaultAddress"))
faultAddress = context.Message.GetVariable<Uri>("FaultAddress");
if (faultAddress == null && context.Message.Variables.ContainsKey("ResponseAddress"))
faultAddress = context.Message.GetVariable<Uri>("ResponseAddress");
if (faultAddress == null)
throw new ArgumentException($"The fault/response address could not be found for the faulted routing slip: {context.Message.TrackingNumber}");
var endpoint = await context.GetFaultEndpoint<TResponse>(faultAddress, requestId).ConfigureAwait(false);
var response = await CreateFaultedResponseMessage(context, request, requestId);
await endpoint.Send(response).ConfigureAwait(false);
}
protected abstract Task<TResponse> CreateResponseMessage(ConsumeContext<RoutingSlipActivityCompleted> context, TRequest request);
protected abstract Task<TFaultResponse> CreateFaultedResponseMessage(ConsumeContext<RoutingSlipActivityFaulted> context, TRequest request, Guid requestId);
public Task Consume(ConsumeContext<RoutingSlipCompleted> context)
{
throw new NotImplementedException();
}
}
My activity has no additional configuration, basically it is written according to the documentation.
You might want to check out this sample, which uses the RequestResponseProxy to handle a request via routing slip, and then generates the response based upon the RoutingSlipCompleted/RoutingSlipFaulted events.
Related
I am building a new .Net 6 WebAPI that will be consumed by many applications so I need to implement API Keys to limit access to only those applications. Only a very small amount of the individual users will require authorization (admins) so I would like to combine with JWT for the Admin endpoints. We do not want to require users to have to crate an account where not necessary (non-admins). Is this possible? Thank You.
Yes it is possible.
The solution I recommend is to setup multiple authentication methods in asp.net core 6 using two authentication schemes that you have to specify inside Authorize attribute.
Here a simple implementation of ApiKey authentication:
namespace MyAuthentication;
public class ApiKeyAuthenticationHandler : AuthenticationHandler<ApiKeyAuthenticationOptions>
{
private enum AuthenticationFailureReason
{
NONE = 0,
API_KEY_HEADER_NOT_PROVIDED,
API_KEY_HEADER_VALUE_NULL,
API_KEY_INVALID
}
private readonly Microsoft.Extensions.Logging.ILogger _logger;
private AuthenticationFailureReason _failureReason = AuthenticationFailureReason.NONE;
public ApiKeyAuthenticationHandler(IOptionsMonitor<ApiKeyAuthenticationOptions> options,
ILoggerFactory loggerFactory,
ILogger<ApiKeyAuthenticationHandler> logger,
UrlEncoder encoder,
ISystemClock clock) : base(options, loggerFactory, encoder, clock)
{
_logger = logger;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//ApiKey header get
if (!TryGetApiKeyHeader(out string providedApiKey, out AuthenticateResult authenticateResult))
{
return authenticateResult;
}
//TODO: you apikey validity check
if (await ApiKeyCheckAsync(providedApiKey))
{
var principal = new ClaimsPrincipal(); //TODO: Create your Identity retreiving claims
var ticket = new AuthenticationTicket(principal, ApiKeyAuthenticationOptions.Scheme);
return AuthenticateResult.Success(ticket);
}
_failureReason = AuthenticationFailureReason.API_KEY_INVALID;
return AuthenticateResult.NoResult();
}
protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
{
//Create response
Response.Headers.Append(HeaderNames.WWWAuthenticate, $#"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}""");
Response.StatusCode = StatusCodes.Status401Unauthorized;
Response.ContentType = MediaTypeNames.Application.Json;
//TODO: setup a response to provide additional information if you want
var result = new
{
StatusCode = Response.StatusCode,
Message = _failureReason switch
{
AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED => "ApiKey not provided",
AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL => "ApiKey value is null",
AuthenticationFailureReason.NONE or AuthenticationFailureReason.API_KEY_INVALID or _ => "ApiKey is not valid"
}
};
using var responseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(responseStream, result);
await Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
{
//Create response
Response.Headers.Append(HeaderNames.WWWAuthenticate, $#"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}""");
Response.StatusCode = StatusCodes.Status403Forbidden;
Response.ContentType = MediaTypeNames.Application.Json;
var result = new
{
StatusCode = Response.StatusCode,
Message = "Forbidden"
};
using var responseStream = new MemoryStream();
await JsonSerializer.SerializeAsync(responseStream, result);
await Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
#region Privates
private bool TryGetApiKeyHeader(out string apiKeyHeaderValue, out AuthenticateResult result)
{
apiKeyHeaderValue = null;
if (!Request.Headers.TryGetValue("X-Api-Key", out var apiKeyHeaderValues))
{
_logger.LogError("ApiKey header not provided");
_failureReason = AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED;
result = AuthenticateResult.Fail("ApiKey header not provided");
return false;
}
apiKeyHeaderValue = apiKeyHeaderValues.FirstOrDefault();
if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(apiKeyHeaderValue))
{
_logger.LogError("ApiKey header value null");
_failureReason = AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL;
result = AuthenticateResult.Fail("ApiKey header value null");
return false;
}
result = null;
return true;
}
private Task<bool> ApiKeyCheckAsync(string apiKey)
{
//TODO: setup your validation code...
return Task.FromResult<bool>(true);
}
#endregion
}
public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
public const string DefaultScheme = "ApiKey";
public static string Scheme => DefaultScheme;
public static string AuthenticationType => DefaultScheme;
}
public static class AuthenticationBuilderExtensions
{
public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, Action<ApiKeyAuthenticationOptions> options)
=> authenticationBuilder.AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(ApiKeyAuthenticationOptions.DefaultScheme, options);
}
Then register inside builder setup:
_ = services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = ApiKeyAuthenticationOptions.DefaultScheme;
options.DefaultChallengeScheme = ApiKeyAuthenticationOptions.DefaultScheme;
})
.AddApiKeySupport(options => { });
You have to also setup the standard JWT Bearer validation (I don't post it for the sake of brevity).
To protect your endpoint add the Authorize attribute like:
[Authorize(AuthenticationSchemes = ApiKeyAuthenticationOptions.DefaultScheme)] //ApiKey
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
//or..
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] //Jwt
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
//or..
[Authorize(AuthenticationSchemes = $"{JwtBearerDefaults.AuthenticationScheme},{ApiKeyAuthenticationOptions.DefaultScheme}" )] //ApiKey and Jwt
[HttpGet]
public async Task<IActionResult> Get()
{
//...omissis...
return null;
}
For me it is the best way so as to carry out the authorization check before the start of the application pipeline (fail fast) and to be able to create the user identity.
But if you don't need to put informations about the Api Key inside the ClaimsPrincipal and only check the validity of Api Key the simplest way to do that is:
Protect the "admin" actions with JWT auth (with Authorize attribute)
Setup and register a middleware to only check the Api Key in all actions
Here is an example:
public class SimpleApiKeyMiddleware
{
private static readonly string API_KEY_HEADER = "X-Api-Key";
private readonly RequestDelegate _next;
private readonly ILogger<SimpleApiKeyMiddleware> _logger;
public SimpleApiKeyMiddleware(RequestDelegate next, ILogger<SimpleApiKeyMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext httpContext)
{
//Get apikey header
if (!httpContext.Request.Headers.TryGetValue(API_KEY_HEADER, out var apiKey))
{
_logger.LogError("ApiKey not found inside request headers");
//Error and exit from asp.net core pipeline
await GenerateForbiddenResponse(httpContext, "ApiKey not found inside request headers");
}
else if (!await ApiKeyCheckAsync(apiKey))
{
_logger.LogError("ApiKey is not valid: {ApiKey}", apiKey);
//Error and exit from asp.net core pipeline
await GenerateForbiddenResponse(httpContext, "ApiKey not valid");
}
else
{
_logger.LogInformation("ApiKey validated: {ApiKey}", apiKey);
//Proceed with pipeline
await _next(httpContext);
}
}
private Task<bool> ApiKeyCheckAsync(string apiKey)
{
//TODO: setup your validation code...
return Task.FromResult<bool>(true);
}
private async Task GenerateForbiddenResponse(HttpContext context, string message)
{
context.Response.StatusCode = StatusCodes.Status403Forbidden;
context.Response.ContentType = MediaTypeNames.Application.Json;
using var responseStream = new MemoryStream();
await System.Text.Json.JsonSerializer.SerializeAsync(responseStream, new
{
Status = StatusCodes.Status403Forbidden,
Message = message
});
await context.Response.BodyWriter.WriteAsync(responseStream.ToArray());
}
}
Registration:
_ = app.UseMiddleware<ApiKeyMiddleware>(); //Register as first middleware to avoid other middleware execution before api key check
Usage:
//Admin: Jwt and Api Key check
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] //Jwt and Api Key
[HttpGet]
public async Task<IActionResult> MyAdminApi()
{
//...omissis...
}
//Non Admin: Api Key check only
[HttpGet]
public async Task<IActionResult> MyNonAdminApi()
{
//...omissis...
}
Note: the middleware code above forces exit from pipeline returning an http result so as to stop next middleware execution. Also note that the asp.net core 6 pipeline executes Authorization first and then all the registered middlewares.
I am trying to execute custom asyncCodeActivity in UIPath. Added the package, passing all data, however UIPath just hangs when it reaches custom activity and does not throw any exceptions/or stops. I tried to create Class Library using CodeActivity and AsyncCodeActivity - my activity should make several APICalls but I get result it just stops when it reaches my custom activity and does not go to the next one. Is there any example how to create async custom activity for UIPath? My class library worked ok when I tried to test it outside of UIpath. Will appreciate any help.
My class library using CodeActivity:
public class AddInvoice : CodeActivity
{
[Category("Input")]
[RequiredArgument]
public InArgument<string> PickupZip { get; set; }
[Category("Output")]
[RequiredArgument]
public OutArgument<String> Output { get; set; }
public async Task<string> ApiTest(CodeActivityContext context)
{
try
{
var origin = await GoogleAPIWrapper.GetAddressByZip(PickupZip.Get(context));
string PickupAddress;
string DeliveryAddress;
var inv = new IList();
if (origin.StatusId >= 0)
{
invoice.PickupCity = origin.Locality;
invoice.PickupState = origin.AdminLevel1;
}
else
{
invoice.PickupCity = null;
invoice.PickupState = null;
}
var tkn = token.Get(context);
var client = new HttpClient();
HttpClientHandler handler = new HttpClientHandler();
client = new HttpClient(handler, false);
client.BaseAddress = new Uri("http://test.test.com/");
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + tkn);
StringContent content = new StringContent(JsonConvert.SerializeObject(inv), Encoding.UTF8, "application/json");
var response = await client.PostAsync("api/insert/", content);
var resultContent = response.StatusCode;
Output.Set(context, resultContent.ToString());
}
catch (Exception e)
{
Output.Set(context, e.ToString());
}
return "ok";
}
protected override void Execute(CodeActivityContext context)
{
try
{
string result = ApiTest(context).GetAwaiter().GetResult();
}
catch (Exception e)
{
Output.Set(context, e.ToString());
}
}
public class IList
{
public string PickupState { get; set; }
public string PickupCity { get; set; }
}
}
Classes that derive from CodeActivity are synchronous by default. Since UiPath is based on Windows Workflow, deriving from an AsyncCodeActivity class should work.
You didn't ask explicitly for it, but since you're essentially calling a web service, have a look at the Web Activities package, the HTTP Request in particular. This also comes with JSON deserialization. You can find more information about web service integration here, for example (disclaimer: I am the author).
I've implemented OAuth authentication in my Web Api project based on these blog posts
It works well, including the refresh token logic.
I want to add an option for basic authentication as well for a couple of calls for scheduled jobs.
I've tried adding a Basic Auth solution as middleware but I'm still getting 401 asking for the Bearer token.
I can get it to work by removing the [Authorize] attribute from those api calls and checking manually in code if the user is authenticated but seems like the wrong way to solve it.
Is there a way to support both Basic Auth and OAuth authentication using OWin?
How about you attribute your actions or controller with you want to implement Basic authentication with the attribute [OverrideAuthentication] Then you create custom authentication filter attribute which inherits from Attribute, IAuthenticationFilter as the code below
public class BasicAuthenticationAttribute : Attribute, IAuthenticationFilter
{
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
var req = context.Request;
// Get credential from the Authorization header
//(if present) and authenticate
if (req.Headers.Authorization != null && "basic".Equals(req.Headers.Authorization.Scheme, StringComparison.OrdinalIgnoreCase))
{
var rawCreds = req.Headers.Authorization.Parameter;
var credArray = GetCredentials(rawCreds);
var clientId = credArray[0];
var secret = credArray[1];
if (ValidCredentials(clientId, secret))
{
var claims = new List<Claim>()
{
new Claim(ClaimTypes.Name, clientId)
};
var identity = new ClaimsIdentity(claims, "Basic");
var principal = new ClaimsPrincipal(new[] { identity });
// The request message contains valid credential
context.Principal = principal;
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
}
else
{
context.ErrorResult = new UnauthorizedResult(new AuthenticationHeaderValue[0], context.Request);
}
return Task.FromResult(0);
}
private string[] GetCredentials(string rawCred)
{
var encoding = Encoding.GetEncoding("UTF-8");
var cred = encoding.GetString(Convert.FromBase64String(rawCred));
var credArray = cred.Split(':');
if (credArray.Length == 2)
{
return credArray;
}
else
{
return credArray = ":".Split(':');
}
}
private bool ValidCredentials(string clientId, string secret)
{
//compare the values from web.config
if (clientId == secret)
{
return true;
}
return false;
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context,CancellationToken cancellationToken)
{
context.Result = new ResultWithChallenge(context.Result);
return Task.FromResult(0);
}
public class ResultWithChallenge : IHttpActionResult
{
private readonly IHttpActionResult next;
public ResultWithChallenge(IHttpActionResult next)
{
this.next = next;
}
public async Task<HttpResponseMessage> ExecuteAsync( CancellationToken cancellationToken)
{
var response = await next.ExecuteAsync(cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
response.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic"));
}
return response;
}
}
public bool AllowMultiple
{
get { return false; }
}
}
Now you use it to attribute you controllers or actions as the code below:
[OverrideAuthentication]
[BasicAuthentication]
[Route("")]
public async Task<IHttpActionResult> Get()
{
}
Notice how we are creating claims identity and setting the Authentication scheme to Basic, you can put any claims you want here.
Please see the code below. Using in-memory hosting of httpclient, and Passing httpclient object to controller in order to unit test action method. But I am getting "Internal Server Error" ReasonPhrase upon HttpResponseMessage response =_httpClient.GetAsync. Please help me, is it correct approach?
private readonly HttpClient _httpClient;
public SecurityMfMvcController(HttpClient httpClient)
{
this._httpClient = httpClient;
}
[HttpGet]
public ActionResult GetSecuritiesMfs()
{
try
{
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response =
_httpClient.GetAsync(ConfigurationManager.AppSettings["ApiUrl"] + "SecuritiesWebApiMf").Result;
response.EnsureSuccessStatusCode();
List<SecurityMutualFundDto> list =
response.Content.ReadAsAsync<List<SecurityMutualFundDto>>().Result;
return View("SecuritiesMf", list);
}
catch (Exception ex)
{
return View("Error", ex.Message);
}
}
//Unit Test Method for this Action
[Test]
public void TestActionGetSecuritiesMfs()
{
var config = new HttpConfiguration()
{
IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always
};
//use the configuration that the web application has defined
WebApiConfig.Register(config);
HttpServer server = new HttpServer(config);
//create a client with a handler which makes sure to exercise the formatters
using (var client = new HttpClient(new InMemoryHttpContentSerializationHandler(server)))
{
System.Uri uri = new System.Uri("http://localhost:55893/api/");
client.BaseAddress = uri;
var controller = new SecurityMfMvcController(client);
var result = controller.GetSecuritiesMfs();
Assert.IsNotNull(result);
}
}
//MessageHandler
public class InMemoryHttpContentSerializationHandler : DelegatingHandler
{
public InMemoryHttpContentSerializationHandler(HttpMessageHandler innerHandler)
: base(innerHandler)
{
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Content = await ConvertToStreamContentAsync(request.Content);
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
response.Content = await ConvertToStreamContentAsync(response.Content);
return response;
}
private async Task<StreamContent> ConvertToStreamContentAsync(HttpContent originalContent)
{
if (originalContent == null)
{
return null;
}
StreamContent streamContent = originalContent as StreamContent;
if (streamContent != null)
{
return streamContent;
}
MemoryStream ms = new MemoryStream();
await originalContent.CopyToAsync(ms);
// Reset the stream position back to 0 as in the previous CopyToAsync() call,
// a formatter for example, could have made the position to be at the end
ms.Position = 0;
streamContent = new StreamContent(ms);
// copy headers from the original content
foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
{
streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
return streamContent;
}
}
You could mock your http request pipeline and test your action:
var mockHttpRequest = new Mock<HttpRequestMessage>(new object[] {new HttpMethod("GET"), "www.someuri.com"});
var mockHttpConfig = new Mock<HttpConfiguration>();
var mockRouteData = new Mock<IHttpRouteData>();
var mockHttpContext =
new Mock<HttpControllerContext>(new object[]
{mockHttpConfig.Object, mockRouteData.Object, mockHttpRequest.Object});
Then set your controller object with these values:
var controller = new YourController();
controller.ControllerContext = mockHttpContext.Object;
controller.Request = controller.ControllerContext.Request;
response = controller.SecuritiesMF();
and you could check your response as follows:
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
I got it working, correct me in case of anything wrong here. I have to create a "FakeHttpMessageHandler" as below and the content type to match System.Net.Http.StreamContent for application/json content type. the below code is working to unit test mvc action method using httpclient to call WebAPI. however I need to double check whether this is the right approach for unit test, will review further.
[Test]
public void TestActionMethodSelectByIdUsingFakeHandler()
{
var uobj = new UnitTestForApiController();
var testobj= uobj.GetsecuritiesMfsList();
MemoryStream stream = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, testobj);
var response = new HttpResponseMessage(HttpStatusCode.OK)
{Content = new StreamContent(stream)};
using (var httpClient = new HttpClient(new FakeHandler
{
Response = response,
InnerHandler = new HttpClientHandler()
}))
{
System.Uri uri = new System.Uri("http://localhost:55893/api/");
httpClient.BaseAddress = uri;
var controller = new SecuritiesMfMvcController(httpClient);
var result = controller.Select(2155) as ViewResult;
Assert.IsNotNull(result);
Assert.AreEqual(result.ViewName,"Select");
Assert.AreEqual(result.Model, testobj.FirstOrDefault());
}
//FakeHandler class goes as below
public class FakeHandler : DelegatingHandler
{
public HttpResponseMessage Response { get; set; }
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
return Task.Factory.StartNew(() => Response, cancellationToken);
}
}
//We can also have logic for abstracting appropriate StreamContent Creation into FakeContent class like below:
public class FakeHttpContent : HttpContent
{
public object Content { get; set; }
public FakeHttpContent(object content)
{
Content = content;
}
protected async override Task SerializeToStreamAsync(Stream stream,
TransportContext context)
{
MemoryStream ms = new MemoryStream();
IFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, Content);
await ms.CopyToAsync(stream);
}
protected override bool TryComputeLength(out long length)
{
length = Content.ToString().Length;
return true;
}
}
I have one DelegatingHandler to verify an ApiKey included in the Request Header:
public class ApiKeyHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (!ApiKey.VerifyRequest(request))
{
var response = request.CreateErrorResponse(HttpStatusCode.Forbidden, "Invalid Api Key");
var tsc = new TaskCompletionSource<HttpResponseMessage>();
tsc.SetResult(response);
return tsc.Task;
}
return base.SendAsync(request, cancellationToken);
}
}
Now I want to extend it to check the origin of the request, because this APIKey is only required for external requests (CORS has not be required yet).
External is defined by Domain and its exclusively Server to Server.
I found the Is_Local Property but this doesnt work for me.
I'm now going with the IP Address:
((dynamic)request.Properties["MS_HttpContext"]).Request.UserHostAddress;
Since you seem to be needing an IP of the caller, WebApiContrib has a good extension method that works on both self host and web host:
public static class HttpRequestMessageExtensions
{
private const string HttpContext = "MS_HttpContext";
private const string RemoteEndpointMessage = "System.ServiceModel.Channels.RemoteEndpointMessageProperty";
public static string GetClientIpAddress(this HttpRequestMessage request)
{
if (request.Properties.ContainsKey(HttpContext))
{
dynamic ctx = request.Properties[HttpContext];
if (ctx != null)
{
return ctx.Request.UserHostAddress;
}
}
if (request.Properties.ContainsKey(RemoteEndpointMessage))
{
dynamic remoteEndpoint = request.Properties[RemoteEndpointMessage];
if (remoteEndpoint != null)
{
return remoteEndpoint.Address;
}
}
return null;
}
}