Files
responder/tutorial-auth.html
2026-07-17 21:00:53 +00:00

602 lines
66 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-content_root="./">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Authentication &#8212; responder 9.2.0 documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=5ecbeea2" />
<link rel="stylesheet" type="text/css" href="_static/basic.css?v=b08954a9" />
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=eb4102c5" />
<link rel="stylesheet" type="text/css" href="_static/copybutton.css?v=76b2166b" />
<link rel="stylesheet" type="text/css" href="_static/sphinx-design.min.css?v=95c83b7e" />
<link rel="stylesheet" type="text/css" href="_static/design-elements.e5416f61bae5d36adc6d722a2b6f8cff.css?v=452a8e97" />
<script src="_static/documentation_options.js?v=e8b940be"></script>
<script src="_static/doctools.js?v=fd6eb6e6"></script>
<script src="_static/sphinx_highlight.js?v=6ffebe34"></script>
<script src="_static/clipboard.min.js?v=a7894cd8"></script>
<script src="_static/copybutton.js?v=fd10adb8"></script>
<script>
</script>
<script src="_static/design-tabs.js?v=f930bc37"></script>
<script src="_static/design-elements.bbdccc18c4abea9397628f9fea3d48c2.js?v=03c7770e"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="WebSocket Tutorial" href="tutorial-websockets.html" />
<link rel="prev" title="Using SQLAlchemy" href="tutorial-sqlalchemy.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
</head><body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<section id="authentication">
<h1>Authentication<a class="headerlink" href="#authentication" title="Link to this heading"></a></h1>
<p>Every API that handles user data needs authentication — a way to verify
who is making a request. This guide covers the most common patterns:
API keys, JWT tokens, and how to build reusable auth guards with
Responder’s before-request hooks.</p>
<section id="api-key-authentication">
<h2>API Key Authentication<a class="headerlink" href="#api-key-authentication" title="Link to this heading"></a></h2>
<p>The simplest approach. The client sends a secret key in a header, and
your server checks it against a known value. This is common for
server-to-server communication and simple APIs:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">API_KEYS</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;sk-abc123&quot;</span><span class="p">,</span> <span class="s2">&quot;sk-def456&quot;</span><span class="p">}</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">check_api_key</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;X-API-Key&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">key</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">API_KEYS</span><span class="p">:</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;Invalid or missing API key&quot;</span><span class="p">}</span>
</pre></div>
</div>
<p>Because the before-request hook sets <code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code>, the route
handler is skipped entirely for unauthorized requests. The client never
reaches your endpoint — the guard catches them first.</p>
<p>The client sends the key like this:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ curl -H &quot;X-API-Key: sk-abc123&quot; http://localhost:5042/protected
</pre></div>
</div>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>To read a credential <em>inside</em> a route handler (rather than a guard), use
a typed parameter marker — <code class="docutils literal notranslate"><span class="pre">api_key:</span> <span class="pre">str</span> <span class="pre">=</span> <span class="pre">Header(None,</span> <span class="pre">alias=&quot;X-API-Key&quot;)</span></code>
injects the validated header straight into the handler. See <a class="reference internal" href="tour.html"><span class="doc">Feature Tour</span></a>
for the full set (<a class="reference internal" href="api.html#responder.Query" title="responder.Query"><code class="xref py py-func docutils literal notranslate"><span class="pre">Query()</span></code></a>, <a class="reference internal" href="api.html#responder.Header" title="responder.Header"><code class="xref py py-func docutils literal notranslate"><span class="pre">Header()</span></code></a>,
<a class="reference internal" href="api.html#responder.Cookie" title="responder.Cookie"><code class="xref py py-func docutils literal notranslate"><span class="pre">Cookie()</span></code></a>, <a class="reference internal" href="api.html#responder.Path" title="responder.Path"><code class="xref py py-func docutils literal notranslate"><span class="pre">Path()</span></code></a>).</p>
</div>
</section>
<section id="bearer-token-authentication">
<h2>Bearer Token Authentication<a class="headerlink" href="#bearer-token-authentication" title="Link to this heading"></a></h2>
<p>Bearer tokens are the standard for modern APIs. The client sends a token
in the <code class="docutils literal notranslate"><span class="pre">Authorization</span></code> header, and the server validates it. The most
common format is <a class="reference external" href="https://jwt.io/">JWT</a> (JSON Web Tokens).</p>
<p>Install PyJWT:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uv pip install pyjwt
</pre></div>
</div>
<p>Create a helper to encode and decode tokens:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">jwt</span>
<span class="kn">from</span><span class="w"> </span><span class="nn">datetime</span><span class="w"> </span><span class="kn">import</span> <span class="n">datetime</span><span class="p">,</span> <span class="n">timedelta</span><span class="p">,</span> <span class="n">timezone</span>
<span class="n">SECRET</span> <span class="o">=</span> <span class="s2">&quot;your-secret-key&quot;</span> <span class="c1"># load from the environment in production</span>
<span class="k">def</span><span class="w"> </span><span class="nf">create_token</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;sub&quot;</span><span class="p">:</span> <span class="n">user_id</span><span class="p">,</span>
<span class="s2">&quot;exp&quot;</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span> <span class="o">+</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">hours</span><span class="o">=</span><span class="mi">24</span><span class="p">),</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">jwt</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">SECRET</span><span class="p">,</span> <span class="n">algorithm</span><span class="o">=</span><span class="s2">&quot;HS256&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">verify_token</span><span class="p">(</span><span class="n">token</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">dict</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="n">jwt</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">SECRET</span><span class="p">,</span> <span class="n">algorithms</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;HS256&quot;</span><span class="p">])</span>
<span class="k">except</span> <span class="n">jwt</span><span class="o">.</span><span class="n">InvalidTokenError</span><span class="p">:</span>
<span class="k">return</span> <span class="kc">None</span>
</pre></div>
</div>
<p>Add a login endpoint that issues tokens, and a before-request hook that
verifies them:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/login&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;POST&quot;</span><span class="p">])</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
<span class="c1"># In a real app, check credentials against a database</span>
<span class="k">if</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;username&quot;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&quot;admin&quot;</span> <span class="ow">and</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;password&quot;</span><span class="p">)</span> <span class="o">==</span> <span class="s2">&quot;secret&quot;</span><span class="p">:</span>
<span class="n">token</span> <span class="o">=</span> <span class="n">create_token</span><span class="p">(</span><span class="n">user_id</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;token&quot;</span><span class="p">:</span> <span class="n">token</span><span class="p">}</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;Invalid credentials&quot;</span><span class="p">}</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">auth_guard</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="c1"># Skip auth for the login endpoint itself</span>
<span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">path</span> <span class="o">==</span> <span class="s2">&quot;/login&quot;</span><span class="p">:</span>
<span class="k">return</span>
<span class="n">auth</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;Authorization&quot;</span><span class="p">,</span> <span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">auth</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">&quot;Bearer &quot;</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;Missing bearer token&quot;</span><span class="p">}</span>
<span class="k">return</span>
<span class="n">token</span> <span class="o">=</span> <span class="n">auth</span><span class="p">[</span><span class="mi">7</span><span class="p">:]</span> <span class="c1"># Strip &quot;Bearer &quot;</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">verify_token</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="k">if</span> <span class="n">payload</span> <span class="ow">is</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;Invalid or expired token&quot;</span><span class="p">}</span>
<span class="k">return</span>
<span class="c1"># Store the authenticated user on the request state</span>
<span class="n">req</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">user_id</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s2">&quot;sub&quot;</span><span class="p">]</span>
</pre></div>
</div>
<p>Now any route can access the authenticated user:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/me&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;user_id&quot;</span><span class="p">:</span> <span class="n">req</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">user_id</span><span class="p">}</span>
</pre></div>
</div>
<p>The client flow:</p>
<ol class="arabic simple">
<li><p><code class="docutils literal notranslate"><span class="pre">POST</span> <span class="pre">/login</span></code> with credentials → receive a token</p></li>
<li><p>Include <code class="docutils literal notranslate"><span class="pre">Authorization:</span> <span class="pre">Bearer</span> <span class="pre">&lt;token&gt;</span></code> on every subsequent request</p></li>
<li><p>The token expires after 24 hours — the client must log in again</p></li>
</ol>
<p>For APIs that use a single auth scheme on selected routes, Responder’s auth
helpers are more direct than a global guard:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">BearerAuth</span>
<span class="n">bearer</span> <span class="o">=</span> <span class="n">BearerAuth</span><span class="p">(</span><span class="n">verify</span><span class="o">=</span><span class="k">lambda</span> <span class="n">token</span><span class="p">:</span> <span class="n">users</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">token</span><span class="p">))</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/me&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">bearer</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">get_me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;user&quot;</span><span class="p">:</span> <span class="n">user</span><span class="p">}</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">auth=</span></code> enforces the scheme, sends the correct <code class="docutils literal notranslate"><span class="pre">WWW-Authenticate</span></code> challenge
on failure, documents the route in OpenAPI when OpenAPI is enabled, and injects
the verified principal into a <code class="docutils literal notranslate"><span class="pre">user</span></code>, <code class="docutils literal notranslate"><span class="pre">principal</span></code>, or <code class="docutils literal notranslate"><span class="pre">auth</span></code> parameter.</p>
</section>
<section id="validating-jwts-with-jwtauth">
<h2>Validating JWTs with JWTAuth<a class="headerlink" href="#validating-jwts-with-jwtauth" title="Link to this heading"></a></h2>
<p>The hand-rolled guard above works, but
<code class="xref py py-class docutils literal notranslate"><span class="pre">JWTAuth</span></code> does the whole job — signature
validation, <code class="docutils literal notranslate"><span class="pre">exp</span></code>/<code class="docutils literal notranslate"><span class="pre">nbf</span></code>/<code class="docutils literal notranslate"><span class="pre">iat</span></code> checks (with configurable clock-skew
<code class="docutils literal notranslate"><span class="pre">leeway</span></code>), audience and issuer verification, and OpenAPI documentation —
in one declaration. It needs the optional PyJWT dependency:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uv pip install &#39;responder[jwt,orjson]&#39;
</pre></div>
</div>
<p>A real-world setup validates tokens minted by an identity provider for a
specific API:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">JWTAuth</span>
<span class="n">auth</span> <span class="o">=</span> <span class="n">JWTAuth</span><span class="p">(</span>
<span class="n">secret</span><span class="o">=</span><span class="s2">&quot;your-256-bit-secret&quot;</span><span class="p">,</span> <span class="c1"># HS256 shared secret</span>
<span class="n">audience</span><span class="o">=</span><span class="s2">&quot;https://api.example.com&quot;</span><span class="p">,</span> <span class="c1"># must match the token&#39;s aud</span>
<span class="n">issuer</span><span class="o">=</span><span class="s2">&quot;https://issuer.example.com&quot;</span><span class="p">,</span> <span class="c1"># must match the token&#39;s iss</span>
<span class="n">leeway</span><span class="o">=</span><span class="mi">30</span><span class="p">,</span> <span class="c1"># tolerate 30s of clock skew</span>
<span class="p">)</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/me&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">auth</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
<span class="c1"># ``user`` is the validated claims dict</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;sub&quot;</span><span class="p">:</span> <span class="n">user</span><span class="p">[</span><span class="s2">&quot;sub&quot;</span><span class="p">],</span> <span class="s2">&quot;scope&quot;</span><span class="p">:</span> <span class="n">user</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;scope&quot;</span><span class="p">)}</span>
</pre></div>
</div>
<p>Invalid, expired, or missing tokens reject with <code class="docutils literal notranslate"><span class="pre">401</span></code> and a <code class="docutils literal notranslate"><span class="pre">Bearer</span></code>
challenge before your handler runs.</p>
<p>For asymmetric algorithms, pass the issuer’s PEM public key as <code class="docutils literal notranslate"><span class="pre">secret</span></code>
with <code class="docutils literal notranslate"><span class="pre">algorithms=(&quot;RS256&quot;,)</span></code> — or skip key management entirely and point
<code class="docutils literal notranslate"><span class="pre">jwks_url</span></code> at the issuer’s key set. Keys are resolved by the token’s
<code class="docutils literal notranslate"><span class="pre">kid</span></code> header, cached (<code class="docutils literal notranslate"><span class="pre">jwks_cache_ttl</span></code> seconds), and refreshed
automatically on key rotation:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">auth</span> <span class="o">=</span> <span class="n">JWTAuth</span><span class="p">(</span>
<span class="n">jwks_url</span><span class="o">=</span><span class="s2">&quot;https://issuer.example.com/.well-known/jwks.json&quot;</span><span class="p">,</span>
<span class="n">algorithms</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;RS256&quot;</span><span class="p">,),</span>
<span class="n">audience</span><span class="o">=</span><span class="s2">&quot;https://api.example.com&quot;</span><span class="p">,</span>
<span class="n">issuer</span><span class="o">=</span><span class="s2">&quot;https://issuer.example.com&quot;</span><span class="p">,</span>
<span class="p">)</span>
</pre></div>
</div>
<p>(JWKS and <code class="docutils literal notranslate"><span class="pre">RS*</span></code>/<code class="docutils literal notranslate"><span class="pre">ES*</span></code> algorithms additionally require the
<code class="docutils literal notranslate"><span class="pre">cryptography</span></code> package.)</p>
<p>Because the claims dict is the principal, the standard OAuth2 <code class="docutils literal notranslate"><span class="pre">scope</span></code>
claim (or a <code class="docutils literal notranslate"><span class="pre">scopes</span></code>/<code class="docutils literal notranslate"><span class="pre">roles</span></code> claim) feeds directly into scope checks —
<code class="docutils literal notranslate"><span class="pre">requires</span></code> rejects with <code class="docutils literal notranslate"><span class="pre">403</span></code> and an <code class="docutils literal notranslate"><span class="pre">insufficient_scope</span></code> challenge
when a scope is missing:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/admin&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">auth</span><span class="o">.</span><span class="n">requires</span><span class="p">(</span><span class="s2">&quot;admin&quot;</span><span class="p">))</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">admin_dashboard</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span> <span class="o">...</span>
</pre></div>
</div>
<p>To map claims onto your own user object, pass <code class="docutils literal notranslate"><span class="pre">verify=</span></code> — it receives the
validated claims and returns the principal to inject (or a falsy value to
reject):</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">auth</span> <span class="o">=</span> <span class="n">JWTAuth</span><span class="p">(</span><span class="n">secret</span><span class="o">=</span><span class="n">SECRET</span><span class="p">,</span> <span class="n">verify</span><span class="o">=</span><span class="k">lambda</span> <span class="n">claims</span><span class="p">:</span> <span class="n">users</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">claims</span><span class="p">[</span><span class="s2">&quot;sub&quot;</span><span class="p">]))</span>
</pre></div>
</div>
</section>
<section id="oauth2-and-swagger-ui-s-authorize-button">
<h2>OAuth2 and Swagger UI’s Authorize Button<a class="headerlink" href="#oauth2-and-swagger-ui-s-authorize-button" title="Link to this heading"></a></h2>
<p>When your tokens come from an OAuth2 authorization server, document the
flow itself with <code class="xref py py-class docutils literal notranslate"><span class="pre">OAuth2Auth</span></code> — the OpenAPI
schema then emits a proper <code class="docutils literal notranslate"><span class="pre">type:</span> <span class="pre">oauth2</span></code> security scheme with flows and
scopes, and Swagger UI’s <em>Authorize</em> button lets consumers log in against
your issuer right from the docs page:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.auth</span><span class="w"> </span><span class="kn">import</span> <span class="n">JWTAuth</span><span class="p">,</span> <span class="n">OAuth2Auth</span>
<span class="n">oauth2</span> <span class="o">=</span> <span class="n">OAuth2Auth</span><span class="o">.</span><span class="n">authorization_code</span><span class="p">(</span>
<span class="s2">&quot;https://issuer.example.com/authorize&quot;</span><span class="p">,</span>
<span class="s2">&quot;https://issuer.example.com/oauth/token&quot;</span><span class="p">,</span>
<span class="n">scopes</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;read&quot;</span><span class="p">:</span> <span class="s2">&quot;Read your data&quot;</span><span class="p">,</span> <span class="s2">&quot;write&quot;</span><span class="p">:</span> <span class="s2">&quot;Modify your data&quot;</span><span class="p">},</span>
<span class="n">jwt</span><span class="o">=</span><span class="n">JWTAuth</span><span class="p">(</span>
<span class="n">jwks_url</span><span class="o">=</span><span class="s2">&quot;https://issuer.example.com/.well-known/jwks.json&quot;</span><span class="p">,</span>
<span class="n">algorithms</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;RS256&quot;</span><span class="p">,),</span>
<span class="n">audience</span><span class="o">=</span><span class="s2">&quot;https://api.example.com&quot;</span><span class="p">,</span>
<span class="p">),</span>
<span class="p">)</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/items&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="n">oauth2</span><span class="o">.</span><span class="n">requires</span><span class="p">(</span><span class="s2">&quot;read&quot;</span><span class="p">))</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="n">load_items</span><span class="p">(</span><span class="n">owner</span><span class="o">=</span><span class="n">user</span><span class="p">[</span><span class="s2">&quot;sub&quot;</span><span class="p">])</span>
</pre></div>
</div>
<p>At runtime, <code class="docutils literal notranslate"><span class="pre">OAuth2Auth</span></code> is a resource server, not an authorization
server: it extracts the <code class="docutils literal notranslate"><span class="pre">Authorization:</span> <span class="pre">Bearer</span></code> token and validates it,
either locally through the <code class="docutils literal notranslate"><span class="pre">jwt=</span></code> <code class="xref py py-class docutils literal notranslate"><span class="pre">JWTAuth</span></code>
(as above) or through a <code class="docutils literal notranslate"><span class="pre">verify=</span></code> introspection callable for opaque
tokens:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">introspect</span><span class="p">(</span><span class="n">token</span><span class="p">):</span>
<span class="k">async</span> <span class="k">with</span> <span class="n">httpx</span><span class="o">.</span><span class="n">AsyncClient</span><span class="p">()</span> <span class="k">as</span> <span class="n">client</span><span class="p">:</span>
<span class="n">response</span> <span class="o">=</span> <span class="k">await</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">INTROSPECTION_URL</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;token&quot;</span><span class="p">:</span> <span class="n">token</span><span class="p">})</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">response</span><span class="o">.</span><span class="n">json</span><span class="p">()</span>
<span class="k">return</span> <span class="n">data</span> <span class="k">if</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;active&quot;</span><span class="p">)</span> <span class="k">else</span> <span class="kc">None</span>
<span class="n">oauth2</span> <span class="o">=</span> <span class="n">OAuth2Auth</span><span class="o">.</span><span class="n">client_credentials</span><span class="p">(</span>
<span class="s2">&quot;https://issuer.example.com/oauth/token&quot;</span><span class="p">,</span>
<span class="n">scopes</span><span class="o">=</span><span class="p">{</span><span class="s2">&quot;svc&quot;</span><span class="p">:</span> <span class="s2">&quot;Service-to-service access&quot;</span><span class="p">},</span>
<span class="n">verify</span><span class="o">=</span><span class="n">introspect</span><span class="p">,</span>
<span class="p">)</span>
</pre></div>
</div>
<p>Besides <code class="docutils literal notranslate"><span class="pre">authorization_code</span></code> (interactive user login) and
<code class="docutils literal notranslate"><span class="pre">client_credentials</span></code> (machine-to-machine), a <code class="docutils literal notranslate"><span class="pre">password</span></code> constructor
covers the resource-owner-password flow, and multiple flows can be combined
by passing flow objects directly:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.auth</span><span class="w"> </span><span class="kn">import</span> <span class="p">(</span>
<span class="n">OAuth2Auth</span><span class="p">,</span>
<span class="n">OAuth2AuthorizationCodeFlow</span><span class="p">,</span>
<span class="n">OAuth2ClientCredentialsFlow</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">oauth2</span> <span class="o">=</span> <span class="n">OAuth2Auth</span><span class="p">(</span>
<span class="p">[</span>
<span class="n">OAuth2AuthorizationCodeFlow</span><span class="p">(</span><span class="n">AUTHORIZE_URL</span><span class="p">,</span> <span class="n">TOKEN_URL</span><span class="p">,</span> <span class="n">scopes</span><span class="o">=</span><span class="n">SCOPES</span><span class="p">),</span>
<span class="n">OAuth2ClientCredentialsFlow</span><span class="p">(</span><span class="n">TOKEN_URL</span><span class="p">,</span> <span class="n">scopes</span><span class="o">=</span><span class="n">SCOPES</span><span class="p">),</span>
<span class="p">],</span>
<span class="n">jwt</span><span class="o">=</span><span class="n">JWTAuth</span><span class="p">(</span><span class="n">jwks_url</span><span class="o">=</span><span class="n">JWKS_URL</span><span class="p">,</span> <span class="n">algorithms</span><span class="o">=</span><span class="p">(</span><span class="s2">&quot;RS256&quot;</span><span class="p">,)),</span>
<span class="p">)</span>
</pre></div>
</div>
<p>Like every auth helper, both classes compose with routers — declare
<code class="docutils literal notranslate"><span class="pre">Router(prefix=&quot;/v1&quot;,</span> <span class="pre">auth=oauth2)</span></code> (or
<code class="docutils literal notranslate"><span class="pre">api.include_router(router,</span> <span class="pre">auth=oauth2)</span></code>) to protect a whole group of
routes at once. See <a class="reference internal" href="routers.html"><span class="doc">Composing Apps with Routers</span></a>.</p>
</section>
<section id="skipping-auth-for-public-routes">
<h2>Skipping Auth for Public Routes<a class="headerlink" href="#skipping-auth-for-public-routes" title="Link to this heading"></a></h2>
<p>For APIs where most routes require the same auth scheme, put it on the app and
mark public routes explicitly:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">bearer</span> <span class="o">=</span> <span class="n">BearerAuth</span><span class="p">(</span><span class="n">verify</span><span class="o">=</span><span class="k">lambda</span> <span class="n">token</span><span class="p">:</span> <span class="n">users</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">token</span><span class="p">))</span>
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">auth</span><span class="o">=</span><span class="n">bearer</span><span class="p">)</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&quot;/login&quot;</span><span class="p">,</span> <span class="n">auth</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;token&quot;</span><span class="p">:</span> <span class="n">issue_token</span><span class="p">()}</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;/me&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">me</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;user&quot;</span><span class="p">:</span> <span class="n">user</span><span class="p">}</span>
</pre></div>
</div>
<p>Routes inherit <code class="docutils literal notranslate"><span class="pre">API(auth=...)</span></code> by default. Passing <code class="docutils literal notranslate"><span class="pre">auth=None</span></code> on a route
makes that route public and removes the inherited OpenAPI security requirement.</p>
</section>
<section id="custom-exception-for-auth-errors">
<h2>Custom Exception for Auth Errors<a class="headerlink" href="#custom-exception-for-auth-errors" title="Link to this heading"></a></h2>
<p>For a one-off rejection, reach for <a class="reference internal" href="api.html#responder.abort" title="responder.abort"><code class="xref py py-func docutils literal notranslate"><span class="pre">abort()</span></code></a> — it raises a
rendered HTTP error from anywhere in the request path (handler, hook, or
dependency) without importing Starlette:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder</span><span class="w"> </span><span class="kn">import</span> <span class="n">abort</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">auth_guard</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">path</span> <span class="ow">in</span> <span class="n">PUBLIC_PATHS</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">if</span> <span class="s2">&quot;Authorization&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
<span class="n">abort</span><span class="p">(</span><span class="mi">401</span><span class="p">,</span> <span class="n">detail</span><span class="o">=</span><span class="s2">&quot;Missing authorization header&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p><code class="docutils literal notranslate"><span class="pre">abort</span></code> renders as JSON for <code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/json</span></code> clients and plain
text otherwise.</p>
<p>When you want a <em>structured</em> error payload, define a custom exception and
register a handler instead:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">AuthError</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message</span><span class="o">=</span><span class="s2">&quot;Unauthorized&quot;</span><span class="p">,</span> <span class="n">status_code</span><span class="o">=</span><span class="mi">401</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">message</span> <span class="o">=</span> <span class="n">message</span>
<span class="bp">self</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="n">status_code</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">exception_handler</span><span class="p">(</span><span class="n">AuthError</span><span class="p">)</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">handle_auth_error</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="n">exc</span><span class="o">.</span><span class="n">status_code</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="n">exc</span><span class="o">.</span><span class="n">message</span><span class="p">}</span>
</pre></div>
</div>
<p>Now your auth guard can simply raise:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">auth_guard</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="k">if</span> <span class="n">req</span><span class="o">.</span><span class="n">url</span><span class="o">.</span><span class="n">path</span> <span class="ow">in</span> <span class="n">PUBLIC_PATHS</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">if</span> <span class="s2">&quot;Authorization&quot;</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">AuthError</span><span class="p">(</span><span class="s2">&quot;Missing authorization header&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>The decorator delegates to
<code class="docutils literal notranslate"><span class="pre">api.add_exception_handler(AuthError,</span> <span class="pre">handle_auth_error)</span></code> — call that form
directly when you wire handlers from a setup function rather than at import
time.</p>
</section>
<section id="using-sessions-for-web-apps">
<h2>Using Sessions for Web Apps<a class="headerlink" href="#using-sessions-for-web-apps" title="Link to this heading"></a></h2>
<p>For traditional web applications (with HTML pages and forms), cookie-based
sessions are simpler than tokens. The browser handles cookies automatically
— no client-side token management needed:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.sessions</span><span class="w"> </span><span class="kn">import</span> <span class="n">regenerate_session</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/login&quot;</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">&quot;POST&quot;</span><span class="p">])</span>
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">&quot;form&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="n">data</span><span class="p">[</span><span class="s2">&quot;username&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&quot;admin&quot;</span> <span class="ow">and</span> <span class="n">data</span><span class="p">[</span><span class="s2">&quot;password&quot;</span><span class="p">]</span> <span class="o">==</span> <span class="s2">&quot;secret&quot;</span><span class="p">:</span>
<span class="n">regenerate_session</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="c1"># rotate the id on login</span>
<span class="n">resp</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s2">&quot;user&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="s2">&quot;username&quot;</span><span class="p">]</span>
<span class="c1"># Honor ?next=, but never bounce off-site (open-redirect guard).</span>
<span class="n">target</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">params</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;next&quot;</span><span class="p">,</span> <span class="s2">&quot;/dashboard&quot;</span><span class="p">)</span>
<span class="n">api</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="n">target</span><span class="p">,</span> <span class="n">allow_external</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">401</span>
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="s2">&quot;&lt;p&gt;Invalid credentials&lt;/p&gt;&quot;</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/dashboard&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">dashboard</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;user&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">user</span><span class="p">:</span>
<span class="n">api</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="s2">&quot;/login&quot;</span><span class="p">)</span>
<span class="k">return</span>
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">&quot;&lt;h1&gt;Welcome, </span><span class="si">{</span><span class="n">user</span><span class="si">}</span><span class="s2">!&lt;/h1&gt;&quot;</span>
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/logout&quot;</span><span class="p">)</span>
<span class="k">def</span><span class="w"> </span><span class="nf">logout</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
<span class="n">api</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="s2">&quot;/login&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>Two security details in that login handler are worth calling out:</p>
<ul class="simple">
<li><p><strong>Rotate the session id on login.</strong> <code class="docutils literal notranslate"><span class="pre">regenerate_session(req)</span></code> issues a
fresh id after the privilege change, defeating session fixation. It takes
effect with a server-side <code class="docutils literal notranslate"><span class="pre">session_backend</span></code> (see <a class="reference internal" href="guide-config.html"><span class="doc">Configuration</span></a>);
for the default signed-cookie sessions the cookie <em>is</em> the session, so the
call is a harmless no-op.</p></li>
<li><p><strong>Guard user-controlled redirects.</strong> <code class="docutils literal notranslate"><span class="pre">api.redirect</span></code> allows external URLs
by default; pass <code class="docutils literal notranslate"><span class="pre">allow_external=False</span></code> for any target derived from user
input (like <code class="docutils literal notranslate"><span class="pre">?next=</span></code>) so an attacker can’t craft
<code class="docutils literal notranslate"><span class="pre">?next=https://evil.example</span></code> to bounce a freshly authenticated user
off-site. An external target then returns <code class="docutils literal notranslate"><span class="pre">400</span></code>.</p></li>
</ul>
<p>Sessions are secure by default — just set a stable secret key in
production:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">secret_key</span><span class="o">=</span><span class="s2">&quot;&lt;a long, random string&gt;&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>Generate one with:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python -c &quot;import secrets; print(secrets.token_urlsafe(32))&quot;
</pre></div>
</div>
<p>Better still, supply it out of band via the <code class="docutils literal notranslate"><span class="pre">RESPONDER_SECRET_KEY</span></code>
environment variable so it never lands in source control.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The old public default is gone: <code class="docutils literal notranslate"><span class="pre">secret_key=&quot;NOTASECRET&quot;</span></code> now raises
<a class="reference internal" href="api.html#responder.ext.sessions.SessionConfigError" title="responder.ext.sessions.SessionConfigError"><code class="xref py py-class docutils literal notranslate"><span class="pre">SessionConfigError</span></code></a>. With the default
<code class="docutils literal notranslate"><span class="pre">sessions=&quot;auto&quot;</span></code>, omitting a key mints a random per-process key and
logs a loud warning — fine for a quick local run, but it breaks across
restarts and multiple workers (everyone gets logged out). Set a real key
for anything you deploy. The cookie is also <code class="docutils literal notranslate"><span class="pre">Secure</span></code> in production by
default; pass <code class="docutils literal notranslate"><span class="pre">session_https_only=False</span></code> only if you genuinely serve
plain HTTP. See <a class="reference internal" href="guide-config.html"><span class="doc">Configuration</span></a> for the full set of session knobs
(<code class="docutils literal notranslate"><span class="pre">session_cookie</span></code>, <code class="docutils literal notranslate"><span class="pre">session_https_only</span></code>, <code class="docutils literal notranslate"><span class="pre">session_same_site</span></code>,
<code class="docutils literal notranslate"><span class="pre">session_max_age</span></code>, <code class="docutils literal notranslate"><span class="pre">session_backend</span></code>).</p>
</div>
<p>Session data is signed, not encrypted — clients can read it but can’t forge
it. Never store secrets like passwords in the session.</p>
</section>
<section id="role-based-access-control">
<h2>Role-Based Access Control<a class="headerlink" href="#role-based-access-control" title="Link to this heading"></a></h2>
<p>For APIs where different users have different permissions, embed the
role in the token and check it in route-specific guards:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">create_token</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">role</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s2">&quot;user&quot;</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
<span class="n">payload</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">&quot;sub&quot;</span><span class="p">:</span> <span class="n">user_id</span><span class="p">,</span>
<span class="s2">&quot;role&quot;</span><span class="p">:</span> <span class="n">role</span><span class="p">,</span>
<span class="s2">&quot;exp&quot;</span><span class="p">:</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">timezone</span><span class="o">.</span><span class="n">utc</span><span class="p">)</span> <span class="o">+</span> <span class="n">timedelta</span><span class="p">(</span><span class="n">hours</span><span class="o">=</span><span class="mi">24</span><span class="p">),</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">jwt</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">SECRET</span><span class="p">,</span> <span class="n">algorithm</span><span class="o">=</span><span class="s2">&quot;HS256&quot;</span><span class="p">)</span>
</pre></div>
</div>
<p>Create a helper that checks for a specific role:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.types</span><span class="w"> </span><span class="kn">import</span> <span class="n">Hook</span>
<span class="k">def</span><span class="w"> </span><span class="nf">require_role</span><span class="p">(</span><span class="o">*</span><span class="n">roles</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Hook</span><span class="p">:</span>
<span class="w"> </span><span class="sd">&quot;&quot;&quot;Before-request hook factory that restricts by role.&quot;&quot;&quot;</span>
<span class="k">def</span><span class="w"> </span><span class="nf">check</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">user_role</span> <span class="o">=</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">state</span><span class="p">,</span> <span class="s2">&quot;role&quot;</span><span class="p">,</span> <span class="kc">None</span><span class="p">)</span>
<span class="k">if</span> <span class="n">user_role</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">roles</span><span class="p">:</span>
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">403</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;error&quot;</span><span class="p">:</span> <span class="s2">&quot;Insufficient permissions&quot;</span><span class="p">}</span>
<span class="k">return</span> <span class="n">check</span>
</pre></div>
</div>
<p>Use it on specific routes:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">&quot;/admin/users&quot;</span><span class="p">,</span> <span class="n">before</span><span class="o">=</span><span class="n">require_role</span><span class="p">(</span><span class="s2">&quot;admin&quot;</span><span class="p">))</span>
<span class="k">def</span><span class="w"> </span><span class="nf">list_all_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">&quot;users&quot;</span><span class="p">:</span> <span class="p">[</span><span class="o">...</span><span class="p">]}</span>
</pre></div>
</div>
<p>And store the role during token verification:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># In your auth_guard:</span>
<span class="n">req</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">user_id</span> <span class="o">=</span> <span class="n">payload</span><span class="p">[</span><span class="s2">&quot;sub&quot;</span><span class="p">]</span>
<span class="n">req</span><span class="o">.</span><span class="n">state</span><span class="o">.</span><span class="n">role</span> <span class="o">=</span> <span class="n">payload</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;role&quot;</span><span class="p">,</span> <span class="s2">&quot;user&quot;</span><span class="p">)</span>
</pre></div>
</div>
</section>
<section id="choosing-an-auth-strategy">
<h2>Choosing an Auth Strategy<a class="headerlink" href="#choosing-an-auth-strategy" title="Link to this heading"></a></h2>
<ul class="simple">
<li><p><strong>API keys</strong> — simplest. Good for server-to-server, CLI tools, and
internal services. No expiration unless you build it.</p></li>
<li><p><strong>JWT tokens</strong> — standard for SPAs and mobile apps. Stateless, so
they scale well. Downside: you can’t revoke them without a blocklist.</p></li>
<li><p><strong>Sessions</strong> — best for traditional web apps with HTML forms. The
browser manages cookies automatically. Stateful — the server controls
the session lifecycle.</p></li>
</ul>
<p>Start with API keys for internal tools, JWT for public APIs, and
sessions for web apps with login pages.</p>
</section>
</section>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="Main">
<div class="sphinxsidebarwrapper"><p class="logo">
<a href="index.html">
<img class="logo" src="_static/responder.png" />
</a>
</p>
<p>
<strong>Responder</strong> — a familiar HTTP service framework for Python.
<br />
<small>v9.2.0</small>
</p>
<h3>Useful Links</h3>
<ul>
<li><a href="https://github.com/kennethreitz/responder">Responder @ GitHub</a></li>
<li><a href="https://pypi.org/project/responder/">Responder @ PyPI</a></li>
<li><a href="https://github.com/kennethreitz/responder/issues">Issue Tracker</a></li>
</ul><h3>Navigation</h3>
<p class="caption" role="heading"><span class="caption-text">User Guide</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="quickstart.html">Quick Start</a></li>
<li class="toctree-l1"><a class="reference internal" href="tour.html">Feature Tour</a></li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a></li>
<li class="toctree-l1"><a class="reference internal" href="routers.html">Composing Apps with Routers</a></li>
<li class="toctree-l1"><a class="reference internal" href="guide-config.html">Configuration</a></li>
<li class="toctree-l1"><a class="reference internal" href="clientgen.html">Generated Clients</a></li>
<li class="toctree-l1"><a class="reference internal" href="deployment.html">Deployment</a></li>
<li class="toctree-l1"><a class="reference internal" href="testing.html">Testing</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">Tutorials</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="tutorial-rest.html">Building a REST API</a></li>
<li class="toctree-l1"><a class="reference internal" href="tutorial-sqlalchemy.html">Using SQLAlchemy</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Authentication</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#api-key-authentication">API Key Authentication</a></li>
<li class="toctree-l2"><a class="reference internal" href="#bearer-token-authentication">Bearer Token Authentication</a></li>
<li class="toctree-l2"><a class="reference internal" href="#validating-jwts-with-jwtauth">Validating JWTs with JWTAuth</a></li>
<li class="toctree-l2"><a class="reference internal" href="#oauth2-and-swagger-ui-s-authorize-button">OAuth2 and Swagger UI’s Authorize Button</a></li>
<li class="toctree-l2"><a class="reference internal" href="#skipping-auth-for-public-routes">Skipping Auth for Public Routes</a></li>
<li class="toctree-l2"><a class="reference internal" href="#custom-exception-for-auth-errors">Custom Exception for Auth Errors</a></li>
<li class="toctree-l2"><a class="reference internal" href="#using-sessions-for-web-apps">Using Sessions for Web Apps</a></li>
<li class="toctree-l2"><a class="reference internal" href="#role-based-access-control">Role-Based Access Control</a></li>
<li class="toctree-l2"><a class="reference internal" href="#choosing-an-auth-strategy">Choosing an Auth Strategy</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="tutorial-websockets.html">WebSocket Tutorial</a></li>
<li class="toctree-l1"><a class="reference internal" href="tutorial-middleware.html">Writing Middleware</a></li>
<li class="toctree-l1"><a class="reference internal" href="tutorial-flask.html">Migrating from Flask</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">Reference</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="api.html">API Reference</a></li>
<li class="toctree-l1"><a class="reference internal" href="cli.html">Command Line Interface</a></li>
<li class="toctree-l1"><a class="reference internal" href="runtime-contracts.html">Runtime Contracts</a></li>
</ul>
<p class="caption" role="heading"><span class="caption-text">Project</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="changes.html">Changelog</a></li>
<li class="toctree-l1"><a class="reference internal" href="migration-v9.html">Migrating to v9</a></li>
<li class="toctree-l1"><a class="reference internal" href="migration-v8.html">Migrating to v8</a></li>
<li class="toctree-l1"><a class="reference internal" href="migration-v7.html">Migrating to v7</a></li>
<li class="toctree-l1"><a class="reference internal" href="migration-v6.html">Migrating to v6</a></li>
<li class="toctree-l1"><a class="reference internal" href="migration-v5.html">Migrating to v5</a></li>
<li class="toctree-l1"><a class="reference internal" href="sandbox.html">Sandbox</a></li>
<li class="toctree-l1"><a class="reference internal" href="backlog.html">Backlog</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
<li>Previous: <a href="tutorial-sqlalchemy.html" title="previous chapter">Using SQLAlchemy</a></li>
<li>Next: <a href="tutorial-websockets.html" title="next chapter">WebSocket Tutorial</a></li>
</ul></li>
</ul>
</div>
<search id="searchbox" style="display: none" role="search">
<h3 id="searchlabel">Quick search</h3>
<div class="searchformwrapper">
<form class="search" action="search.html" method="get">
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
<input type="submit" value="Go" />
</form>
</div>
</search>
<script>document.getElementById('searchbox').style.display = "block"</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&#169;2018-2026, Kenneth Reitz.
|
<a href="_sources/tutorial-auth.rst.txt"
rel="nofollow">Page source</a>
</div>
<a href="https://github.com/kennethreitz/responder" class="github">
<img src="_static/forkme_right_darkblue.png" alt="Fork me on GitHub" class="github"/>
</a>
</body>
</html>