Authentication and security
===========================

.. testsetup::

   import tornado

Cookies and signed cookies
~~~~~~~~~~~~~~~~~~~~~~~~~~

You can set cookies in the user's browser with the ``set_cookie``
method:

.. testcode::

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_cookie("mycookie"):
                self.set_cookie("mycookie", "myvalue")
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")

Cookies are not secure and can easily be modified by clients.  If you
need to set cookies to, e.g., identify the currently logged in user,
you need to sign your cookies to prevent forgery. Tornado supports
signed cookies with the `~.RequestHandler.set_signed_cookie` and
`~.RequestHandler.get_signed_cookie` methods. To use these methods,
you need to specify a secret key named ``cookie_secret`` when you
create your application. You can pass in application settings as
keyword arguments to your application:

.. testcode::

    application = tornado.web.Application([
        (r"/", MainHandler),
    ], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

Signed cookies contain the encoded value of the cookie in addition to a
timestamp and an `HMAC <http://en.wikipedia.org/wiki/HMAC>`_ signature.
If the cookie is old or if the signature doesn't match,
``get_signed_cookie`` will return ``None`` just as if the cookie isn't
set. The secure version of the example above:

.. testcode::

    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            if not self.get_signed_cookie("mycookie"):
                self.set_signed_cookie("mycookie", "myvalue")
                self.write("Your cookie was not set yet!")
            else:
                self.write("Your cookie was set!")

Tornado's signed cookies guarantee integrity but not confidentiality.
That is, the cookie cannot be modified but its contents can be seen by the
user.  The ``cookie_secret`` is a symmetric key and must be kept secret --
anyone who obtains the value of this key could produce their own signed
cookies.

By default, Tornado's signed cookies expire after 30 days.  To change this,
use the ``expires_days`` keyword argument to ``set_signed_cookie`` *and* the
``max_age_days`` argument to ``get_signed_cookie``.  These two values are
passed separately so that you may e.g. have a cookie that is valid for 30 days
for most purposes, but for certain sensitive actions (such as changing billing
information) you use a smaller ``max_age_days`` when reading the cookie.

Tornado also supports multiple signing keys to enable signing key
rotation. ``cookie_secret`` then must be a dict with integer key versions
as keys and the corresponding secrets as values. The currently used
signing key must then be set as ``key_version`` application setting
but all other keys in the dict are allowed for cookie signature validation,
if the correct key version is set in the cookie.
To implement cookie updates, the current signing key version can be
queried via `~.RequestHandler.get_signed_cookie_key_version`.

.. _user-authentication:

User authentication
~~~~~~~~~~~~~~~~~~~

The currently authenticated user is available in every request handler
as `self.current_user <.RequestHandler.current_user>`, and in every
template as ``current_user``. By default, ``current_user`` is
``None``.

To implement user authentication in your application, you need to
override the ``get_current_user()`` method in your request handlers to
determine the current user based on, e.g., the value of a cookie. Here
is an example that lets users log into the application simply by
specifying a nickname, which is then saved in a cookie:

.. testcode::

    class BaseHandler(tornado.web.RequestHandler):
        def get_current_user(self):
            return self.get_signed_cookie("user")

    class MainHandler(BaseHandler):
        def get(self):
            if not self.current_user:
                self.redirect("/login")
                return
            name = tornado.escape.xhtml_escape(self.current_user)
            self.write("Hello, " + name)

    class LoginHandler(BaseHandler):
        def get(self):
            self.write('<html><body><form action="/login" method="post">'
                       'Name: <input type="text" name="name">'
                       '<input type="submit" value="Sign in">'
                       '</form></body></html>')

        def post(self):
            self.set_signed_cookie("user", self.get_argument("name"))
            self.redirect("/")

    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__")

You can require that the user be logged in using the `Python
decorator <http://www.python.org/dev/peps/pep-0318/>`_
`tornado.web.authenticated`. If a request goes to a method with this
decorator, and the user is not logged in, they will be redirected to
``login_url`` (another application setting). The example above could be
rewritten:

.. testcode::

    class MainHandler(BaseHandler):
        @tornado.web.authenticated
        def get(self):
            name = tornado.escape.xhtml_escape(self.current_user)
            self.write("Hello, " + name)

    settings = {
        "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
        "login_url": "/login",
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)

If you decorate ``post()`` methods with the ``authenticated``
decorator, and the user is not logged in, the server will send a
``403`` response.  The ``@authenticated`` decorator is simply
shorthand for ``if not self.current_user: self.redirect()`` and may
not be appropriate for non-browser-based login schemes.

Check out the `Tornado Blog example application
<https://github.com/tornadoweb/tornado/tree/stable/demos/blog>`_ for a
complete example that uses authentication (and stores user data in a
PostgreSQL database).

Third party authentication
~~~~~~~~~~~~~~~~~~~~~~~~~~

The `tornado.auth` module implements the authentication and
authorization protocols for a number of the most popular sites on the
web, including Google/Gmail, Facebook, Twitter, and FriendFeed.
The module includes methods to log users in via these sites and, where
applicable, methods to authorize access to the service so you can, e.g.,
download a user's address book or publish a Twitter message on their
behalf.

Here is an example handler that uses Google for authentication, saving
the Google credentials in a cookie for later access:

.. testcode::

    class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,
                                   tornado.auth.GoogleOAuth2Mixin):
        async def get(self):
            if self.get_argument('code', False):
                user = await self.get_authenticated_user(
                    redirect_uri='http://your.site.com/auth/google',
                    code=self.get_argument('code'))
                # Save the user with e.g. set_signed_cookie
            else:
                await self.authorize_redirect(
                    redirect_uri='http://your.site.com/auth/google',
                    client_id=self.settings['google_oauth']['key'],
                    scope=['profile', 'email'],
                    response_type='code',
                    extra_params={'approval_prompt': 'auto'})

See the `tornado.auth` module documentation for more details.

..
   The ``xsrf`` label is kept so that links to the old "Cross-site request
   forgery protection" section continue to work. Internal links should use
   ``cross_origin_protection`` or ``xsrf-cookies`` instead.

.. _xsrf:

.. _cross_origin_protection:

Cross-origin protection
~~~~~~~~~~~~~~~~~~~~~~~

Cross-origin protection guards against a class of attacks most commonly known as
`cross-site request
forgery <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ (XSRF or CSRF).
This is a common problem for personalized web applications, in which a third-party
site can construct a request which a visitor's browser will submit to your application
along with the visitor's authentication cookies.

Modern browsers send a header which the server can use to identify and block
cross-site requests. Tornado from version 6.6 can check this for you, if you
enable the application setting ``cross_origin_protection``:

.. testcode::

    settings = {
        "login_url": "/login",
        "cross_origin_protection": True,
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)

.. note::
    This feature is a new security measure in the web stack. Tornado uses
    the same terminology as Go's `cross-origin protection 
    <https://pkg.go.dev/net/http#CrossOriginProtection>`_, with an algorithm based
    on `this blog post by Filippo Valsorda <https://words.filippo.io/csrf/>`_. Other
    packages and documents refer to it by different names, such as "fetch metadata"
    or ``Sec-Fetch-Site``.
    
    Conversely, this feature is **unrelated** to Cross-Origin Resource Sharing (CORS)
    and the ``Access-Control-Allow-Origin`` header. Applications using
    ``cross_origin_protection`` should still consider their CORS requirements.


With this setting, the application will reject state-changing requests if the
`Sec-Fetch-Site <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Sec-Fetch-Site>`_
header is set to something other than ``same-origin`` or ``none``. If that
header is missing, it rejects these requests if the ``Origin`` and ``Host``
headers do not match. You can allow specific additional origins with the
``trusted_origins`` application setting (a list).

The ``cross_origin_protection`` setting was introduced in Tornado 6.6 and defaults
to False. It is expected that in Tornado 6.7 it will be enabled by default.

.. note::

   These checks apply to HTTP verbs like ``POST`` or ``PUT``, which typically
   modify application state. ``GET``, ``HEAD`` and ``OPTIONS`` requests should
   not affect application state, and are not checked on the server, but the
   browser can still keep the response from leaking.

   The exception is WebSocket connections, which begin with a ``GET`` request
   but are not subject to the browser's same-origin policy. `.WebSocketHandler`
   always performs these checks, whether or not ``cross_origin_protection``
   is enabled. Instead of `.RequestHandler.check_trusted_origin`, it calls
   `.WebSocketHandler.check_origin` (after `~.RequestHandler.prepare`), whose
   default implementation calls ``check_trusted_origin`` and therefore also
   uses the ``trusted_origins`` setting.

This protection is more convenient than older methods like XSRF cookies, described
below, because it does not require any modifications to the HTML or JavaScript making
the requests. It should be sufficient to protect most web applications from XSRF
attacks.

Some applications may need to customize the ``trusted_origins`` application setting (or
override `.RequestHandler.check_trusted_origin`) to include additional domains that
should be allowed to send cross-site requests. Note that an origin looks like the
beginning of a url but never includes a trailing slash:

.. code-block:: python

    "trusted_origins": [
        "https://example.com",
        "https://example.org",
    ],

An "origin" is a smaller scope than a "site" (a site includes subdomains). This means that
applications built for a site-based security model may need to include their subdomains as
"trusted origins" to transition to ``cross_origin_protection``. Wildcards are not supported in
the ``trusted_origins`` application setting but can be implemented by overriding
`.RequestHandler.check_trusted_origin`.

`.RequestHandler.check_trusted_origin` is called before `~.RequestHandler.prepare`,
so it cannot depend on how (or whether) the request is authenticated. This is
intentional: cookies are attached to cross-site requests automatically, so the
origin must be checked before the request is allowed to act on its cookies.

This protection relies on the ``Sec-Fetch-Site`` HTTP header, and, as a fallback for 
when that header is not present, the ``Origin`` and ``Host`` headers. Any proxies
or routers in your application serving stack must be configured to pass these headers
through. Note that as XSRF is fundamentally a browser-based attack, requests from
non-browser clients (which do not send ``Sec-Fetch-Site`` or ``Origin``) are always
permitted. This means that if a proxy strips both of these headers, or a very old
browser does not send them, the protection is silently disabled.

All major browsers have supported the ``Sec-Fetch-Site`` header since 2023, although
this header is only sent on HTTPS connections or to localhost. For older browsers or 
unencrypted applications, we fall back to comparing the ``Origin`` and ``Host``
headers.

.. _xsrf-cookies:

XSRF cookies
^^^^^^^^^^^^

Tornado supports a second, older method of preventing XSRF attacks using cookies. 
This involves modifying every form submission on your site to include a value that
can be compared with a cookie set for this purpose. This is an intrusive modification
and is no longer recommended for new applications, although it was the only option
available in Tornado before version 6.6.

To use cookie-based XSRF protection, set the application setting ``xsrf_cookies`` to
``True``, and modify your application as described below. The ``xsrf_cookies`` setting
will emit deprecation warnings beginning in Tornado 6.7 and may be removed in future
versions (Tornado 7.0 at the earliest).

.. testcode::

    settings = {
        "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
        "login_url": "/login",
        "xsrf_cookies": True,
    }
    application = tornado.web.Application([
        (r"/", MainHandler),
        (r"/login", LoginHandler),
    ], **settings)

If ``xsrf_cookies`` is set, the Tornado web application will set the
``_xsrf`` cookie for all users and reject all ``POST``, ``PUT``, and
``DELETE`` requests that do not contain a correct ``_xsrf`` value. If
you turn this setting on, you need to instrument all forms that submit
via ``POST`` to contain this field. You can do this with the special
`.UIModule` ``xsrf_form_html()``, available in all templates::

    <form action="/new_message" method="post">
      {% module xsrf_form_html() %}
      <input type="text" name="message"/>
      <input type="submit" value="Post"/>
    </form>

If you submit AJAX ``POST`` requests, you will also need to instrument
your JavaScript to include the ``_xsrf`` value with each request. This
is the `jQuery <http://jquery.com/>`_ function we use at FriendFeed for
AJAX ``POST`` requests that automatically adds the ``_xsrf`` value to
all requests::

    function getCookie(name) {
        var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
        return r ? r[1] : undefined;
    }

    jQuery.postJSON = function(url, args, callback) {
        args._xsrf = getCookie("_xsrf");
        $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
            success: function(response) {
            callback(eval("(" + response + ")"));
        }});
    };

For ``PUT`` and ``DELETE`` requests (as well as ``POST`` requests that
do not use form-encoded arguments), the XSRF token may also be passed
via an HTTP header named ``X-XSRFToken``.  The XSRF cookie is normally
set when ``xsrf_form_html`` is used, but in a pure-JavaScript application
that does not use any regular forms you may need to access
``self.xsrf_token`` manually (just reading the property is enough to
set the cookie as a side effect).

If you need to customize XSRF behavior on a per-handler basis, you can
override `.RequestHandler.check_xsrf_cookie()`. For example, if you
have an API whose authentication does not use cookies, you may want to
disable XSRF protection by making ``check_xsrf_cookie()`` do nothing.
However, if you support both cookie and non-cookie-based authentication,
it is important that XSRF protection be used whenever the current
request is authenticated with a cookie.

.. _dnsrebinding:

DNS Rebinding
~~~~~~~~~~~~~

`DNS rebinding <https://en.wikipedia.org/wiki/DNS_rebinding>`_ is an
attack that can bypass the same-origin policy and allow external sites
to access resources on private networks. This attack involves a DNS
name (with a short TTL) that alternates between returning an IP
address controlled by the attacker and one controlled by the victim
(often a guessable private IP address such as ``127.0.0.1`` or
``192.168.1.1``).

Applications that use TLS are *not* vulnerable to this attack (because
the browser will display certificate mismatch warnings that block
automated access to the target site).

Applications that cannot use TLS and rely on network-level access
controls (for example, assuming that a server on ``127.0.0.1`` can
only be accessed by the local machine) should guard against DNS
rebinding by validating the ``Host`` HTTP header. This means passing a
restrictive hostname pattern to either a `.HostMatches` router or the
first argument of `.Application.add_handlers`::

    # BAD: uses a default host pattern of r'.*'
    app = Application([('/foo', FooHandler)])

    # GOOD: only matches localhost or its ip address.
    app = Application()
    app.add_handlers(r'(localhost|127\.0\.0\.1)',
                     [('/foo', FooHandler)])

    # GOOD: same as previous example using tornado.routing.
    app = Application([
        (HostMatches(r'(localhost|127\.0\.0\.1)'),
            [('/foo', FooHandler)]),
        ])

In addition, the ``default_host`` argument to `.Application` and the
`.DefaultHostMatches` router must not be used in applications that may
be vulnerable to DNS rebinding, because it has a similar effect to a
wildcard host pattern.
