all repos — site @ 64db133bcd5009df25ffbffbb1ba9f05f9cf315b

source for my site, found at icyphox.sh

Flask JWT with Login post
Anirudh Oppiliappan x@icyphox.sh
Wed, 24 Jun 2020 21:09:20 +0530
commit

64db133bcd5009df25ffbffbb1ba9f05f9cf315b

parent

b5dc1c050a6f316c2779eba3d2377d6000bca594

M pages/blog/feed.xmlpages/blog/feed.xml

@@ -11,7 +11,113 @@ <link>https://icyphox.sh/</link>

</image> <language>en-us</language> <copyright>Creative Commons BY-NC-SA 4.0</copyright> - <item><title>You don't need news</title><description><![CDATA[<p>News&#8212;the never ending feed of information pertaining to &#8220;current + <item><title>Flask-JWT-Extended × Flask-Login</title><description><![CDATA[<p>For the past few months, I&#8217;ve been working on building a backend for +<code>$STARTUP</code>, with a bunch of friends. I&#8217;ll probably write in detail about +it when we launch our beta. The backend is your bog standard REST API, +built on Flask&#8212;if you didn&#8217;t guess from the title already.</p> + +<p>Our existing codebase heavily relies on +<a href="https://flask-login.readthedocs.io">Flask-Login</a>; it offers some pretty +neat interfaces for dealing with users and their states. However, its +default mode of operation&#8212;sessions&#8212;don&#8217;t really fit into a Flask +app that&#8217;s really just an API. It&#8217;s not optimal. Besides, this is what +<a href="https://jwt.io">JWTs</a> were built for. </p> + +<p>I won&#8217;t bother delving deep into JSON web tokens, but the general +flow is like so:</p> + +<ul> +<li>client logs in via say <code>/login</code></li> +<li>a unique token is sent in the response</li> +<li>each subsequent request authenticated request is sent with the token</li> +</ul> + +<p>The neat thing about tokens is you can store stuff in them&#8212;&#8220;claims&#8221;, +as they&#8217;re called.</p> + +<h2 id="returning-an-access_token-to-the-client">returning an <code>access_token</code> to the client</h2> + +<p>The <code>access_token</code> is sent to the client upon login. The idea is simple, +perform your usual checks (username / password etc.) and login the user +via <code>flask_login.login_user</code>. Generate an access token using +<code>flask_jwt_extended.create_access_token</code>, store your user identity in it +(and other claims) and return it to the user in your <code>200</code> response.</p> + +<p>Here&#8217;s the excerpt from our codebase.</p> + +<div class="codehilite"><pre><span></span><code><span class="n">access_token</span> <span class="o">=</span> <span class="n">create_access_token</span><span class="p">(</span><span class="n">identity</span><span class="o">=</span><span class="n">email</span><span class="p">)</span> +<span class="n">login_user</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">remember</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">[</span><span class="s2">&quot;remember&quot;</span><span class="p">])</span> +<span class="k">return</span> <span class="n">good</span><span class="p">(</span><span class="s2">&quot;Logged in successfully!&quot;</span><span class="p">,</span> <span class="n">access_token</span><span class="o">=</span><span class="n">access_token</span><span class="p">)</span> +</code></pre></div> + +<p>But, for <code>login_user</code> to work, we need to setup a custom user loader to +pull out the identity from the request and return the user object.</p> + +<h2 id="defining-a-custom-user-loader-in-flask-login">defining a custom user loader in Flask-Login</h2> + +<p>By default, Flask-Login handles user loading via the <code>user_loader</code> +decorator, which should return a user object. However, since we want to +pull a user object from the incoming request (the token contains it), +we&#8217;ll have to write a custom user loader via the <code>request_loader</code> +decorator.</p> + +<div class="codehilite"><pre><span></span><code><span class="c1"># Checks the &#39;Authorization&#39; header by default.</span> +<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s2">&quot;JWT_TOKEN_LOCATION&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;json&quot;</span><span class="p">]</span> + +<span class="c1"># Defaults to &#39;identity&#39;, but the spec prefers &#39;sub&#39;.</span> +<span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="p">[</span><span class="s2">&quot;JWT_IDENTITY_CLAIM&quot;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&quot;sub&quot;</span> + +<span class="nd">@login</span><span class="o">.</span><span class="n">request_loader</span> +<span class="k">def</span> <span class="nf">load_person_from_request</span><span class="p">(</span><span class="n">request</span><span class="p">):</span> + <span class="k">try</span><span class="p">:</span> + <span class="n">token</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">json</span><span class="p">[</span><span class="s2">&quot;access_token&quot;</span><span class="p">]</span> + <span class="k">except</span> <span class="ne">Exception</span><span class="p">:</span> + <span class="k">return</span> <span class="kc">None</span> + <span class="n">data</span> <span class="o">=</span> <span class="n">decode_token</span><span class="p">(</span><span class="n">token</span><span class="p">)</span> + <span class="c1"># this can be your &#39;User&#39; class</span> + <span class="n">person</span> <span class="o">=</span> <span class="n">PersonSignup</span><span class="o">.</span><span class="n">query</span><span class="o">.</span><span class="n">filter_by</span><span class="p">(</span><span class="n">email</span><span class="o">=</span><span class="n">data</span><span class="p">[</span><span class="s2">&quot;sub&quot;</span><span class="p">])</span><span class="o">.</span><span class="n">first</span><span class="p">()</span> + <span class="k">if</span> <span class="n">person</span><span class="p">:</span> + <span class="k">return</span> <span class="n">person</span> + <span class="k">return</span> <span class="kc">None</span> +</code></pre></div> + +<p>There&#8217;s just one mildly annoying thing to deal with, though. +Flask-Login insists on setting a session cookie. We will have to disable +this behaviour ourselves. And the best part? There&#8217;s no documentation +for this&#8212;well there is, but it&#8217;s incomplete and points to deprecated +functions.</p> + +<h2 id="disabling-flask-logins-session-cookie">disabling Flask-Login&#8217;s session cookie</h2> + +<p>To do this, we define a custom session interface, like so:</p> + +<div class="codehilite"><pre><span></span><code><span class="kn">from</span> <span class="nn">flask.sessions</span> <span class="kn">import</span> <span class="n">SecureCookieSessionInterface</span> +<span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">g</span> +<span class="kn">from</span> <span class="nn">flask_login</span> <span class="kn">import</span> <span class="n">user_loaded_from_request</span> + +<span class="nd">@user_loaded_from_request</span><span class="o">.</span><span class="n">connect</span> +<span class="k">def</span> <span class="nf">user_loaded_from_request</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span> + <span class="n">g</span><span class="o">.</span><span class="n">login_via_request</span> <span class="o">=</span> <span class="kc">True</span> + + +<span class="k">class</span> <span class="nc">CustomSessionInterface</span><span class="p">(</span><span class="n">SecureCookieSessionInterface</span><span class="p">):</span> + <span class="k">def</span> <span class="nf">should_set_cookie</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> + <span class="k">return</span> <span class="kc">False</span> + + <span class="k">def</span> <span class="nf">save_session</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span> + <span class="k">if</span> <span class="n">g</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">&quot;login_via_request&quot;</span><span class="p">):</span> + <span class="k">return</span> + <span class="k">return</span> <span class="nb">super</span><span class="p">(</span><span class="n">CustomSessionInterface</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span><span class="o">.</span><span class="n">save_session</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span> + + +<span class="n">app</span><span class="o">.</span><span class="n">session_interface</span> <span class="o">=</span> <span class="n">CustomSessionInterface</span><span class="p">()</span> +</code></pre></div> + +<p>In essence, this checks the global store <code>g</code> for <code>login_via_request</code> and +and doesn&#8217;t set a cookie in that case. I&#8217;ve submitted a PR upstream for +this to be included in the docs +(<a href="https://github.com/maxcountryman/flask-login/pull/514">#514</a>).</p> +]]></description><link>https://icyphox.sh/blog/flask-jwt-login</link><pubDate>Wed, 24 Jun 2020 00:00:00 +0000</pubDate><guid>https://icyphox.sh/blog/flask-jwt-login</guid></item><item><title>You don't need news</title><description><![CDATA[<p>News&#8212;the never ending feed of information pertaining to &#8220;current events&#8221;, politics, trivia, and other equally useless junk. News today is literally just this: &#8220;&lt;big name person&gt; did/said &lt;dumb thing&gt;!&#8221;, &#8220;&lt;group&gt; protests against &lt;bad thing&gt;!&#8221;, and so on. Okay, shit&#8217;s going
A pages/blog/flask-jwt-login.md

@@ -0,0 +1,117 @@

+--- +template: +url: flask-jwt-login +title: Flask-JWT-Extended × Flask-Login +subtitle: Apparently I do webshit now +date: 2020-06-24 +--- + +For the past few months, I've been working on building a backend for +`$STARTUP`, with a bunch of friends. I'll probably write in detail about +it when we launch our beta. The backend is your bog standard REST API, +built on Flask -- if you didn't guess from the title already. + +Our existing codebase heavily relies on +[Flask-Login](https://flask-login.readthedocs.io); it offers some pretty +neat interfaces for dealing with users and their states. However, its +default mode of operation -- sessions -- don't really fit into a Flask +app that's really just an API. It's not optimal. Besides, this is what +[JWTs](https://jwt.io) were built for. + +I won't bother delving deep into JSON web tokens, but the general +flow is like so: + +- client logs in via say `/login` +- a unique token is sent in the response +- each subsequent request authenticated request is sent with the token + +The neat thing about tokens is you can store stuff in them -- "claims", +as they're called. + +## returning an `access_token` to the client + +The `access_token` is sent to the client upon login. The idea is simple, +perform your usual checks (username / password etc.) and login the user +via `flask_login.login_user`. Generate an access token using +`flask_jwt_extended.create_access_token`, store your user identity in it +(and other claims) and return it to the user in your `200` response. + +Here's the excerpt from our codebase. + +```python +access_token = create_access_token(identity=email) +login_user(user, remember=request.json["remember"]) +return good("Logged in successfully!", access_token=access_token) +``` + +But, for `login_user` to work, we need to setup a custom user loader to +pull out the identity from the request and return the user object. + +## defining a custom user loader in Flask-Login + +By default, Flask-Login handles user loading via the `user_loader` +decorator, which should return a user object. However, since we want to +pull a user object from the incoming request (the token contains it), +we'll have to write a custom user loader via the `request_loader` +decorator. + + +```python +# Checks the 'Authorization' header by default. +app.config["JWT_TOKEN_LOCATION"] = ["json"] + +# Defaults to 'identity', but the spec prefers 'sub'. +app.config["JWT_IDENTITY_CLAIM"] = "sub" + +@login.request_loader +def load_person_from_request(request): + try: + token = request.json["access_token"] + except Exception: + return None + data = decode_token(token) + # this can be your 'User' class + person = PersonSignup.query.filter_by(email=data["sub"]).first() + if person: + return person + return None +``` + + +There's just one mildly annoying thing to deal with, though. +Flask-Login insists on setting a session cookie. We will have to disable +this behaviour ourselves. And the best part? There's no documentation +for this -- well there is, but it's incomplete and points to deprecated +functions. + +## disabling Flask-Login's session cookie + +To do this, we define a custom session interface, like so: + +```python +from flask.sessions import SecureCookieSessionInterface +from flask import g +from flask_login import user_loaded_from_request + +@user_loaded_from_request.connect +def user_loaded_from_request(app, user=None): + g.login_via_request = True + + +class CustomSessionInterface(SecureCookieSessionInterface): + def should_set_cookie(self, *args, **kwargs): + return False + + def save_session(self, *args, **kwargs): + if g.get("login_via_request"): + return + return super(CustomSessionInterface, self).save_session(*args, **kwargs) + + +app.session_interface = CustomSessionInterface() +``` + +In essence, this checks the global store `g` for `login_via_request` and +and doesn't set a cookie in that case. I've submitted a PR upstream for +this to be included in the docs +([#514](https://github.com/maxcountryman/flask-login/pull/514)).
A pages/txt/flask-jwt-login.txt

@@ -0,0 +1,117 @@

+--- +template: +url: flask-jwt-login +title: Flask-JWT-Extended × Flask-Login +subtitle: Apparently I do webshit now +date: 2020-06-24 +--- + +For the past few months, I've been working on building a backend for +`$STARTUP`, with a bunch of friends. I'll probably write in detail about +it when we launch our beta. The backend is your bog standard REST API, +built on Flask -- if you didn't guess from the title already. + +Our existing codebase heavily relies on +[Flask-Login](https://flask-login.readthedocs.io); it offers some pretty +neat interfaces for dealing with users and their states. However, its +default mode of operation -- sessions -- don't really fit into a Flask +app that's really just an API. It's not optimal. Besides, this is what +[JWTs](https://jwt.io) were built for. + +I won't bother delving deep into JSON web tokens, but the general +flow is like so: + +- client logs in via say `/login` +- a unique token is sent in the response +- each subsequent request authenticated request is sent with the token + +The neat thing about tokens is you can store stuff in them -- "claims", +as they're called. + +## returning an `access_token` to the client + +The `access_token` is sent to the client upon login. The idea is simple, +perform your usual checks (username / password etc.) and login the user +via `flask_login.login_user`. Generate an access token using +`flask_jwt_extended.create_access_token`, store your user identity in it +(and other claims) and return it to the user in your `200` response. + +Here's the excerpt from our codebase. + +```python +access_token = create_access_token(identity=email) +login_user(user, remember=request.json["remember"]) +return good("Logged in successfully!", access_token=access_token) +``` + +But, for `login_user` to work, we need to setup a custom user loader to +pull out the identity from the request and return the user object. + +## defining a custom user loader in Flask-Login + +By default, Flask-Login handles user loading via the `user_loader` +decorator, which should return a user object. However, since we want to +pull a user object from the incoming request (the token contains it), +we'll have to write a custom user loader via the `request_loader` +decorator. + + +```python +# Checks the 'Authorization' header by default. +app.config["JWT_TOKEN_LOCATION"] = ["json"] + +# Defaults to 'identity', but the spec prefers 'sub'. +app.config["JWT_IDENTITY_CLAIM"] = "sub" + +@login.request_loader +def load_person_from_request(request): + try: + token = request.json["access_token"] + except Exception: + return None + data = decode_token(token) + # this can be your 'User' class + person = PersonSignup.query.filter_by(email=data["sub"]).first() + if person: + return person + return None +``` + + +There's just one mildly annoying thing to deal with, though. +Flask-Login insists on setting a session cookie. We will have to disable +this behaviour ourselves. And the best part? There's no documentation +for this -- well there is, but it's incomplete and points to deprecated +functions. + +## disabling Flask-Login's session cookie + +To do this, we define a custom session interface, like so: + +```python +from flask.sessions import SecureCookieSessionInterface +from flask import g +from flask_login import user_loaded_from_request + +@user_loaded_from_request.connect +def user_loaded_from_request(app, user=None): + g.login_via_request = True + + +class CustomSessionInterface(SecureCookieSessionInterface): + def should_set_cookie(self, *args, **kwargs): + return False + + def save_session(self, *args, **kwargs): + if g.get("login_via_request"): + return + return super(CustomSessionInterface, self).save_session(*args, **kwargs) + + +app.session_interface = CustomSessionInterface() +``` + +In essence, this checks the global store `g` for `login_via_request` and +and doesn't set a cookie in that case. I've submitted a PR upstream for +this to be included in the docs +([#514](https://github.com/maxcountryman/flask-login/pull/514)).
M templates/index.htmltemplates/index.html

@@ -32,6 +32,11 @@ <table>

<tbody> <tr> + <td align="left"><a href="/blog/flask-jwt-login">Flask-JWT-Extended × Flask-Login</a></td> + <td align="right">2020-06-24</td> + </tr> + + <tr> <td align="left"><a href="/blog/dont-news">You don't need news</a></td> <td align="right">2020-06-21</td> </tr>
M templates/text.htmltemplates/text.html

@@ -48,33 +48,29 @@ <hr>

<div class="openring"> <div class="openring-feed"> - <h4><a href="https://www.bellingcat.com/news/2020/06/18/levijatan-serbian-animal-rights-vigilantes-go-to-the-polls/">Levijatan: Serbian Animal Rights Vigilantes Go To The Polls</a></h4> - <p>In a scene from Srećni Ljudi (Happy People), a Serbian television show from the early 1990s, two young brothers harass a younger boy sitting on a bench, a dog underneath his feet. “Is that the little mongrel you got for your birthday?” One brother, played …</p> + <h4><a href="https://drewdevault.com/2020/06/21/BARE-message-encoding.html">Introducing the BARE message encoding</a></h4> + <p>I like stateless tokens. We started with stateful tokens: where a generated +string acts as a unique identifier for a resource, and the resource itself is +looked up separately. For example, your sr.ht OAuth token is a stateful token: +we just generate a rand…</p> - <p>via <a href="https://www.bellingcat.com">bellingcat</a> on Jun 18, 2020</p> + <p>via <a href="https://drewdevault.com">Drew DeVault's Blog</a> on Jun 21, 2020</p> </div> <div class="openring-feed"> - <h4><a href="https://peppe.rs/posts/turing_complete_type_systems/">Turing Complete Type Systems</a></h4> - <p>Rust’s type system is Turing complete: - -FizzBuzz with Rust Traits -A Forth implementation with Rust Traits + <h4><a href="https://gru.gq/2020/06/16/second-mover-advantage/">Second Mover Advantage</a></h4> + <p>Russian online disinformation has predominantly been focussed on amplifying existing narratives. It almost always uses “conspiracy theories,” or other fringe false narratives, invented by elements of the target audience. The reason is simple: it is cheap a…</p> -It is impossible to determine if a program written in a generally Turing complete system will ever stop. That is, it is impossible to write a program…</p> - - <p>via <a href="https://peppe.rs">nerdypepper's μblog</a> on Jun 18, 2020</p> + <p>via <a href="https://gru.gq">grugq’s domain</a> on Jun 16, 2020</p> </div> <div class="openring-feed"> - <h4><a href="https://k1ss.org/blog/20200525a">25/05/2020: This month in KISS (#2)</a></h4> - <p>Welcome to the second monthly update for KISS. This post will be -quite a long one, we've seen some nice changes this month and some -great work by the Community.…</p> + <h4><a href="https://www.bellingcat.com/resources/how-tos/2020/06/23/how-to-track-desert-locust-swarms/">How To Track Desert Locust Swarms</a></h4> + <p>Billions of desert locusts are swarming across East Africa, multiplying in numbers over several months of favorable rain and breeding conditions, creating what the UN Food and Agriculture Organization (FAO) called an “unprecedented threat to food security,…</p> - <p>via <a href="https://k1ss.org">KISS Linux Blog</a> on May 25, 2020</p> + <p>via <a href="https://www.bellingcat.com">bellingcat</a> on Jun 23, 2020</p> </div>