1 <?xml version="1.0" encoding="utf-8"?><!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html lang="en" xmlns="http://www.w3.org/1999/xhtml">
3 <title>Twisted Documentation: HTTP Authentication</title>
4 <link href="../stylesheet.css" rel="stylesheet" type="text/css"/>
8 <h1 class="title">HTTP Authentication</h1>
9 <div class="toc"><ol/></div>
13 <p>Many of the previous examples have looked at how to serve content by using
14 existing resource classes or implementing new ones. In this example we'll use
15 Twisted Web's basic or digest HTTP authentication to control access to these
18 <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.html" title="twisted.web.guard">guard</a></code>, the Twisted Web
19 module which provides most of the APIs that will be used in this
21 add <a href="http://en.wikipedia.org/wiki/Authentication" shape="rect">authentication</a>
22 and <a href="http://en.wikipedia.org/wiki/Authorization" shape="rect">authorization</a>
23 to a resource hierarchy. It does this by providing a resource which
24 implements <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.resource.Resource.getChild.html" title="twisted.web.resource.Resource.getChild">getChild</a></code> to return
25 a <a href="dynamic-dispatch.html" shape="rect">dynamically selected
26 resource</a>. The selection is based on the authentication headers in
27 the request. If those headers indicate that the request is made on
28 behalf of Alice, then Alice's resource will be returned. If they
29 indicate that it was made on behalf of Bob, his will be returned. If
30 the headers contain invalid credentials, an error resource is
31 returned. Whatever happens, once this resource is returned, URL
32 traversal continues as normal from that resource.</p>
34 <p>The resource that implements this is <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.HTTPAuthSessionWrapper.html" title="twisted.web.guard.HTTPAuthSessionWrapper">HTTPAuthSessionWrapper</a></code>, though it is directly
35 responsible for very little of the process. It will extract headers from the
36 request and hand them off to a credentials factory to parse them according to
37 the appropriate standards (eg <a href="http://tools.ietf.org/html/rfc2617" shape="rect">HTTP
38 Authentication: Basic and Digest Access Authentication</a>) and then hand the
39 resulting credentials object off to a <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.Portal.html" title="twisted.cred.portal.Portal">Portal</a></code>, the core
40 of <a href="../../../core/howto/cred.html" shape="rect">Twisted Cred</a>, a system for
41 uniform handling of authentication and authorization. We won't discuss Twisted
42 Cred in much depth here. To make use of it with Twisted Web, the only thing you
43 really need to know is how to implement an <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.html" title="twisted.cred.portal.IRealm">IRealm</a></code>.</p>
45 <p>You need to implement a realm because the realm is the object that
46 actually decides which resources are used for which users. This can be
47 as complex or as simple as it suitable for your application. For this
48 example we'll keep it very simple: each user will have a resource
49 which is a static file listing of the <code>public_html</code>
50 directory in their UNIX home directory. First, we need to
51 import <code>implements</code> from <code>zope.interface</code>
52 and <code>IRealm</code>
53 from <code>twisted.cred.portal</code>. Together these will let me mark
54 this class as a realm (this is mostly - but not entirely - a
55 documentation thing). We'll also need <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.static.File.html" title="twisted.web.static.File">File</a></code> for the actual implementation
58 <pre class="python"><p class="py-linenumber">1
65 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
67 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>
68 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
70 <span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
71 <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
74 <p>A realm only needs to implement one method: <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.portal.IRealm.requestAvatar.html" title="twisted.cred.portal.IRealm.requestAvatar">requestAvatar</a></code>. This method is called
75 after any successful authentication attempt (ie, Alice supplied the right
76 password). Its job is to return the <i>avatar</i> for the user who succeeded in
77 authenticating. An <i>avatar</i> is just an object that represents a user. In
78 this case, it will be a <code>File</code>. In general, with <code>Guard</code>,
79 the avatar must be a resource of some sort.</p>
81 <pre class="python"><p class="py-linenumber">1
87 <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
88 <span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
89 <span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">"/home/%s/public_html"</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
90 <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
93 <p>A few notes on this method:</p>
95 <li>The <code>avatarId</code> parameter is essentially the username. It's the
96 job of some other code to extract the username from the request headers and
97 make sure it gets passed here.</li>
98 <li>The <code>mind</code> is always <code>None</code> when writing a realm to
99 be used with <code>Guard</code>. You can ignore it until you want to write a
100 realm for something else.</li>
101 <li><code>Guard</code> is always
102 passed <code class="twisted.web.resource">IResource</code> as
103 the <code>interfaces</code> parameter. If <code>interfaces</code> only
104 contains interfaces your code doesn't understand,
105 raising <code>NotImplementedError</code> is the thing to do, as
106 above. You'll only need to worry about getting a different interface when
107 you write a realm for something other than <code>Guard</code>.</li>
108 <li>If you want to track when a user logs out, that's what the last element of
109 the returned tuple is for. It will be called when this avatar logs
110 out. <code>lambda: None</code> is the idiomatic no-op logout function.</li>
111 <li>Notice that the path handling code in this example is written very
112 poorly. This example may be vulnerable to certain unintentional information
113 disclosure attacks. This sort of problem is exactly the
114 reason <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.python.filepath.FilePath.html" title="twisted.python.filepath.FilePath">FilePath</a></code>
115 exists. However, that's an example for another day...</li>
118 <p>We're almost ready to set up the resource for this example. To
119 create an <code>HTTPAuthSessionWrapper</code>, though, we need two
120 things. First, a portal, which requires the realm above, plus at least
121 one credentials checker:</p>
123 <pre class="python"><p class="py-linenumber">1
127 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">Portal</span>
128 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
130 <span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
133 <p><code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.cred.checkers.FilePasswordDB.html" title="twisted.cred.checkers.FilePasswordDB">FilePasswordDB</a></code> is the
134 credentials checker. It knows how to read <code>passwd(5)</code>-style (loosely)
135 files to check credentials against. It is responsible for the authentication
136 work after <code>HTTPAuthSessionWrapper</code> extracts the credentials from the
139 <p>Next we need either <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.BasicCredentialFactory.html" title="twisted.web.guard.BasicCredentialFactory">BasicCredentialFactory</a></code>
140 or <code class="API"><a href="http://twistedmatrix.com/documents/12.1.0/api/twisted.web.guard.DigestCredentialFactory.html" title="twisted.web.guard.DigestCredentialFactory">DigestCredentialFactory</a></code>. The former
141 knows how to challenge HTTP clients to do basic authentication; the
142 latter, digest authentication. We'll use digest here:</p>
144 <pre class="python"><p class="py-linenumber">1
147 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">DigestCredentialFactory</span>
149 <span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">"md5"</span>, <span class="py-src-string">"example.org"</span>)
152 <p>The two parameters to this constructor are the hash algorithm and
153 the HTTP authentication realm which will be used. The only other valid
154 hash algorithm is "sha" (but be careful, MD5 is more widely supported
155 than SHA). The HTTP authentication realm is mostly just a string that
156 is presented to the user to let them know why they're authenticating
157 (you can read more about this in
158 the <a href="http://tools.ietf.org/html/rfc2617" shape="rect">RFC</a>).</p>
160 <p>With those things created, we can finally
161 instantiate <code>HTTPAuthSessionWrapper</code>:</p>
163 <pre class="python"><p class="py-linenumber">1
166 </p><span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>
168 <span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
171 <p>There's just one last thing that needs to be done
172 here. When <a href="rpy-scripts.html" shape="rect">rpy scripts</a> were
173 introduced, it was mentioned that they are evaluated in an unusual
174 context. This is the first example that actually needs to take this
175 into account. It so happens that <code>DigestCredentialFactory</code>
176 instances are stateful. Authentication will only succeed if the same
177 instance is used to both generate challenges and examine the responses
178 to those challenges. However, the normal mode of operation for an rpy
179 script is for it to be re-executed for every request. This leads to a
180 new <code>DigestCredentialFactory</code> being created for every request, preventing
181 any authentication attempt from ever succeeding.</p>
183 <p>There are two ways to deal with this. First, and the better of the two ways,
184 we could move almost all of the code into a real Python module, including the
185 code that instantiates the <code>DigestCredentialFactory</code>. This would
186 ensure that the same instance was used for every request. Second, and the easier
187 of the two ways, we could add a call to <code>cache()</code> to the beginning of
190 <pre class="python"><p class="py-linenumber">1
191 </p><span class="py-src-variable">cache</span>()
194 <p><code>cache</code> is part of the globals of any rpy script, so you don't
195 need to import it (it's okay to be cringing at this
196 point). Calling <code>cache</code> makes Twisted re-use the result of the first
197 evaluation of the rpy script for subsequent requests too - just what we want in
200 <p>Here's the complete example (with imports re-arranged to the more
201 conventional style):</p>
203 <pre class="python"><p class="py-linenumber"> 1
225 </p><span class="py-src-variable">cache</span>()
227 <span class="py-src-keyword">from</span> <span class="py-src-variable">zope</span>.<span class="py-src-variable">interface</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">implements</span>
229 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">portal</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IRealm</span>, <span class="py-src-variable">Portal</span>
230 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">cred</span>.<span class="py-src-variable">checkers</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">FilePasswordDB</span>
231 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">static</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">File</span>
232 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">resource</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">IResource</span>
233 <span class="py-src-keyword">from</span> <span class="py-src-variable">twisted</span>.<span class="py-src-variable">web</span>.<span class="py-src-variable">guard</span> <span class="py-src-keyword">import</span> <span class="py-src-variable">HTTPAuthSessionWrapper</span>, <span class="py-src-variable">DigestCredentialFactory</span>
235 <span class="py-src-keyword">class</span> <span class="py-src-identifier">PublicHTMLRealm</span>(<span class="py-src-parameter">object</span>):
236 <span class="py-src-variable">implements</span>(<span class="py-src-variable">IRealm</span>)
238 <span class="py-src-keyword">def</span> <span class="py-src-identifier">requestAvatar</span>(<span class="py-src-parameter">self</span>, <span class="py-src-parameter">avatarId</span>, <span class="py-src-parameter">mind</span>, *<span class="py-src-parameter">interfaces</span>):
239 <span class="py-src-keyword">if</span> <span class="py-src-variable">IResource</span> <span class="py-src-keyword">in</span> <span class="py-src-variable">interfaces</span>:
240 <span class="py-src-keyword">return</span> (<span class="py-src-variable">IResource</span>, <span class="py-src-variable">File</span>(<span class="py-src-string">"/home/%s/public_html"</span> % (<span class="py-src-variable">avatarId</span>,)), <span class="py-src-keyword">lambda</span>: <span class="py-src-variable">None</span>)
241 <span class="py-src-keyword">raise</span> <span class="py-src-variable">NotImplementedError</span>()
243 <span class="py-src-variable">portal</span> = <span class="py-src-variable">Portal</span>(<span class="py-src-variable">PublicHTMLRealm</span>(), [<span class="py-src-variable">FilePasswordDB</span>(<span class="py-src-string">'httpd.password'</span>)])
245 <span class="py-src-variable">credentialFactory</span> = <span class="py-src-variable">DigestCredentialFactory</span>(<span class="py-src-string">"md5"</span>, <span class="py-src-string">"localhost:8080"</span>)
246 <span class="py-src-variable">resource</span> = <span class="py-src-variable">HTTPAuthSessionWrapper</span>(<span class="py-src-variable">portal</span>, [<span class="py-src-variable">credentialFactory</span>])
249 <p>And voila, a password-protected per-user Twisted Web server.</p>
253 <p><a href="../index.html">Index</a></p>
254 <span class="version">Version: 12.1.0</span>