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
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. 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 for a complete example that uses authentication (and stores user data in a MySQL 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:
class GoogleOAuth2LoginHandler(tornado.web.RequestHandler,
tornado.auth.GoogleOAuth2Mixin):
@tornado.gen.coroutine
def get(self):
if self.get_argument('code', False):
user = yield self.get_authenticated_user(
redirect_uri='http://your.site.com/auth/google',
code=self.get_argument('code'))
# Save the user with e.g. set_secure_cookie
else:
yield 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.
Cross-site request forgery protection
Cross-site request forgery, or XSRF, is a common problem for personalized web applications. See the Wikipedia article 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
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 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.
DNS Rebinding
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.