242 lines
8.9 KiB
ReStructuredText
242 lines
8.9 KiB
ReStructuredText
|
Authentication and security
|
||
|
===========================
|
||
|
|
||
|
Cookies and secure cookies
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
You can set cookies in the user's browser with the ``set_cookie``
|
||
|
method:
|
||
|
|
||
|
::
|
||
|
|
||
|
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 easily forged by malicious clients. If you need to set
|
||
|
cookies to, e.g., save the user ID of the currently logged in user, you
|
||
|
need to sign your cookies to prevent forgery. Tornado supports this out
|
||
|
of the box with the ``set_secure_cookie`` and ``get_secure_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:
|
||
|
|
||
|
::
|
||
|
|
||
|
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_secure_cookie`` will return ``None`` just as if the cookie isn't
|
||
|
set. The secure version of the example above:
|
||
|
|
||
|
::
|
||
|
|
||
|
class MainHandler(tornado.web.RequestHandler):
|
||
|
def get(self):
|
||
|
if not self.get_secure_cookie("mycookie"):
|
||
|
self.set_secure_cookie("mycookie", "myvalue")
|
||
|
self.write("Your cookie was not set yet!")
|
||
|
else:
|
||
|
self.write("Your cookie was set!")
|
||
|
|
||
|
User authentication
|
||
|
~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
The currently authenticated user is available in every request handler
|
||
|
as ``self.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:
|
||
|
|
||
|
::
|
||
|
|
||
|
class BaseHandler(tornado.web.RequestHandler):
|
||
|
def get_current_user(self):
|
||
|
return self.get_secure_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_secure_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:
|
||
|
|
||
|
::
|
||
|
|
||
|
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.
|
||
|
|
||
|
Tornado comes with built-in support for third-party authentication
|
||
|
schemes like Google OAuth. See the `tornado.auth`
|
||
|
for more details. 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
|
||
|
MySQL database).
|
||
|
|
||
|
.. _xsrf:
|
||
|
|
||
|
Cross-site request forgery protection
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
`Cross-site request
|
||
|
forgery <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_, or
|
||
|
XSRF, is a common problem for personalized web applications. See the
|
||
|
`Wikipedia
|
||
|
article <http://en.wikipedia.org/wiki/Cross-site_request_forgery>`_ for
|
||
|
more information on how XSRF works.
|
||
|
|
||
|
The generally accepted solution to prevent XSRF is to cookie every user
|
||
|
with an unpredictable value and include that value as an additional
|
||
|
argument with every form submission on your site. If the cookie and the
|
||
|
value in the form submission do not match, then the request is likely
|
||
|
forged.
|
||
|
|
||
|
Tornado comes with built-in XSRF protection. To include it in your site,
|
||
|
include the application setting ``xsrf_cookies``:
|
||
|
|
||
|
::
|
||
|
|
||
|
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
|
||
|
function ``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.
|
||
|
|
||
|
Third party authentication
|
||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
|
||
|
Tornado's ``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:
|
||
|
|
||
|
::
|
||
|
|
||
|
class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin):
|
||
|
@tornado.web.asynchronous
|
||
|
def get(self):
|
||
|
if self.get_argument("openid.mode", None):
|
||
|
self.get_authenticated_user(self._on_auth)
|
||
|
return
|
||
|
self.authenticate_redirect()
|
||
|
|
||
|
def _on_auth(self, user):
|
||
|
if not user:
|
||
|
self.authenticate_redirect()
|
||
|
return
|
||
|
# Save the user with, e.g., set_secure_cookie()
|
||
|
|
||
|
See the `tornado.auth` module documentation for more details.
|