I want to implement custom encryption middleware for API calls. At first, I read request body (IOwinContext.Request.Body
) and headers (Encryption-Key & Signature). Then, I decrypt request body, which gives me pure json string. Now comes the tricky part: I want to write this json back to IOwinContextRequest.Body
, so it can be deserialized to object and later passed as an argument for Controller method. Here's what I do:
Startup:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(typeof(EncryptionMiddleware));
...
}
}
Middleware:
public class EncryptionMiddleware : OwinMiddleware
{
public EncryptionMiddleware(OwinMiddleware next) : base(next)
{
//
}
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string json = GetDecryptedJson(context);
MemoryStream stream = new MemoryStream();
stream.Write(json, 0, json.Length);
request.Headers["Content-Lenght"] = json.Lenght.ToString();
request.Body = stream;
await Next.Invoke(context);
}
}
Now, what I get is this error:
System.Web.Extensions.dll!System.Web.Script.Serialization.JavaScriptObjectDeserializer.DeserializePrimitiveObject() Exception thrown: 'System.ArgumentException' in System.Web.Extensions.dll
Additional information: Invalid JSON primitive: 8yi9OH2JE0H0cwZ.
Where original IOwinContext.Request.Body
is:
8yi9OH2JE0H0cwZ/fyY5Fks4nW(...omitted...)PvL32AVRjLA==
So I assumed that you cannot change request body this way. To test this, I've rewritten middleware like this:
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string requestBody = new StreamReader(request.Body).ReadToEnd();
Debug.WriteLine(requestBody); // Prints "ORIGINAL BODY"
string newBody = "\"newBody\"";
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(newBody));
request.Headers["Content-Length"] = newBody.Length.ToString();
request.Body = memStream;
await Next.Invoke(context);
}
Now I thought that Controller method should receive "ORIGINAL BODY" instead of "newBody", but I actually got this error:
System.dll!System.Diagnostics.PerformanceCounter.InitializeImpl() Exception thrown: 'System.InvalidOperationException' in System.dll
Additional information: The requested Performance Counter is not a custom counter, it has to be initialized as ReadOnly.
The question is: what is wrong with my approach? What is the correct way to rewrite request body? Is there any sufficient workaround? BTW: Decryption of data is tested and is flawless, so error should not originate there.
EDIT: before you answer/comment, TLS is already used. This is another layer of security. I am NOT reinventing the wheel. I'm adding a new one.
I have solved this issue a long time ago, but only after @Nkosi 's advice, I'm posting the solution.
What I did is a workaround, or rather "bridge" from middleware to action filter. Here's the code:
Middleware
public class EncryptionMiddleware : OwinMiddleware
{
public EncryptionMiddleware(OwinMiddleware next) : base(next)
{
//
}
public async override Task Invoke(IOwinContext context)
{
var request = context.Request;
string requestBody = new StreamReader(request.Body).ReadToEnd();
var obj = // do your work here
System.Web.HttpContext.Current.Items[OBJECT_ITEM_KEY] = obj;
await Next.Invoke(context);
return;
}
}
Filter
public class EncryptedParameter : ActionFilterAttribute, IActionFilter
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var obj = HttpContext.Current.Items[OBJECT_ITEM_KEY];
HttpContext.Current.Items.Remove(AppConfig.ITEM_DATA_KEY);
if (filterContext.ActionParameters.ContainsKey("data"))
filterContext.ActionParameters["data"] = obj;
}
}
Controller
public class MyController : Controller
{
[HttpPost]
[EncryptedParameter]
public JsonResult MyMethod(MyObject data)
{
// your logic here
}
}