Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Request an MVC page, from a console app, passing in a context containing FormsAuthentication cookie

I have a console app (that will ultimately run as a worker in Azure) that requests a page from my main ASP.Net MVC site, and converts it to a PDF. That site is secured by forms authentication.

In the code that requests the page, I pass in an HttpContext that contains a valid forms authentication cookie:

byte[] pdfBytes = PdfHelper.UrlToPdfBytes(url, new HttpContextWrapper(GetHttpContext(url)));

And the GetHttpContext method:

private static HttpContext GetHttpContext(string url)
{
    var stringWriter = new StringWriter();

    var httpRequest = new HttpRequest("", url, "");
    var httpResponse = new HttpResponse(stringWriter);

    var httpContext = new HttpContext(httpRequest, httpResponse);

    FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
        2,
        "remoteuser",
        DateTime.Now,
        DateTime.Now.AddMinutes(5),
        false,
        String.Empty,
        FormsAuthentication.FormsCookiePath);

    // Encrypt the ticket.
    string encryptedTicket = FormsAuthentication.Encrypt(ticket);

    // Create the cookie.
    HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
    {
        HttpOnly = true//,
        //Domain = new Uri(url).Host
    };

    httpRequest.Cookies.Add(cookie);

    return httpContext;
}

No matter what I try, though, the resulting PDF always contains the login form - the authentication cookie is seemingly ignored.

The cookie names are the same, and the values inside match those of the cookie that exists in the browser, yet I can't get it to work.

The attributes on my controller actions don't even get hit (I have one that ensures the logged in user can access the data being requested), which suggests that it's IIS that is throwing the request to the login form (via the settings in web.config).

I got the request to work by adding the AllowAnonymous attribute on the controller, so I know the request itself works as expected, but I can not get the FormsAuthentication aspect working. I'm only working locally in VS2017 at the moment, using IIS (not IIS Express).

I've set breakpoints in the PDFHelper.UrlToPdfBytes method, and the cookie is there (and the ticket decrypts correctly).

This has been frustrating me for days now, so any help is appreciated.

like image 748
Leigh Bowers Avatar asked Mar 29 '18 10:03

Leigh Bowers


1 Answers

You can get the cookies from creating a POST request to the login URL, then use those cookies when getting your PDF. Here is a method that returns the HttpCookies for a given request Url. I called it GetAuthCookies.

private static IEnumerable<HttpCookie> GetAuthCookies(string loginUrl, string requestUrl, string username, string password)
{
  var formContent = new Dictionary<string, string>();

  /* set these keys to the login form input's name values for username/password

     I.E.  If you have a login form like this

      <form>
        <input name="userName" id="userName_21342353465" type="text" />
        <input name="password" id="password_21342353465" type="password" />
      </form>

     then you would use "userName" and "password" for your keys below.
  */

  formContent.Add("userName", username);
  formContent.Add("password", password);

  // add any other required (or optional) fields in the form...

  var cookieContainer = new CookieContainer();
  var content = new FormUrlEncodedContent(formContent);

  var handler = new HttpClientHandler { CookieContainer = cookieContainer };

  var cookieCollection = new CookieCollection();

  using (var client = new HttpClient(handler))
  {
    using (var response = client.PostAsync(loginUrl, content).Result)
    {
      // Below is some getting the resposne string, you can use this to determine login status, may help with finding missing values in request
      //var responseString = response.Content.ReadAsStringAsync().Result;
    }

    foreach (var cookie in cookieContainer.GetCookies(new Uri(requestUrl)).Cast<Cookie>())
    {
      cookieCollection.Add(cookie);
    }

    foreach (var cookie in cookieCollection.Cast<Cookie>())
    {
      yield return new HttpCookie(cookie.Name, cookie.Value);
    }
  }
}

You can hard code the values, use config for them or have the user enter them from the console... here I hard coded the LoginUrl and have get the username and password from the console, here is the code changes GetHttpContext along with the hard coded LoginUrl and two methods to retrieve the UserName/Password from the console.

    private static HttpContext GetHttpContext(string url)
{
  var stringWriter = new StringWriter();

  var httpRequest = new HttpRequest("", url, "");
  var httpResponse = new HttpResponse(stringWriter);

  var httpContext = new HttpContext(httpRequest, httpResponse);

  var username = GetUserName();
  var password = GetPassword();
  var cookies = GetAuthCookies(LoginUrl, url, username, password);


  foreach (var cookie in cookies)
  {
    httpContext.Request.Cookies.Add(cookie);
  }

  return httpContext;
}

private const string LoginUrl = @"{{ Login's Post Back URL }}";

private static string GetPassword()
{
  var password = new StringBuilder();

  while (password.Length == 0)
  {
    Console.Write("Enter password: ");
    ConsoleKeyInfo key;
    while ((key = Console.ReadKey(true)).Key != ConsoleKey.Enter)
    {
      switch (key.Key)
      {
        case ConsoleKey.Backspace:
          if (password.Length > 0)
          {
            password.Remove(password.Length - 1, 1);
            Console.Write(key.KeyChar);
            Console.Write(' ');
            Console.Write(key.KeyChar);
          }
          break;
        default:
          password.Append(key.KeyChar);
          Console.Write('*');
          break;
      }
    }
    Console.WriteLine();
  }
  return password.ToString();
}

private static string GetUserName()
{
  var username = string.Empty;
  while (string.IsNullOrWhiteSpace(username))
  {
    Console.Write("Enter username: ");
    username = Console.ReadLine();
  }
  return username;
}
like image 108
Larry Dukek Avatar answered Nov 09 '22 01:11

Larry Dukek