Jekyll2025-06-25T23:25:35+00:00https://yoongkang.com/feed.xmlLim Yoong KangName order2021-01-13T00:00:00+00:002021-01-13T00:00:00+00:00https://yoongkang.com/blog/name-order<p>Just a little something about the first name, last name ordering.</p>
<p>In many parts of Asia, it’s more common to use the surname first, and then the given name. That’s obviously different in the West. Sometimes there are historical reasons why the order is flipped only in English, such as Japanese names. There’s an <a href="https://www.japantimes.co.jp/life/2020/09/15/language/japanese-name-order/">ongoing discussion to change that in Japan</a>, so we can expect even that to change in the future (and in fact, it’s already changing now).</p>
<p>Back when I lived in Asia, it was normal to call me “Lim Yoong Kang”, where “Lim” is my surname, and “Yoong Kang” my given name. However, when I arrived in Australia, for various reasons it made things easier to reverse the order – making it “Yoong Kang Lim”.</p>
<p>At first that was because I filled in some forms, and then most IT systems automatically assigned the default ordering. Later, it became more convenient to just use that order everywhere, otherwise weird things start to happen (like people assuming “Lim” is my given name). After a while I kind of just accepted that, and it became natural.</p>
<p>I told myself it was a far better compromise than, for example, <a href="https://www.sbs.com.au/news/job-hunters-change-foreign-sounding-names">giving myself an Anglicised name</a> so that people don’t bin my resume. No judgement to those who did end up doing that, as it’s a perfectly understandable decision, but it was something I didn’t want to do.</p>
<p>Anyway, long story short, it kind of stuck after a while and I never really felt right about it, although it wasn’t something I worried about too much.</p>
<p>Well, in 2021, I’ve decided to make more of an effort to change that. The Western ordering is simply that, it’s just Western. I’d probably even call it Eurocentric. The assumption that everyone follows that name order in a culturally diverse society like Australia is clearly invalid. So why keep using it? I’ve decided I don’t have to pander to other people, and I especially don’t want to pander to people who ignore these aspects of my culture. It’s my own name, I should get to decide which order is appropriate.</p>
<p>With that said, starting from this blog I want the ordering of my name to better reflect the conventions of my heritage. I’m hoping I could change that in other places too, where practical.</p>Just a little something about the first name, last name ordering.I made the front page of Hacker News2020-04-12T00:00:00+00:002020-04-12T00:00:00+00:00https://yoongkang.com/blog/i-made-the-front-page-of-hackernews<p>Yesterday, someone submitted one of my blog posts from 2018 to Hacker News, the one about my experience dealing with <a href="/blog/pdf-forms-with-python/">fillable PDF forms</a>.</p>
<p>It made the <a href="https://news.ycombinator.com/front?day=2020-04-11">front page of Hacker News</a>. For a while, it was ranked pretty high:</p>
<p><img src="/images/anxiety/hackernews-front-page.png" alt="Front Page of Hacker News" /></p>
<p>Just thought that was worth mentioning!</p>Yesterday, someone submitted one of my blog posts from 2018 to Hacker News, the one about my experience dealing with fillable PDF forms.Year 2019 in review2020-01-30T00:00:00+00:002020-01-30T00:00:00+00:00https://yoongkang.com/blog/year-2019-in-review<p>It sucked.</p>It sucked.Statelessness is not unique to JWTs2019-12-31T00:00:00+00:002019-12-31T00:00:00+00:00https://yoongkang.com/blog/statelessness-is-not-unique-to-jwts<p>JSON Web Tokens (JWTs) are commonly used today in a number of applications, especially as bearer tokens.</p>
<p>JWTs are often said to have the advantage of being “stateless”.</p>
<p>By that, it means that it is possible to validate a token without checking any persistent store, like a database or something like Redis. For every authenticated request, we are able to avoid making an extra hit to the database just to validate the user session.</p>
<p>This statelessness is sometimes said to be the <a href="https://www.jbspeakr.cc/purpose-jwt-stateless-authentication/">main purpose of using JWTs</a>.</p>
<p>In reality, statelessness is a feature that is not unique to JWTs, and I would argue here that it is probably not the best reason to use JWTs if that is the only benefit you get out of it.</p>
<p>Let’s first examine what properties allow JWTs to be stateless, and then discuss if this is indeed the main advantage of JWTs.</p>
<h2 id="jwts-and-cryptographic-signing">JWTs and cryptographic signing</h2>
<p>When people talk about “JWTs”, what they usually mean is a variant called <a href="https://tools.ietf.org/html/rfc7515">JSON Web Signature, or JWS</a>. Another variant is called <a href="https://tools.ietf.org/html/rfc7516">JSON Web Encryption</a>, which as far as I know is not as commonly used.</p>
<p>As its name implies, the JWS variant uses cryptographic signing. If you are new to cryptography, signing is a way to create something called a “signature”, a bit of data that is impossible to create without a secret key. By checking this signature, we can verify that it was generated by the party holding this secret key.</p>
<p>You can <a href="https://jwt.io">find examples of JWTs online</a>, but a JWT’s structure is basically this:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>header.payload.signature
</code></pre></div></div>
<p>There are three parts, separated by a period. The header and payload are both encoded with URL-safe base64 encoding, which means that anyone can see their contents by decoding them. The last part, the signature, is generated using cryptographic signing.</p>
<p>This signature cannot be generated without knowing the secret, which means that a server can be confident that it was generated by a party that knows this secret. If this was generated using symmetric cryptography, then the same key will be used for both signing and validating the signature. The JWT specification also allows for signing using asymmetric cryptography, which means that it is signed with a private key, and validated with a public key.</p>
<p>It also follows that, in order to validate the token, a server will only need to know the secret key (if using symmetric signing) or the public key (if using asymmetric signing). This means the server does not need to check a database to verify that it is a valid token, allowing JWTs to be “stateless”.</p>
<h2 id="that-sounds-great-whats-the-problem">That sounds great, what’s the problem?</h2>
<p>In reality, there is nothing about this that is unique to JWTs.</p>
<p>A token can be stateless without it having to be a JWT. It just needs cryptographic signing, the same way JWTs use cryptographic signing.</p>
<p>Essentially, all you need to do is use HMAC with SHA to generate a signature, and send it together with the data serialised in some way (e.g. URL-safe base64 encoding).</p>
<p>In fact, major web frameworks like Django allow you to use stateless tokens. If you configure Django to use <a href="https://docs.djangoproject.com/en/3.0/topics/http/sessions/#using-cookie-based-sessions">cookie-based sessions</a>, you are essentially using stateless signed tokens, only that the token is stored and transported in a cookie. That is why it also comes with a few problems of stateless tokens that JWTs also have, namely the problem of invalidating the tokens. No surprises that Django’s implementation uses HMAC with SHA.</p>
<p>By avoiding JWTs, you also avoid all the problems that come with parsing the header. The JWT header was the <a href="https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/">source of a number of security issues in the past</a>, specifically that it allowed the client to specify the algorithm that the JWT is signed with, of which “none” is an option. Luckily, this has been fixed in most libraries, but that is still not a great sign.</p>
<p>In my experience, I found that there are other security footguns in the JWT/JOSE specification as well, especially those that come with asymmetric algorithms. For example there are things like the <code class="highlighter-rouge">jku</code> claim in the header, that tells you the URL of the public key you should use to validate the token. That is a strange thing to include in the token, because an attacker could generate a token and make the <code class="highlighter-rouge">jku</code> claim point to a public key that belongs to the attacker. If the developer naively trusts these built-in claims, then they make their systems vulnerable to these exploits. More information <a href="https://github.com/ticarpi/jwt_tool/wiki/Vulnerable-claims">can be found here</a>. The specification, in my opinion, has a number of things that require the developer to know about and guard against.</p>
<p>If the same server or party is both generating and also validating the token, it is probably best to avoid JWTs entirely. Using HMAC and SHA requires very rudimentary cryptography knowledge, has good library support (especially if you use a web framework like Django), and is generally not that easy to mess up.</p>
<p>On top of that, by using HMAC + SHA to generate a token, the token can still be stateless! So, you get all the benefits of statelessness, and none of the disadvantages.</p>
<h2 id="so-are-jwts-useful-at-all">So are JWTs useful at all?</h2>
<p>Some would argue that <a href="https://kevin.burke.dev/kevin/things-to-use-instead-of-jwt/">JWTs are not useful, at all</a>. Especially not for <a href="http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/">session tokens</a>.</p>
<p>I would probably take a softer stance, and say that it can be a viable and pragmatic option in some cases. Note that I do not work in cybersecurity, and this opinion comes solely from the perspective of a developer who builds applications.</p>
<p>One scenario where I would probably use JWTs is if two different servers need to send data to each other, and these two servers are owned by separate organisations. This comes up more often than you would expect.</p>
<p>For example, let’s say I am writing a Python app, and my client has a Java web service. For some reason we need to exchange some data, probably via the user’s browser or mobile app.</p>
<p>By the way, this is a <a href="https://www.digitalocean.com/community/tutorials/an-introduction-to-oauth-2">situation similar to OAuth2</a>, where an authorisation server generates a token, which gets sent to a separate resource server that parses that token (OAuth2 commonly uses JWTs, but does not specify a particular token format).</p>
<p>If I use JWTs, I can be fairly confident that my client would be able to decode the JWT using a reasonably mature library.</p>
<p>Since JWT/JOSE is, for better or worse, an established standard by now, I could probably do a lot worse than making my client work with a well-tested JWT/JOSE library. The alternative is to agree with the client to create a token format using an ad hoc serialisation and signing scheme, which can take more development time (both for my client and for myself), and requires a bit of crypto knowledge.</p>
<p>There are more alternatives, of course, like <a href="https://paseto.io/">PASETO</a> which was created to address the deficiencies of the JOSE standard. I will not go into those alternatives here.</p>
<h2 id="can-you-show-me-some-code-to-create-a-stateless-token-in-django">Can you show me some code to create a stateless token in Django?</h2>
<p>Sure. Use the <a href="https://docs.djangoproject.com/en/dev/topics/signing/">signing module</a>.</p>
<p>Here’s how you do it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">django.core</span> <span class="kn">import</span> <span class="n">signing</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">{</span><span class="s">'user_id'</span><span class="p">:</span> <span class="mi">123456</span><span class="p">}</span>
<span class="c1"># Here's how you generate a token
</span><span class="n">token</span> <span class="o">=</span> <span class="n">signing</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="c1"># Here's how you validate the token
</span><span class="n">decoded_data</span> <span class="o">=</span> <span class="n">signing</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">)</span>
</code></pre></div></div>
<p>If you don’t use Django, find a trusted crypto or HMAC library in your language of choice. In Python you could just use the built-in <a href="https://docs.python.org/3/library/hmac.html">hmac module</a>. Make sure you compare the signature using a constant-time compare algorithm to prevent timing attacks.</p>
<h2 id="summary">Summary</h2>
<p>Statelessness is often touted as an advantage of JWTs, but in reality that feature is not unique to JWTs. Any token that was generated using cryptographic signing can be stateless, including JWTs.</p>
<p>Furthermore, JWTs introduce some extra security burden on the developer, who needs to be extra careful to avoid the pitfalls of JWTs.</p>
<p>If the same party is generating and validating the token, in my opinion it is best to avoid JWTs entirely. A very good option is including a signature of the payload using HMAC + SHA. Django provides this out of the box, and the Python standard library gives you some good tools to use. This gives you stateless tokens without having to use JWTs.</p>
<p>In my personal opinion as a developer (and not a cybersecurity professional), JWTs can be a pragmatic option in some cases where a standard is useful, as this means decent library support which avoids extra development effort. One such case is when the token needs to be generated by one party, and validated by another.</p>JSON Web Tokens (JWTs) are commonly used today in a number of applications, especially as bearer tokens.Cookie-based authentication with SPA and Django2019-12-07T00:00:00+00:002019-12-07T00:00:00+00:00https://yoongkang.com/blog/cookie-based-authentication-spa-django<p><em><strong>Disclaimer</strong>: I do not work in security, and this article does not make any security recommendations. For advice specific to your situation, please consult a security professional.</em></p>
<p>A question I see asked a lot is how to implement authentication between an SPA (e.g. React, Vue, etc.) and a Django API.</p>
<p>The two methods that I frequently see in the wild are either token-based authentication, or cookie-based authentication. The terminology is somewhat confusing as tokens are used for both mechanisms, but “token-based authentication” usually refers to manually inserting a token in a header in an AJAX request, whereas “cookie-based authentication” refers to using cookies to send this automatically.</p>
<p>Sometimes you will see some security-focused articles recommending against using token-based authentication, and advocating cookies instead. Unfortunately, there isn’t a lot of up-to-date information on how to actually do this in Django, so I’m hoping that this article will help.</p>
<p>First, I will explain why many articles recommend using cookies over local storage, and also describe some things that most of those articles don’t tell you. Then I will show you how to do cookie-based auth with Django.</p>
<h2 id="whats-the-problem-with-token-based-auth">What’s the problem with token-based auth?</h2>
<p>When people refer to “token-based auth” they mean attaching a token to a header (frequently the <code class="highlighter-rouge">Authorization</code> header) manually in an AJAX request. Here’s an example using the <code class="highlighter-rouge">axios</code> library:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">token</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">someAuthenticationToken</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">axios</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/some_endpoint/</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="na">Authorization</span><span class="p">:</span> <span class="s2">`Bearer: </span><span class="p">${</span><span class="nx">token</span><span class="p">}</span><span class="s2">`</span> <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>The main objection to this approach is that you will need to store the token somewhere that is accessible using JavaScript. This normally means the browser’s <code class="highlighter-rouge">localStorage</code>.</p>
<p>Since you can get the token using JavaScript, if your site is vulnerable to XSS (cross-site scripting), someone can steal that token.</p>
<p>That’s pretty much the main gist of the argument.</p>
<p>Here is an <a href="https://auth0.com/docs/security/store-tokens">article from Auth0</a> that recommends against storing tokens in <code class="highlighter-rouge">localStorage</code> for the reason I just described.</p>
<p>If you don’t know what XSS is, read the following section, otherwise feel free to skip it.</p>
<h2 id="whats-xss">What’s XSS?</h2>
<p>XSS is an exploit where an attacker manages to get malicious JavaScript to run on your browser.</p>
<p>This can happen in a number of ways, and is quite difficult to detect and very easy to miss.</p>
<p>A simple example is if you allow users to submit some text via a form, and you render that text as HTML to other users. Think of something like a message board that allows you to insert HTML in your posts.</p>
<p>Since <code class="highlighter-rouge"><script></code> tags are valid HTML, an attacker could enter in some JavaScript that grabs your <code class="highlighter-rouge">localStorage</code> and sends it to some remote server. I’ll leave it to you to find some examples online on how this is done.</p>
<h2 id="how-does-using-cookies-help">How does using cookies help?</h2>
<p>The argument normally goes like this:</p>
<p>You can enable a setting called <code class="highlighter-rouge">HttpOnly</code> on session cookies. It’s a confusing name, but <code class="highlighter-rouge">HttpOnly</code> means your browser will only send the cookie automatically only on HTTP requests, and it is not accessible via JavaScript. So in the event that XSS occurs, an attacker would not be able to steal your token directly.</p>
<h2 id="cookies-dont-protect-you-against-xss">Cookies don’t protect you against XSS</h2>
<p>Unfortunately, many articles stop there, and in my opinion readers may get the wrong impression that if you use cookies you’re safe if XSS happens.</p>
<p>You are NOT protected against XSS if you use cookies.</p>
<p>If I were an attacker, and I can run malicious JavaScript on your browser while you’re logged in, then I can pretty much do anything you can do on your browser. Since the cookies will be sent automatically with each request, I don’t even have to know your token to make authenticated requests. This is equivalent to finding someone’s laptop with an open tab where the owner forgot to log out.</p>
<p>In fact, tokens can expire (and they usually do have a short expiry time) or be invalidated, so stealing a token that may be expired by the time I gets my hands on it may not be very useful. It’s more desirable to make your browser do things for me, while you’re still logged in.</p>
<p>For example, I can make an authenticated request on your behalf to change your email address to mine. I can then initiate a password reset, which will send the password reset link to my email address where I can change your password.</p>
<p>Or, if I only cared about your data, I could just do a GET request to the APIs and send the response data off somewhere.</p>
<p>Or, I could run a malicious script to manipulate the DOM and render a form that asks you to re-enter your password, and have that form send the password to me. If you re-use your passwords in other sites, then now all of your accounts online are vulnerable.</p>
<h2 id="does-that-mean-it-doesnt-really-matter-where-i-put-the-token">Does that mean it doesn’t really matter where I put the token?</h2>
<p>That is a decision you will need to make yourself by considering all factors, including risk, as well as other security measures you put in place, e.g. CAPTCHA, OTP code to phone before certain changes, password re-entry, etc.</p>
<p>If you have other protections in place, you may determine that the choice does not make a huge difference.</p>
<p>However, I will not make a security recommendation, because I am not a security professional.</p>
<p>For a more nuanced discussion about this, I can point you to an article about this called <a href="https://portswigger.net/research/web-storage-the-lesser-evil-for-session-tokens">Web Storage: the lesser evil for session tokens</a> from an actual security researcher.</p>
<p>I can say, however – hopefully without much controversy – that if a cookie has the <code class="highlighter-rouge">HttpOnly</code> and <code class="highlighter-rouge">Secure</code> settings turned on, then storing the token in the cookie is probably not more vulnerable than storing it in <code class="highlighter-rouge">localStorage</code>, assuming the appropriate CSRF protections are put in place. Using a cookie gives you additional protection against the token being accessed directly by JavaScript.</p>
<p>For that reason, I would normally prefer using a cookie, with the appropriate CSRF protections.</p>
<p>Read on to find out how to do this in various circumstances.</p>
<h2 id="do-i-need-to-serve-the-spa-using-django-templates-in-order-to-use-cookies">Do I need to serve the SPA using Django templates in order to use cookies?</h2>
<p>No, you do not.</p>
<p>But if that’s your deployment setup, then you don’t have to do anything else. Django handles this out of the box.</p>
<h2 id="how-do-i-use-cookies-if-my-spa-and-django-are-deployed-separately">How do I use cookies if my SPA and Django are deployed separately?</h2>
<p>Your browser doesn’t know or care how your SPA and backend are deployed. It only knows the domain of where it’s making requests to.</p>
<p>It is possible to serve your Django and your SPA from the same domain, and this is the setup I would recommend for most applications. One way is to use nginx or reverse proxy to proxy requests to the right place based on the path of the request.</p>
<p>If you use nginx, you would be doing something like this (just for illustration, you should be using SSL in your own deployment):</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">server</span> <span class="p">{</span>
<span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
<span class="kn">server_domain</span> <span class="s">example.com</span>
<span class="s">location</span> <span class="n">/api/</span> <span class="p">{</span>
<span class="kn">include</span> <span class="s">proxy_params</span>
<span class="s">proxy_pass</span> <span class="s">http://unix:/tmp/gunicorn.sock</span><span class="p">;</span>
<span class="p">}</span>
<span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
<span class="kn">root</span> <span class="n">/path/to/spa</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you use Netlify to serve your SPA, you could also <a href="https://docs.netlify.com/routing/redirects/rewrites-proxies/#proxy-to-another-service">write some redirect/proxy rules</a>.</p>
<p>This might be tricky if you happen to have path conflicts, but you can get over that by namespacing the URLs for the Django API, e.g. by having all requests to paths starting with <code class="highlighter-rouge">/api/</code> go to Django.</p>
<p>You’ll also need to set your CSRF cookie somehow, or your request to login will fail. Since your page is being rendered by your SPA rather than Django, your CSRF cookie (or any other cookies that Django cares about) wouldn’t be set in the beginning. To get around this, you need to make an initial request to an endpoint which will set the cookie.</p>
<p>If you set up the proxy correctly, then you can basically have some backend code that looks like this (for illustration only, this isn’t production-ready code):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth</span> <span class="kn">import</span> <span class="n">authenticate</span><span class="p">,</span> <span class="n">login</span>
<span class="kn">from</span> <span class="nn">django.views.decorators.http</span> <span class="kn">import</span> <span class="n">require_POST</span>
<span class="kn">from</span> <span class="nn">django.views.decorators.csrf</span> <span class="kn">import</span> <span class="n">ensure_csrf_cookie</span>
<span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">JsonResponse</span>
<span class="o">@</span><span class="n">ensure_csrf_cookie</span>
<span class="k">def</span> <span class="nf">set_csrf_token</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="s">"""
This will be `/api/set-csrf-cookie/` on `urls.py`
"""</span>
<span class="k">return</span> <span class="n">JsonResponse</span><span class="p">({</span><span class="s">"details"</span><span class="p">:</span> <span class="s">"CSRF cookie set"</span><span class="p">})</span>
<span class="o">@</span><span class="n">require_POST</span>
<span class="k">def</span> <span class="nf">login_view</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="s">"""
This will be `/api/login/` on `urls.py`
"""</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">body</span><span class="p">)</span>
<span class="n">username</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'username'</span><span class="p">)</span>
<span class="n">password</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">'password'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">username</span> <span class="ow">is</span> <span class="bp">None</span> <span class="ow">or</span> <span class="n">password</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
<span class="k">return</span> <span class="n">JsonResponse</span><span class="p">({</span>
<span class="s">"errors"</span><span class="p">:</span> <span class="p">{</span>
<span class="s">"__all__"</span><span class="p">:</span> <span class="s">"Please enter both username and password"</span>
<span class="p">}</span>
<span class="p">},</span> <span class="n">status</span><span class="o">=</span><span class="mi">400</span><span class="p">)</span>
<span class="n">user</span> <span class="o">=</span> <span class="n">authenticate</span><span class="p">(</span><span class="n">username</span><span class="o">=</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="n">password</span><span class="p">)</span>
<span class="k">if</span> <span class="n">user</span> <span class="ow">is</span> <span class="ow">not</span> <span class="bp">None</span><span class="p">:</span>
<span class="n">login</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">user</span><span class="p">)</span>
<span class="k">return</span> <span class="n">JsonResponse</span><span class="p">({</span><span class="s">"detail"</span><span class="p">:</span> <span class="s">"Success"</span><span class="p">})</span>
<span class="k">return</span> <span class="n">JsonResponse</span><span class="p">(</span>
<span class="p">{</span><span class="s">"detail"</span><span class="p">:</span> <span class="s">"Invalid credentials"</span><span class="p">},</span>
<span class="n">status</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Then from your frontend, you can write some functions like this:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">loginRequest</span> <span class="o">=</span> <span class="k">async</span> <span class="p">(</span><span class="nx">username</span><span class="p">,</span> <span class="nx">password</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">axios</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span>
<span class="dl">'</span><span class="s1">/api/login/</span><span class="dl">'</span><span class="p">,</span>
<span class="p">{</span> <span class="nx">username</span><span class="p">,</span> <span class="nx">password</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">headers</span><span class="p">:</span> <span class="p">{</span> <span class="nx">X</span><span class="o">-</span><span class="na">CSRFToken</span><span class="p">:</span> <span class="nx">getCsrfToken</span><span class="p">(),</span> <span class="nx">Content</span><span class="o">-</span><span class="na">Type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/json</span><span class="dl">"</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// handle error here</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">setCsrf</span> <span class="o">=</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">axios</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/api/set-csrf-cookie/</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You’re pretty much done at this point.</p>
<h2 id="what-if-my-spa-and-django-are-on-different-domains">What if my SPA and Django are on different domains?</h2>
<p>You’ll need to relax quite a few security settings, and it’s quite complicated so I’d strongly advise not doing this.</p>
<p>In this case, I’d lean towards using a token in the request header. It’s far less complicated, and not as easy to screw up.</p>
<p>But if you can’t avoid it… read on.</p>
<p>What’s being described here is a setup I see sometimes (actually pretty often) where the SPA and the API are on different domains.</p>
<p>Often they are subdomains, e.g. <code class="highlighter-rouge">api.example.com</code> for the API and <code class="highlighter-rouge">app.example.com</code> for the SPA. But sometimes they are also on completely different domains.</p>
<p>There are a number of things you need to do to get cross-domain requests to work with cookies.</p>
<h3 id="use-the-correct-cors-settings">Use the correct CORS settings</h3>
<p>Browsers have a security feature called the same-origin policy that blocks cross-domain requests by default. But you are able to relax this security feature by enabling something called CORS (Cross-Origin Resource Sharing).</p>
<p>To enable CORS, you will need to configure your server to return a number of headers that suit your needs.</p>
<p>To enable cross-domain requests at all, the server will need to return this header:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Access-Control-Allow-Origin: https://app.example.com
</code></pre></div></div>
<p>It is extremely vital that you whitelist only the domains you trust, otherwise you leave yourself vulnerable.</p>
<p>For cookies to be sent cross-domain, you will also need to enable this header:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Access-Control-Allow-Credentials: true
</code></pre></div></div>
<p>Many Django applications use a library called <a href="https://github.com/adamchainz/django-cors-headers">django-cors-headers</a>. If that’s what you’re using, please refer to the documentation to enable the two headers.</p>
<p>You could also have nginx return those headers, which is probably a little better.</p>
<h3 id="turn-off-the-samesite-setting">Turn off the <code class="highlighter-rouge">SameSite</code> setting</h3>
<p>If you use cookies, you will need to care about something called CSRF (cross-site request forgery). Most likely you already have had experience with this by attaching <code class="highlighter-rouge">{% csrf_token %}</code> to your forms, if you use Django.</p>
<p>A CSRF attack is when someone manages to get you to make a POST request from a different origin, e.g. by making you fill in a form in a different domain that targets your app’s domain, or an AJAX request. Since cookies are sent automatically, this means you will end up making an authenticated request, which you didn’t intend.</p>
<p>Kind of like what you’re trying to do with this deployment setup, except maliciously.</p>
<p>A very recent addition to cookies is a setting called <code class="highlighter-rouge">SameSite</code>, with the purpose of preventing some CSRF attacks. As its name implies, it’s a cookie that won’t be sent in cross-domain requests.</p>
<p>Starting from Django 2.1, session cookies and CSRF cookies have this setting turned on by default.</p>
<p>Prior to version 2.1, Django relied on a CSRF token to protect against CSRF attacks. The way this works is that in POST requests, the browser needs to send a CSRF token through either one of two methods – either together with a form submission (that’s why you have to put <code class="highlighter-rouge">{% csrf_token %}</code> in your forms), or in a header (<code class="highlighter-rouge">X-CSRFToken</code> by default) for Ajax requests (you grab the token from a non-HttpOnly cookie).</p>
<p>Actually, Django still does this as some old browsers may not support <code class="highlighter-rouge">SameSite</code> cookies yet.</p>
<p>In the case where the SPA and the Django API are on different domains, you cannot have the <code class="highlighter-rouge">SameSite</code> setting enabled for your session cookies and CSRF cookies. So you’ll need to add these two settings to your <code class="highlighter-rouge">settings.py</code> file:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SESSION_COOKIE_SAMESITE</span> <span class="o">=</span> <span class="bp">None</span>
<span class="n">CSRF_COOKIE_SAMESITE</span> <span class="o">=</span> <span class="bp">None</span>
</code></pre></div></div>
<p><strong>EDIT (Aug 2020)</strong>: Starting Django 3.1 you’ll need to use the string ‘None’, see: <a href="https://docs.djangoproject.com/en/3.1/ref/settings/#csrf-cookie-samesite">https://docs.djangoproject.com/en/3.1/ref/settings/#csrf-cookie-samesite</a></p>
<p>You’ll also need to explicitly tell Django to trust CSRF tokens sent from your SPA’s domain using the <code class="highlighter-rouge">CSRF_TRUSTED_ORIGINS</code> setting:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CSRF_TRUSTED_ORIGINS</span> <span class="o">=</span> <span class="p">[</span><span class="s">"app.example.com"</span><span class="p">]</span>
</code></pre></div></div>
<p>It’s always important that you validate CSRF tokens when using cookies, and if you use these configurations it is even more crucial, as you can no longer rely on the <code class="highlighter-rouge">SameSite</code> behaviour of cookies.</p>
<p>If you use Django REST Framework, <code class="highlighter-rouge">APIView</code> and <code class="highlighter-rouge">ViewSet</code> will use the <code class="highlighter-rouge">csrf_exempt</code> decorator, meaning CSRF protections are being bypassed by default (because you might not be using cookies).</p>
<p>You will need to configure your viewsets to use the <code class="highlighter-rouge">SessionAuthentication</code> backend, which will enable CSRF protections.</p>
<h3 id="use-withcredentials-when-making-ajax-requests">Use <code class="highlighter-rouge">withCredentials</code> when making AJAX requests</h3>
<p>By default, cookies are not sent (or set) for cross-domain requests (regardless of CORS settings).</p>
<p>You’ll need to explicitly tell your request to send cookies via the <code class="highlighter-rouge">withCredentials</code> property, e.g:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">axios</span>
<span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://api.example.com/some_resource/</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span> <span class="na">withCredentials</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="p">.</span><span class="nx">then</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">)</span>
<span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">);</span>
</code></pre></div></div>
<h2 id="summary">Summary</h2>
<p>In this article, I discussed why cookies, specifically HttpOnly cookies, are often recommended for session tokens over saving tokens in local storage.</p>
<p>I explained that using cookies doesn’t mean that your application is protected against XSS. However, it does mean that someone can’t steal your session token directly.</p>
<p>I then discussed three deployment options, and how cookies work in each one:</p>
<ul>
<li>SPA served via Django templates, where no action is needed</li>
<li>SPA and Django API on the same domain, which requires a small amount of code in the frontend to set a CSRF cookie as this is no longer done automatically.</li>
<li>SPA and Django API on different domains, which requires relaxing a number of security settings (CORS, SameSite).</li>
</ul>
<p>I will hopefully update this post with an actual example repo once I have the time to do it.</p>
<p>If you have any questions or corrections about this post, please feel free to send me an email. I’d love to hear about what you’re doing.</p>Disclaimer: I do not work in security, and this article does not make any security recommendations. For advice specific to your situation, please consult a security professional.Just completed Udacity’s Data Engineering Nanodegree!2019-10-16T00:00:00+00:002019-10-16T00:00:00+00:00https://yoongkang.com/blog/udacity-data-engineering-nanodegree<p>So, this happened recently:</p>
<p><img src="/images/udacity-data-engineering-nanodegree/dend-cert.svg" alt="My certificate" /></p>
<p>Having previously completed the <a href="/blog/my-experience-with-udacity-mlnd/">Machine Learning Engineer Nanodegree back in 2018</a>, I’ve now completed 2 nanodegrees from Udacity.</p>
<p>Thanks again to my employer <a href="https://airteam.com.au">Airteam</a> for sponsoring this, and continually investing in my education and professional development.</p>
<p>I want to talk a little bit about my experiences with the course, and what I’ve learned.</p>
<h2 id="course-structure">Course structure</h2>
<p>First, let’s talk a bit about how the course is structured.</p>
<p>I was part of the first cohort to sign up for this course, which at the time was still a term-based structure. That means, there was a fixed term of about 5 months, and if you don’t complete all the projects, you basically don’t get the certificate (which is probably useless to me anyway).</p>
<p>Udacity has since converted all Nanodegrees to subscription-based courses, where you pay a certain amount of money each month, and can take as long as you’d like.</p>
<p>There are 5 modules, and in each one of them you are required to submit between 1-2 guided projects. Your submission will be reviewed by a contractor hired by Udacity.</p>
<p>The projects are mostly done in the browser, and could be a Jupyter workspace, or a customised workspace on what I believe is a Docker container.</p>
<p>The 5 modules are:</p>
<p><strong>1. Data Modeling</strong></p>
<p>This module is an introduction to databases, and covers Postgres and Apache Cassandra. Basically one relational database, and one NoSQL database. There was a basic project for each database, so two projects in total.</p>
<p><strong>2. Cloud Data Warehouses</strong></p>
<p>An introduction to data warehousing, specifically on AWS. It talked about why transactional and analytical databases require different designs. The specific technology used for the data warehouse is Amazon Redshift. In the project, we did ETL from a data source to create a star schema database in Redshift.</p>
<p><strong>3. Spark and Data Lakes</strong></p>
<p>As the name probably tells you, it’s an introduction to Apache Spark, and also how to build a Data Lake using Spark. The project uses the same schema as the previous module.</p>
<p><strong>4. Data Pipelines with Airflow</strong></p>
<p>Basically, an introduction to Apache Airflow, and how you can use it to create data pipelines. You basically rebuild the cloud data warehouse project using Airflow, with some minor changes to leverage Airflow.</p>
<p><strong>5. Capstone Project</strong></p>
<p>In this module, you do an open-ended project using either Udacity-provided data sources, or you could find your own datasets. You complete a writeup, and submit it.</p>
<h2 id="student-and-peer-experience">Student and peer experience</h2>
<p>For my cohort, we were invited into a shared Slack workspace with other students and our assigned mentors. Each mentor is assigned to a small group of students.</p>
<p>Unfortunately, they’ve since moved newer students to Student Hub, which is essentially a worse version of Slack.</p>
<h2 id="quality-of-video-lectures">Quality of video lectures</h2>
<p>Just for context, I’d previously done the machine learning nanodegree as mentioned, and in that nanodegree I experienced some of the best and clearest instruction I’ve ever received in my life through the video lectures.</p>
<p>Unfortunately, the Data Engineering Nanodegree’s lectures were a bit of a letdown this time, and in my opinion not of their usual quality (at least not of the quality of the machine learning nanodegree). The lectures were not very polished, had very little post-editing, and not rehearsed.</p>
<p>The course instructors who appeared in the videos appear to not be Udacity employees, but rather external “subject matter experts” contracted to record videos on the course content.</p>
<p>That in itself is not usually a problem, and Udacity has done that successfully in the past.</p>
<p>The problem is that the videos didn’t appear to have gone through much QA after being recorded, and there appeared to be a lack of preparation that went in to record these videos.</p>
<p>A specific example is numerous instances in the videos where a course instructor stumbles on sentences and repeats the sentence again, probably expecting the video to be edited appropriately.</p>
<p>The worst parts were the data warehousing and data lakes section, where the instructor appeared to be thinking of what to say on the fly. There were numerous pauses, long “uhhhs” and “ummms” which got REALLY annoying after a while because once you notice it you can’t unhear it. I had to put the videos on 2x speed, and even then it was barely tolerable.</p>
<p>If I might offer a suggestion to improve the video lectures: It would be MUCH better if course instructors were to prepare a script, and to read from that script in the videos. This isn’t theatre or acting class – it’s fine to read from a script, and is far better than just winging it during recording.</p>
<p>It wasn’t uniformly bad though, the Airflow and Spark sections actually were of their usual polished quality in my opinion. It’s likely that those lectures were either rehearsed or read from a prepared script, judging by the lack of “uhhs” and “umms”, and any stumbling. Any mistakes were probably edited out.</p>
<h2 id="quality-of-course-content">Quality of course content</h2>
<p>The course has a substantial practical bent, rather than focusing on theory and motivation. The course is an excellent choice if you want to learn “how” to do things like ETL and Data Warehousing, on cloud providers like AWS.</p>
<p>I felt it was probably slightly thin on the “why” side of some things. So if you don’t have any experience at all working with databases, it’s possible you might get a little bit confused during the course.</p>
<p>Specifically, I wished there was more treatment on what the consumers of data engineering (like BI analysts or data scientists) expect and how they use the solutions we build.</p>
<p>I personally think that was fine, and you will probably get the most value out of this course if you do have a background working with databases.</p>
<p>For a deeper treatment of this, it’s probably better to read from a book anyway. I think one of the instructors <a href="https://www.kimballgroup.com/data-warehouse-business-intelligence-resources/books/data-warehouse-dw-toolkit/">recommended one from Kimball and Ross</a>, which I’m planning to get.</p>
<h2 id="quality-of-the-projects">Quality of the projects</h2>
<p>All of the projects (except the capstone) were based on the same problem domain (a song streaming startup), with the same data, using the same schema.</p>
<p>So it’s just a matter of doing the same thing, using different tools. The difficulty of that is highly dependent on how good you are at learning a new API.</p>
<p>As an experienced developer, once I understood what was expected in the project, I could finish the project in under 2 hours.</p>
<p>But I think most people on the course Slack agreed that the project instructions were quite confusing. Often, I was confused myself at some of the instructions.</p>
<p>The project reviews were sometimes helpful, but often it was pretty pedantic, and your submission may get rejected for things like forgetting to delete a code comment in the Jupyter notebook cells.</p>
<p>For context, there were comments in the provided workspaces that were meant to mark sections to show where you’re meant to put in code, e.g. <code class="highlighter-rouge"># TODO: complete section here</code>. Deleting the comments did <strong>absolutely nothing</strong> to help me understand data engineering, but the submission will get rejected if you don’t do it. I felt like it was a bit of a waste of time.</p>
<p>Some projects can also take a long time to run due to the amount of data, so if you’re doing it in the Udacity-provided workspace, it can go to sleep before it’s complete. My suggestion to avoid that is, don’t use the whole dataset. Work on a smaller dataset, verify it works, then replace the code with the full dataset BUT DON’T RUN IT. Just submit it as it is.</p>
<h2 id="mentoring">Mentoring</h2>
<p>This is one aspect that I can’t comment on, because I didn’t make use of it. I didn’t actually want any mentoring, but mentoring was pushed onto me. Actually the mentors were hired from a pool of people who have completed other nanodegrees in the past. Which means I received an invitation to apply too, but I didn’t have the time for that.</p>
<p>So there’s a chance you get someone who has done a nanodegree, but isn’t really experienced as a data engineer.</p>
<p>The specific mentor I was assigned to lives in a different timezone and the time difference was quite unfortunate.</p>
<p>At one point I fell behind in the course due to illness. So, what happened is that every Sunday night, between 1am and 3am I would get pinged by my mentor asking “how’s progress” (despite my local time being on my Slack profile), and some basic advice on how to use AWS.</p>
<p>I was stupid enough to forget to turn off notifications on Slack, so that meant a few sleepy Mondays until I finally asked my mentor to stop, and that while I appreciate the advice about AWS, I’m probably going to be fine because I happen to use AWS for a living.</p>
<p>However, many people on Slack reported that they benefited from their mentor, including the mentor I was assigned to, so your mileage may vary.</p>
<h2 id="verdict">Verdict</h2>
<p>There were a few problems with the course, and reviews online (especially on Reddit) haven’t been very kind. It’s true that the course felt like it was done in a rush, and it does show. There were also some issues like the content not being complete at launch time, and course content was added later (which didn’t affect me because I was slow anyway).</p>
<p>But I did learn a few things, and that’s what matters in the end.</p>
<p>Would I recommend the course? When I enrolled, the price of the course was $1300. That’s less than half the current price.</p>
<p>At the current price point of $2669 for 5 months access (and a monthly fee after), I personally wouldn’t recommend it, unless you happen to have a lot of money. It’s extremely expensive for what you get.</p>
<p>You’re definitely not going to get a job in data engineering with only this nanodegree, but it’s marketed like it’s a pathway to a data engineering career. Sorry, but it’s not. But it’s a good complement if you already have relevant work experience, probably with cloud platforms like AWS and some database experience.</p>
<p>Although I learned a lot from their courses, I doubt I will do another nanodegree, unless I suddenly start earning a lot more money (in which case I’ll attempt the self driving car one).</p>So, this happened recently:txtimg – A library for text-based images2019-09-19T00:00:00+00:002019-09-19T00:00:00+00:00https://yoongkang.com/blog/txtimg-for-text-based-images<p>Recently, I’ve been doing some translation work for a certain community on the internet. As far as I’m aware of, the community mostly lives on Twitter, and a few other niche forums.</p>
<p>Anyway, sometimes the translations can get pretty long, e.g. a radio or a TV broadcast.</p>
<p>That means, a tweetstorm would be really hard to read.</p>
<p>The solution people usually come up with is to write the translations in a word processor, and take screenshots of the file. So the result is something we call a “text-based image”.</p>
<p>I don’t really use a word processor much, and I hate taking screenshots like that. My tool of choice is a text editor like VSCode, but that looks really terrible when you take a screenshot.</p>
<p>Also, I’ve been toying with the idea of distributing my translations via a web service. Some translations I do aren’t of publicly available content, so the idea is to deliver the translations to only people who have paid for this exclusive content. With a web service, I could also invite other translators to the provide translations.</p>
<p>So I wrote some Python code! I released a library called <code class="highlighter-rouge">txtimg</code>!</p>
<p>You can install it like this:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ pip install txtimg
</code></pre></div></div>
<p>And here’s how you use it:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">txtimg</span> <span class="kn">import</span> <span class="n">TxtImg</span>
<span class="n">text</span> <span class="o">=</span> <span class="s">"""
What did Sushi A say to Sushi B?
What's up B? (WASABI)
"""</span>
<span class="n">t</span> <span class="o">=</span> <span class="n">TxtImg</span><span class="p">()</span>
<span class="n">img</span> <span class="o">=</span> <span class="n">t</span><span class="o">.</span><span class="n">generate_from_text</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
<span class="n">img</span><span class="o">.</span><span class="n">save</span><span class="p">(</span><span class="s">"wasabi.png"</span><span class="p">)</span>
</code></pre></div></div>
<p>This is basically a thin wrapper on top of the Pillow library. Basically you can pass in a string, and it will return a Pillow image object. There are some configuration parameters that I probably should have put more thought into, but it works.</p>
<p>In hindsight, I could have also just used <code class="highlighter-rouge">pandoc</code> but that might be complicated to install on a server. Or it was probably just LaTeX that was difficult to install, I can’t remember.</p>
<p>I’m planning to add more features to this depending on how it goes with my translating thing.</p>
<p>If you want to see my code repo, you can find it here: <a href="https://github.com/yoongkang/txtimg">https://github.com/yoongkang/txtimg</a></p>Recently, I’ve been doing some translation work for a certain community on the internet. As far as I’m aware of, the community mostly lives on Twitter, and a few other niche forums.SyDjango August 20192019-08-22T00:00:00+00:002019-08-22T00:00:00+00:00https://yoongkang.com/blog/sydjango-aug-2019<p>After a lengthy hiatus, <a href="https://www.meetup.com/sydjango/">SyDjango</a> came back on August 2019!</p>
<p><img src="/images/sydjango_august_2019/sydjango.jpg" alt="SyDjango talk" /></p>
<p>The event was co-sponsored and co-organised by <a href="https://www.airteam.com.au">Airteam</a> and <a href="https://www.covergenius.com">Cover Genius</a>. Artem and the rest of the fine people at Cover Genius have very kindly taken care of a lot of the logistics for this meetup. Many thanks to Artem and everyone at Cover Genius for their help.</p>
<p>It was a great night of fantastic talks! Many thanks to our speakers, Iqbal Bhatti, Amit Saha, and Sam Scheding for giving really great talks.</p>
<p>I apologise for having to leave early, as I had a flight to Melbourne very early in the morning the next day.</p>
<p>We have already booked the venue for our October and November meetups. We are of course looking for speakers again! Please let me or Artem know if you are interested in giving a talk. To get in contact with us, we would encourage you to join our Slack channel. You can join using this form here: <a href="https://sydjango.herokuapp.com">https://sydjango.herokuapp.com</a>. Please excuse the crappy design, I should really change that. Alternatively you could just email me.</p>
<p>We now also have an official Twitter account, follow us there to get any general updates: <a href="https://twitter.com/sydjango">https://twitter.com/sydjango</a></p>
<p>Of course, please also reach out if you have any questions about the meetup.</p>After a lengthy hiatus, SyDjango came back on August 2019!Tutorials suck2019-08-16T00:00:00+00:002019-08-16T00:00:00+00:00https://yoongkang.com/blog/tutorials-suck<p>If you do any programming at all, you’ve done some tutorials.</p>
<p>They suck.</p>
<p>Maybe that’s a little harsh. More accurately, I mean that the value you get from tutorials is almost always somewhat limited unless it solves the exact situation you’re in. I’ve never done a tutorial that significantly increased my understanding.</p>
<p>I think in theory it’s possible to write a tutorial that doesn’t suck, but all the ones I’ve used disappointed me in one way or another.</p>
<p>A tutorial is like a cooking recipe, and unless you’re trying to cook the exact thing in the recipe, it’s not very helpful for much else. The recipe could tell you the steps, but often doesn’t succeed in telling you why the steps are there, how they can be applied to other dishes. If you’re lucky they tell you which steps are optional, or ingredients that can be substituted with something else.</p>
<p>It’s much the same with tutorials. Just a series of steps, which you follow mechanically but don’t understand. I don’t know if it’s the format of tutorials that makes it hard to explain things in detail, or if we as a profession have not figured out the best way to write a tutorial.</p>
<p>Let me give an example. Recently, I’ve had to write some C# on .NET for some reason. I don’t usually work with this technology, and this was a one-off project. I really did not want to install .NET on my Mac.</p>
<p>So I thought I’d use Docker. Go ahead and google “Docker C# .NET”, and one of the top results is <a href="https://docs.microsoft.com/en-gb/dotnet/core/docker/build-container">this tutorial</a> from Microsoft.</p>
<p>Now, I think this is a useful tutorial, and looks very well written. I don’t want to single out this tutorial as one that sucks. And I certainly don’t want to bash the authors.</p>
<p>It’s just that it didn’t tell me what I needed.</p>
<p>The tutorial starts off by… telling me to install .NET on my Mac, and then running a command to create the project. This was the exact thing I was trying to avoid! Why do I need to install .NET on my host computer when I could use a perfectly good .NET Docker container, whose image the tutorial says I’ll have to pull anyway?</p>
<p>I tried looking for other tutorials, and they all started with the same steps. No results for “generate .NET project using Docker”. Urgh.</p>
<p>Luckily I knew a little bit about Docker and knew that I could run commands on a container using e.g. <code class="highlighter-rouge">docker-compose run service dotnet new console</code>. I also knew about volumes, and if I used them, any files I generated from those commands would also be in my host machine. That gave me what I needed.</p>
<p>Sometimes, as in this case, you won’t find a single tutorial that tells you what you need. Instead, you figure this out by having some random facts floating around in your head and somehow connecting the dots.</p>
<p>That’s why I don’t envy anyone starting out programming. It’s completely insane how much know-how that is necessary to do your job is either undocumented, or documented poorly. Often what you really need is buried deep in some obscure part of the docs, and often scattered too.</p>
<p>Which is a shame, because often our first instinct would be to reach out to tutorials.</p>
<p>We either need to write better tutorials, or find some better format.</p>If you do any programming at all, you’ve done some tutorials.My fabric deployment script (fabric2)2019-04-18T00:00:00+00:002019-04-18T00:00:00+00:00https://yoongkang.com/blog/fabric-deployment<p>Since 2018, there’s been a new version of <a href="http://www.fabfile.org"><code class="highlighter-rouge">fabric</code>, also known as <code class="highlighter-rouge">fabric2</code></a>.</p>
<p>It comes with an updated API, and is incompatible with the old <code class="highlighter-rouge">fabric 1.x</code> <code class="highlighter-rouge">fabfiles</code>.</p>
<p>It’s also split up some functionality into a few different libraries, including <code class="highlighter-rouge">invoke</code> and <code class="highlighter-rouge">patchwork</code>.</p>
<p>There was a rant on Reddit about the changes that the new version introduced.</p>
<p>I chimed in with some of my own complaints, which were mainly due to what I believed was inadequate documentation.</p>
<p>After that I regretted it. It wasn’t very nice to the developers who obviously put in a lot of work to release the new version, and obviously it wasn’t very productive either.</p>
<p>So instead of just complaining, I decided to make a blog post that would actually help the community and encourage more people to use it.</p>
<h2 id="my-confusion-with-the-documentation">My confusion with the documentation</h2>
<p>As mentioned, some functionality was split out into several other libraries. That means you’ll need to look at the documentation for each library separately.</p>
<p>For example, anything to do with CLI and task running that isn’t “strictly SSH” is now in a library called <a href="http://pyinvoke.org">Invoke</a>.</p>
<p>So if you’re used to the old primary API of <code class="highlighter-rouge">fabric</code> which was to define a task, then run it in the command line using <code class="highlighter-rouge">fab <task-name></code>, then you probably want to be looking at the documentation for invoke. Fabric has a thin wrapper on top of invoke for tasks, and Fabric’s documentation simply refers you to Invoke.</p>
<p>Unfortunately, there were a few things that took me way too long to figure out.</p>
<p>Chief among them is how fabric and invoke work together. For example, a lot of <code class="highlighter-rouge">fabric</code>’s documentation deals with using <code class="highlighter-rouge">Connection</code> objects:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">fabric.connection</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="n">connection</span> <span class="o">=</span> <span class="n">Connection</span><span class="p">(</span><span class="s">"username@remote-ip"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">connection</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s">"ls"</span><span class="p">))</span>
</code></pre></div></div>
<p>If you’re used to some of the old <code class="highlighter-rouge">fabric</code> methods like <code class="highlighter-rouge">cd()</code>, well, you can’t find them in the <code class="highlighter-rouge">Connection</code> object. What you get is <code class="highlighter-rouge">run()</code> and not much else.</p>
<p>But when I look at <code class="highlighter-rouge">invoke</code>’s documentation, they clearly have methods like <code class="highlighter-rouge">cd()</code> on an object passed in as the first argument in a task. This is <a href="http://docs.pyinvoke.org/en/1.2/api/context.html">called a <code class="highlighter-rouge">Context</code> object</a>.</p>
<p>For example here’s an invoke task:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">invoke</span> <span class="kn">import</span> <span class="n">task</span>
<span class="o">@</span><span class="n">task</span>
<span class="k">def</span> <span class="nf">some_task</span><span class="p">(</span><span class="n">c</span><span class="p">):</span>
<span class="k">with</span> <span class="n">c</span><span class="o">.</span><span class="n">cd</span><span class="p">(</span><span class="s">"some-directory"</span><span class="p">):</span>
<span class="n">c</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s">"ls"</span><span class="p">)</span>
</code></pre></div></div>
<p>Well, that’s what I need. But that’s a <code class="highlighter-rouge">Context</code> object, not a fabric <code class="highlighter-rouge">Connection</code> object.</p>
<p>What’s a <code class="highlighter-rouge">Context</code> object?</p>
<p>Looking at the <a href="http://docs.pyinvoke.org/en/1.2/getting-started.html#">“Getting Started”</a> documentation, it first says (emphasis mine):</p>
<blockquote>
<h3 id="defining-and-running-task-functions">Defining and running task functions</h3>
<p>The core use case for Invoke is setting up a collection of task functions and executing them. This is pretty easy – all you need is to make a file called tasks.py importing the task decorator and decorating one or more functions. You will also need to add an arbitrarily-named context argument (convention is to use c, ctx or context) as the first positional arg. <strong>Don’t worry about using this context parameter yet.</strong></p>
</blockquote>
<p>Okay. Eventually they’ll explain what it is, right?</p>
<p>Sure enough, if you scroll down you see this bit:</p>
<blockquote>
<h3 id="aside-what-exactly-is-this-context-arg-anyway">Aside: what exactly is this ‘context’ arg anyway?</h3>
<p>A common problem task runners face is transmission of “global” data - values loaded from configuration files or other configuration vectors, given via CLI flags, generated in ‘setup’ tasks, etc.</p>
<p>Some libraries (such as Fabric 1.x) implement this via module-level attributes, which makes testing difficult and error prone, limits concurrency, and increases implementation complexity.</p>
<p>Invoke encapsulates state in explicit Context objects, handed to tasks when they execute . The context is the primary API endpoint, offering methods which honor the current state (such as Context.run) as well as access to that state itself.</p>
</blockquote>
<p>Maybe I’m unfamiliar with task runners in general, but I don’t really understand what these paragraphs mean.</p>
<h2 id="lets-try-some-stuff-out-and-see-if-it-works">Let’s try some stuff out and see if it works</h2>
<p>So I decided to try and play around with the <code class="highlighter-rouge">Context</code>. The convention is to use <code class="highlighter-rouge">c</code> as its name, and a fabric <code class="highlighter-rouge">Connection</code> also starts with a <code class="highlighter-rouge">c</code>.</p>
<p>Could I just pass the <code class="highlighter-rouge">Connection</code> object into a task….?</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">fabric</span> <span class="kn">import</span> <span class="n">Connection</span>
<span class="kn">from</span> <span class="nn">fabric.tasks</span> <span class="kn">import</span> <span class="n">task</span>
<span class="o">@</span><span class="n">task</span>
<span class="k">def</span> <span class="nf">sub_task</span><span class="p">(</span><span class="n">c</span><span class="p">):</span>
<span class="k">with</span> <span class="n">c</span><span class="o">.</span><span class="n">cd</span><span class="p">(</span><span class="s">"some-folder"</span><span class="p">):</span>
<span class="n">c</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="s">"ls"</span><span class="p">)</span>
<span class="o">@</span><span class="n">task</span>
<span class="k">def</span> <span class="nf">main_task</span><span class="p">(</span><span class="n">c</span><span class="p">):</span>
<span class="n">con</span> <span class="o">=</span> <span class="n">Connection</span><span class="p">(</span><span class="s">"username@some-host"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">sub_task</span><span class="p">(</span><span class="n">con</span><span class="p">))</span>
</code></pre></div></div>
<p>I’ll be damned, that actually worked!</p>
<p>That seems like a pretty important detail about using <code class="highlighter-rouge">fabric</code>, and fundamental in using <code class="highlighter-rouge">fabric</code> and <code class="highlighter-rouge">invoke</code> together – but it was really strange that it wasn’t documented anywhere. I had to discover it more or less by accident!</p>
<p>So if you’re curious where the <code class="highlighter-rouge">cd()</code> method went, the way to do it is to pass your <code class="highlighter-rouge">Connection</code> object into a task.</p>
<p>Once that was cleared up, the API wasn’t actually that difficult to work with. In fact, it was pretty great! Some things that changed in the new release actually makes a lot of sense.</p>
<h2 id="what-use-cases-am-i-interested-in">What use cases am I interested in?</h2>
<p>Ultimately, all I want to do is to use it as a way to automate deploying Django apps. Instead of manually SSH-ing and doing a <code class="highlighter-rouge">git pull</code>, or doing an <code class="highlighter-rouge">rsync</code> to copy files to the application server.</p>
<p>I think it would be helpful to the community if we had some cookbooks or example <code class="highlighter-rouge">fabfiles</code> that we could use and modify quickly.</p>
<p>This seems to be missing both in the official docs and the community, so I decided to publish my own one.</p>
<h2 id="my-deployment-file">My deployment file</h2>
<p>You can find it here in my GitHub repository:</p>
<p><a href="https://github.com/yoongkang/fabric-deployment">https://github.com/yoongkang/fabric-deployment</a></p>
<p>This is a script that works for me personally, and hopefully it helps other people as well.</p>Since 2018, there’s been a new version of fabric, also known as fabric2.