David Brower
David Brower

Reputation: 3048

.NET Core API returning response from Web API not including cookies

I have a .NET Web API controller that contains the following login method:

[HttpPost]
[AllowAnonymous]
[Route("api/account/login")]
//Web API Controller method
public virtual async Task<IHttpActionResult> Login(LoginViewModel model)
{
        SignInStatus response = await this.SignInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberMe, shouldLockout: true);
        if (response == SignInStatus.Success)
            return Ok();

        return BadRequest();
}

The method returns an HttpResponseMessage that contains a number of Headers including Set-Cookie.

What I need to do is to call this Web API controller from a controller written in .NET Core 3.1 and return the HttpResponseMessage unaltered. This what I currently have:

    [HttpPost]
    [Route("[action]")]
    //.NET Core Controller method
    public async Task<ActionResult> Login(LoginViewModel model)
    {
        HttpClient httpClient = new HttpClient();
        HttpResponseMessage response = await httpClient.PostAsync(WebApiControllerURI, new JsonContent(model));
        return Ok(response);
    }

However, in the response from the .NET Core the Set-Cookie is not a header but is contained in the response body:

    {
      "Key": "Set-Cookie",
      "Value": [
        ".AspNet.ApplicationCookie=...."
      ]
    },

I get the same result when I try to return the HttpResponseMessage explicitly:

    [HttpPost]
    [Route("[action]")]
    public async Task<HttpResponseMessage> Login(LoginViewModel model)
    {
        HttpClient httpClient = new HttpClient();
        HttpResponseMessage response = await httpClient.PostAsync(serverUrl + LoginUri, new JsonContent(model));
        return response;
    }

How can I return the response from the Web API controller through the .NET Core controller and retain the headers in the Headers property?

Upvotes: 1

Views: 3280

Answers (2)

CorrieJanse
CorrieJanse

Reputation: 2598

If you want to add a cookie to your API request, then there are two ways:

Method 1 : At startup

Used with static cookies.

// Method 1 - In Startup
app.Use(async (context, next) =>
{
    var cookieOptions = new CookieOptions()
    {
        Path = "/",
        Expires = DateTimeOffset.UtcNow.AddHours(1),
        IsEssential = true,
        HttpOnly = false,
        Secure = false,
    };
    context.Response.Cookies.Append("MyCookie", "TheValue", cookieOptions);

    await next();
});

Method 2: In API

Used when you have a cookie with changing values:

[HttpPost("TestCookie")]
public async Task TestCookie(string cookieName, string cookieValue)
{
    // Method 2 - Add to current context
    var context = HttpContext;
    var cookieOptions = new CookieOptions()
    {
        Path = "/",
        Expires = DateTimeOffset.UtcNow.AddHours(1),
        IsEssential = true,
        HttpOnly = false,
        Secure = false,
    };

    context.Response.Cookies.Append(cookieName, cookieValue, cookieOptions);
}

The context is always returned, even if you don't have a return statement. So you just have to set the cookie header in the context and it will automatically get returned.

Upvotes: 2

RubberChickenLeader
RubberChickenLeader

Reputation: 1003

When you use return Ok(response) or just return response the framework takes the HttpResponse object and serializes it to json.

To proxy the response, take a look at this stack overflow question Creating a proxy to another web api with Asp.net core

The code would be something like this:

/* Copied from the linked stack overflow answer.  Only part of the code since the linked question was about a full proxy. */
public static async Task CopyProxyHttpResponse(this HttpContext context, HttpResponseMessage responseMessage)
        {
            if (responseMessage == null)
            {
                throw new ArgumentNullException(nameof(responseMessage));
            }

            var response = context.Response;

            response.StatusCode = (int)responseMessage.StatusCode;
            foreach (var header in responseMessage.Headers)
            {
                response.Headers[header.Key] = header.Value.ToArray();
            }

            foreach (var header in responseMessage.Content.Headers)
            {
                response.Headers[header.Key] = header.Value.ToArray();
            }

            // SendAsync removes chunking from the response. This removes the header so it doesn't expect a chunked response.
            response.Headers.Remove("transfer-encoding");

            using (var responseStream = await responseMessage.Content.ReadAsStreamAsync())
            {
                await responseStream.CopyToAsync(response.Body, _streamCopyBufferSize, context.RequestAborted);
            }
        }
    [HttpPost]
    [Route("[action]")]
    //.NET Core Controller method
    public async Task<ActionResult> Login(LoginViewModel model)
    {
            HttpResponseMessage response = await httpClient.PostAsync(WebApiControllerURI, new JsonContent(model));
            await HttpContext.CopyProxyHttpResponse(response);
            return Ok();
    }

another option would be a library like AspNetCore.Proxy depending on how much you need to proxy.

Upvotes: 1

Related Questions