How can I set the 'secure' flag for cookies in an ASP.NET MVC website?

Gary McGill picture Gary McGill · Sep 16, 2015 · Viewed 41.6k times · Source

I have set the following in web.config:

<system.web>
  <httpCookies httpOnlyCookies="true" requireSSL="true" />
</system.web>

When I hit the website using an HTTP connection, it redirects to my login page (specifying the scheme as HTTPS). When the browser fetches this page, the response sets some cookies (the ASP.NET session cookie, and the request verification token for my login form):

Set-Cookie: __RequestVerificationToken=IHx8a2zQU374d5CtsoEVW...YtIc1; path=/; HttpOnly Set-Cookie: ASP.NET_SessionId=pfbkkxx2seqhdrxxiodxfbmh; path=/; HttpOnly

These have the HttpOnly flag, which is good - but they do NOT have the secure flag as described here on Wikipedia.

If I then log in, an authentication cookie is created, and this does have the secure flag set:

Set-Cookie:MyWebSite.Authentication=RE3UD...BDW4; path=/; secure; HttpOnly

How can I ensure that the secure flag is set on all my cookies?


UPDATE: as requested, this is the cURL output I get (when fetching the login page directly):

curl https://www.mywebsite.com/Account/Login --verbose --insecure

gives:

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
*   Trying 194.73.98.116...
* Connected to www.mywebsite.com (111.11.11.111) port 443 (#0)
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
} [5 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [85 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [2618 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [401 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [138 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*    subject: OU=Domain Control Validated; CN=*.mywebsite.com
*    start date: 2015-07-29 13:37:38 GMT
*    expire date: 2018-07-29 13:37:38 GMT
*    issuer: C=US; ST=Arizona; L=Scottsdale; O=GoDaddy.com, Inc.; OU=http://certs.godaddy.com/repository/; CN=Go Daddy Secure Certificate Authority - G2
*    SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
} [5 bytes data]
> GET /Account/Login HTTP/1.1
> Host: www.mywebsite.com
> User-Agent: curl/7.43.0
> Accept: */*
> 
{ [5 bytes data]
< HTTP/1.1 200 OK
< Cache-Control: no-cache, no-store, must-revalidate
< Pragma: no-cache
< Content-Type: text/html; charset=utf-8
< Expires: -1
< Server: Microsoft-IIS/8.5
< Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
< X-Frame-Options: Deny
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Content-Security-Policy: default-src 'self';script-src 'self' www.google-analytics.com www.googletagmanager.com;object-src 'none';style-src 'self' fonts.googleapis.com;img-src 'self' www.google-analytics.com placehold.it placeholdit.imgix.net data:;media-src 'none';frame-src 'none';font-src 'self' fonts.gstatic.com;connect-src 'self';base-uri 'self';child-src 'none';frame-ancestors 'none';report-uri /WebResource.axd?cspReport=true
< X-Frame-Options: SAMEORIGIN
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: __RequestVerificationToken=bPWxIp8e4F4I0Jt26t5oZyvDM6059tAWSRbgc-b6Df5IMjyYFDD9fJKgRsKVjbtN3EGgtFuHcf1sTjlYSwDWgnlhSUuNW1q5yv3cGMxmEwE1; path=/; HttpOnly
< Date: Fri, 04 Dec 2015 10:03:35 GMT
< Content-Length: 12596
< 
{ [12596 bytes data]
100 12596  100 12596    0     0  31101      0 --:--:-- --:--:-- --:--:-- 31101
* Connection #0 to host www.mywebsite.com left intact

Answer

Moby picture Moby · Dec 9, 2015

The suggested way around this is to secure the session ID and form request cookies when handling page requests, e.g.

// This code will mark the forms authentication cookie and the
// session cookie as Secure.
if (Response.Cookies.Count > 0)
{
    foreach (string s in Response.Cookies.AllKeys)
    {
        if (s == FormsAuthentication.FormsCookieName || s.ToLower() == "asp.net_sessionid")
        {
             Response.Cookies[s].Secure = true;
        }
    }
}

as well as an additional line in the webconfig for securing form auth tokens:

<authentication mode="Forms">
   <forms ...  requireSSL="true" />
</authentication>

2020 - EDIT:

As requested in the comments, it is possible to configure this using only IIS rewrite rules as well, by checking the cookie for the secure flag and adding it if it's not found, e.g.:

<system.webServer>
  <rewrite>
    <outboundRules>
      <rule name="Use only secure cookies" preCondition="Unsecured cookie">
        <match serverVariable="RESPONSE_SET_COOKIE" pattern=".*" negate="false" />
        <action type="Rewrite" value="{R:0}; secure" />
      </rule>
      <preConditions>
        <preCondition name="Unsecured cookie">
          <add input="{RESPONSE_SET_COOKIE}" pattern="." />
          <add input="{RESPONSE_SET_COOKIE}" pattern="; secure" negate="true" />
        </preCondition>
      </preConditions>
    </outboundRules>
  </rewrite>
...
</system.webServer>

Sources: Securing Request-Response cookies - Secure forms authentication via Web.config - How to Enable Secure HttpOnly Cookies in IIS