<?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: Danny Perez</title>
    <description>The latest articles on DEV Community by Danny Perez (@intricatecloud).</description>
    <link>https://dev.to/intricatecloud</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%2F70101%2Fe176653f-02e3-41e1-bfc2-a1b0a2195459.png</url>
      <title>DEV Community: Danny Perez</title>
      <link>https://dev.to/intricatecloud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/intricatecloud"/>
    <language>en</language>
    <item>
      <title>Passwordless sign-in with Google One Tap for Web</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Mon, 26 Jul 2021 02:52:31 +0000</pubDate>
      <link>https://dev.to/intricatecloud/passwordless-sign-in-with-google-one-tap-for-web-4l51</link>
      <guid>https://dev.to/intricatecloud/passwordless-sign-in-with-google-one-tap-for-web-4l51</guid>
      <description>&lt;p&gt;I sign in with my Google account everywhere I can to avoid having yet-another-password on another random website. I've been seeing an upgraded experience on some sites (maybe I'm just noticing now) like Trello/Medium where you can sign-in with Google with one click on the page without getting redirected. Turns out its called One Tap for Web and its Google's passwordless sign-in option and you can use it on your own website. I took it for a spin and set it up on a hello world React example and here's what I found, warts and all.&lt;/p&gt;

&lt;p&gt;If you'd like to watch a video version of this tutorial, you can check it out on my YouTube channel here. &lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/qS4dY7syQwA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a new react app and add a sign-in state
&lt;/h2&gt;

&lt;p&gt;Start with a bare react app... &lt;code&gt;npx create-react-app one-tap-demo&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For this example app, I'll be using the JS library to initialize the one-tap prompt. The reference guide shows you how to add it &lt;a href="https://developers.google.com/identity/one-tap/web/guides/load-one-tap-client-library"&gt;using mostly HTML&lt;/a&gt;, but if you're using a front-end framework, its easier to use just JS to configure it.&lt;/p&gt;

&lt;p&gt;Add some state to keep track of when the user has signed in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;function App() {
&lt;/span&gt;&lt;span class="gi"&gt;+   const [isSignedIn, setIsSignedIn] = useState(false)
&lt;/span&gt;    return (
        &amp;lt;div className="App"&amp;gt;
            &amp;lt;header className="App-header"&amp;gt;
                &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
&lt;span class="gi"&gt;+                { isSignedIn ? &amp;lt;div&amp;gt;You are signed in&amp;lt;/div&amp;gt; : &amp;lt;div&amp;gt;You are not signed in&amp;lt;/div&amp;gt;}
&lt;/span&gt;            &amp;lt;/header&amp;gt;
        &amp;lt;/div&amp;gt;
    )
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Add the GSI
&lt;/h2&gt;

&lt;p&gt;Add the Google Sign-In library (also called GSI) dynamically when your application starts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;function App() {
&lt;/span&gt;    const [isSignedIn, setIsSignedIn] = useState(false)

+     const initializeGsi = () =&amp;gt; {
&lt;span class="gi"&gt;+        google.accounts.id.initialize({
+            client_id: 'insert-your-client-id-here',
+        });
+        google.accounts.id.prompt(notification =&amp;gt; {
+            console.log(notification)
+        });
+ }
+
+    useEffect(() =&amp;gt; {
+        const script = document.createElement('script')
+        script.src = 'https://accounts.google.com/gsi/client'
+        script.onload = initializeGSI()
+        script.async = true;
+        document.querySelector('body').appendChild(script)
+    }, [])
&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's 2 API calls - one to configure the library, and one to show the user the prompt (after the library has been configured). To see all possible configuration options, &lt;a href="https://developers.google.com/identity/one-tap/web/reference/js-reference#IdConfiguration"&gt;check the reference here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I added it as a &lt;code&gt;useEffect&lt;/code&gt; hook with &lt;code&gt;[]&lt;/code&gt; as an arg, so that it only runs once after the first render. &lt;a href="https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect"&gt;ref&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you refresh the page, if you got it all right the first time, you'll see the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get a Client ID from your Google Developer Console
&lt;/h3&gt;

&lt;p&gt;Follow this guide for adding your &lt;a href="https://developers.google.com/identity/one-tap/web/guides/get-google-api-clientid"&gt;Get your Google API client ID&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When I refreshed the page after following the steps and adding my client id to the app, I got this error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[GSI] Origin is not an authorized javascript origin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For me, it meant that my Google OAuth Client ID is misconfigured. I missed the "Key Point" on the official walkthrough - which only applies if we're using localhost as our domain.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm that your site URL (i.e. &lt;code&gt;http://localhost:3000&lt;/code&gt;) is added as both an authorized Javascript origin and as a valid redirect URI, in the Google OAuth Client Console.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IMPORTANT&lt;/strong&gt; You ALSO need to add &lt;code&gt;http://localhost&lt;/code&gt; as an authorized Javascript origin. This only seems necessary in development when you might be using a different port in your URL (we are).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Using the sign-in button
&lt;/h2&gt;

&lt;p&gt;Now when you refresh the page, it should be working, and you should see the prompt. If you don't see the prompt, check your developer console for errors, and if that doesn't help - see the section on Debugging below.&lt;/p&gt;

&lt;p&gt;IF THIS IS YOUR FIRST TIME DOING THIS, DO &lt;strong&gt;NOT&lt;/strong&gt; CLICK THE X BUTTON ON THE PROMPT BEFORE READING THIS WARNING!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING 1:&lt;/strong&gt; Clicking the X button on the one-tap prompt dismisses the prompt. If you refresh the page after this, you won't see the button come back. Why?&lt;/p&gt;

&lt;p&gt;The One Tap library has some additional side effects around dismissing the prompt. If you've clicked the X button, a cookie has been added to your domain called &lt;code&gt;g_state&lt;/code&gt;. Here's a screenshot of where you can find it - if you clear that cookie value, you'll see the prompt come back.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WARNING 2:&lt;/strong&gt; Clicking the X button &lt;strong&gt;more than once&lt;/strong&gt; will enter you into an Exponential Cool Down mode - &lt;a href="https://developers.google.com/identity/one-tap/web/guides/features#exponential_cool_down"&gt;see the reference here&lt;/a&gt;. What does that mean?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot clear cookies or use an incognito window to get around it (at least I couldn't). It seems to be based on your browser and website (maybe IP?), its not clear. If you accidentally run into this, time to take a break. Or try a different browser/website URL.&lt;/li&gt;
&lt;li&gt;After I dismissed it, I couldn't see it for 10-15 minutes, although the table on the developer guide suggests I wouldn't see it for 2 hours. In any case, its an annoying thing to have to run into during development.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Debugging issues with One Tap sign-in
&lt;/h2&gt;

&lt;p&gt;The developer guide suggests this as example code for your prompt. But it flys over an important detail that the reason WHY your prompt did not display, or got skipped, or got dismissed is also in the &lt;code&gt;notification&lt;/code&gt; object.&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="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accounts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isNotDisplayed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;notification&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSkippedMoment&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="c1"&gt;// continue with another identity provider.&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;p&gt;There are 3 types of notification moments - &lt;code&gt;display&lt;/code&gt;, &lt;code&gt;skipped&lt;/code&gt;, &lt;code&gt;dismissed&lt;/code&gt; and each one has their own list of possible reasons, with its own API call to figure it out - &lt;a href="https://developers.google.com/identity/one-tap/web/reference/js-reference#PromptMomentNotification"&gt;see here for the full list&lt;/a&gt;. If you're having problems with the button and you don't know why, it might be helpful to use the snippet below to see what those reasons look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;google.accounts.id.prompt(notification =&amp;gt; {
&lt;/span&gt;&lt;span class="gi"&gt;+      if (notification.isNotDisplayed()) {
+          console.log(notification.getNotDisplayedReason())
+      } else if (notification.isSkippedMoment()) {
+          console.log(notification.getSkippedReason())
+      } else if(notification.isDismissedMoment()) {
+          console.log(notification.getDismissedReason())
+      }
&lt;/span&gt;        // continue with another identity provider.
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the reasons you might see is &lt;code&gt;opt_out_or_no_session&lt;/code&gt;. This can mean that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A. your user has "opted out" of the prompt by dismissing it. You can try clearing the &lt;code&gt;g_state&lt;/code&gt; cookie that might be on your domain, if you dismissed it by accident&lt;/li&gt;
&lt;li&gt;B. your user does not have a current Google session in your current browser session.

&lt;ul&gt;
&lt;li&gt;While this is a passwordless sign-on via Google, it does require you to have signed into Google (presumably with a password) at some earlier point.&lt;/li&gt;
&lt;li&gt;If you are using an Incognito window, make sure you sign in to Google within that window.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Now that your user has signed in
&lt;/h2&gt;

&lt;p&gt;Once you have selected your account and signed in with no errors, its time to hook it into your React app. If you've used the Google Sign-In for Websites library before (&lt;a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-pt-1/"&gt;see here for my guide on setting it up&lt;/a&gt;), there's an API to let you get at the user's info. But with the One Tap Sign-In for Web library, you only get the users id token (aka their JWT token).&lt;/p&gt;

&lt;p&gt;That means you need to decode the ID token to get the users info. We can do that by adding the jwt-decode library with  &lt;code&gt;npm install --save jwt-decode&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;To do all this, add a callback to your initialize block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ import jwt_decode from 'jwt-decode'
&lt;/span&gt;
function App() {
    const [isSignedIn, setIsSignedIn] = useState(false)
&lt;span class="gi"&gt;+   const [userInfo, setUserInfo] = useState(null)
+   const onOneTapSignedIn(response =&amp;gt; {
+       setIsSignedIn(true)
+       const decodedToken = jwt_decode(response.credential)
+       setUserInfo({...decodedToken})
+   })
&lt;/span&gt;
     const initializeGsi = () =&amp;gt; {
        google.accounts.id.initialize({
            client_id: 'insert-your-client-id-here',
&lt;span class="gi"&gt;+            callback: onOneTapSignedIn
&lt;/span&gt;        });
        ...
 }
    ...
    return (
        &amp;lt;div className="App"&amp;gt;
            &amp;lt;header className="App-header"&amp;gt;
                &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
&lt;span class="gi"&gt;+               { isSignedIn ?
+                   &amp;lt;div&amp;gt;Hello {userInfo.name} ({userInfo.email})&amp;lt;/div&amp;gt; :
+                   &amp;lt;div&amp;gt;You are not signed in&amp;lt;/div&amp;gt;
+               }
&lt;/span&gt;            &amp;lt;/header&amp;gt;
        &amp;lt;/div&amp;gt;
    )
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see all the user info that is available to you, &lt;a href="https://developers.google.com/identity/one-tap/web/reference/js-reference#credential"&gt;see the dev guide here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Signing out
&lt;/h2&gt;

&lt;p&gt;The docs suggest adding a &lt;code&gt;&amp;lt;div id="g_id_signout"&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; to your page, but its not clear if the library is supposed to create a sign-out button for you. I think the answer is no, because I tried it, and nothing happens.&lt;/p&gt;

&lt;p&gt;My current theory is that signing-out is meant to be left to your own application, and can be as easy as refreshing the page.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this post, I'm only using the One-Tap button on the front-end since I have no login system myself. Whenever you refresh the page, you'll see the prompt even if you just finished signing in.&lt;/li&gt;
&lt;li&gt;If I wanted to integrate this with an existing login system, "sign-out" would mean signing out of my own application (and not out of my google account)&lt;/li&gt;
&lt;li&gt;This flow works out as long as you dont enable the auto sign-in option.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+ const signout = () =&amp;gt; {
+     // refresh the page
+     window.location.reload();
+ }
&lt;/span&gt;
return (
    &amp;lt;div className="App"&amp;gt;
        &amp;lt;header className="App-header"&amp;gt;
            &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
            { isSignedIn ?
&lt;span class="gi"&gt;+               &amp;lt;div&amp;gt;
+                   Hello {userInfo.name} ({userInfo.email})
+                   &amp;lt;div className="g_id_signout"
+                       onClick={() =&amp;gt; signout()}&amp;gt;
+                       Sign Out
+                    &amp;lt;/div&amp;gt;
&lt;/span&gt;               &amp;lt;/div&amp;gt; :
               &amp;lt;div&amp;gt;You are not signed in&amp;lt;/div&amp;gt;
            }
        &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
&lt;span class="err"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Limitations of the One Tap Sign-In button
&lt;/h2&gt;

&lt;p&gt;Also lost in the developer guide is this comment for the prompt snippet &lt;code&gt;// continue with another identity provider.&lt;/code&gt; which brings us to the section on limitations of this sign-in prompt.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The One Tap Sign-In button only works on Chrome &amp;amp; Firefox across Android, iOS, macOS, Linux, Window 10. If you have users on Safari or Edge, they won't see the prompt. When I try it, I get a Not Displayed error with reason &lt;code&gt;opt_out_or_no_session&lt;/code&gt; 🤷&lt;/li&gt;
&lt;li&gt;If your users accidentally dismiss the prompt (see the warning above if you missed it the first time), they will also see &lt;code&gt;opt_out_or_no_session&lt;/code&gt; as the Not Displayed reason, and they will be unable to sign-in.&lt;/li&gt;
&lt;li&gt;The library (and the interface itself) is different from the Google Sign-In for Web library. The One Tap library uses &lt;code&gt;google.accounts.id.initialize()&lt;/code&gt; to initialize the app, and the other one uses &lt;code&gt;gapi.auth2.init()&lt;/code&gt; - That seems like a missed opportunity to put both login systems behind the same interface.&lt;/li&gt;
&lt;li&gt;There's no sign out button - the snippet thats mentioned in the docs doesn't seem to do anything. My guess is that a sign-out button could mean refreshing the page which would cause the prompt to appear again, effectively signing you out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's highlighted on the main page of the dev docs, but you can't use this library alone. I did it here for the purposes of a hello world example, but its meant to be an upgrade to your login experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it out
&lt;/h2&gt;

&lt;p&gt;I've pushed this sample code to github available on &lt;a href="https://github.com/intricatecloud/google-one-tap-web-demo"&gt;intricatecloud/google-one-tap-web-demo&lt;/a&gt;. Instructions to run the demo in the README.&lt;/p&gt;

&lt;p&gt;It's worth taking a look at how some of these other sites have implemented the login workflow. Sign in to Google in your current browser session, and then visit medium.com to see the prompt in action.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Avoiding burnout as a new tech lead</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Tue, 28 Jan 2020 14:03:19 +0000</pubDate>
      <link>https://dev.to/intricatecloud/avoiding-burnout-as-a-new-tech-lead-54jc</link>
      <guid>https://dev.to/intricatecloud/avoiding-burnout-as-a-new-tech-lead-54jc</guid>
      <description>&lt;p&gt;Does this sound familiar? The best dev on the team is promoted to being a tech lead, despite not having any management experience. That dev then all of a sudden has to deal with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angela, the senior dev who broke up with her partner and is emotionally devastated and needs to take a few days&lt;/li&gt;
&lt;li&gt;Oscar, the new dev, saying he needs a few more days to refactor a logging module that needs a "cleaner" interface.&lt;/li&gt;
&lt;li&gt;Kevin the PM who keeps making these damn status meetings&lt;/li&gt;
&lt;li&gt;Michael, the sales guy who keeps promising features that don't exist yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You spend all your time drowning in problems, trying to keep everyone happy. You found out that multiple teams were depending on your work only after it all fell behind. People start looking to you for answers, and you don't have any. You stopped caring. You're burnt out.  I've been there myself. 👋 hi.&lt;/p&gt;

&lt;p&gt;Almost as if the presence of programming skills also indicated that the person can manage a project with many other people. There are, at least, a good chunk of people (developers included) who honestly believe this. It sounds wrong, doesn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  How I started as a lead
&lt;/h2&gt;

&lt;p&gt;First time I became a lead, I had no prep other than being a teammate and so my only goal for my first few months was to do everything the last person did - like I imagine the person before them did, and the person before them... I also started reading everything I could about &lt;a href="https://www.intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/"&gt;being a tech lead - check out my list here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also decided to keep taking on the same amount of work I usually did, in addition to my tech lead responsibilities. Sound familiar? :raises_hand: I&lt;/p&gt;

&lt;p&gt;They were in a lot of meetings, so I felt I had to be in a lot of meetings. That blew apart my schedule for the week because I couldn't work on some relatively important things, and so schedules fell behind quickly after that. I would stay late at the office (seemed to be common among tech leads or maybe just consultants), and spend long evenings catching up on extra work after people started leaving for the day. In my bachelor days, this was fine - I had no other responsibilities and coding was fun - thats the excuse anyway.&lt;/p&gt;

&lt;p&gt;All this meant that my days were filled with meetings and other "administrivia", my evening/nights were filled with coding, and I started burning myself out over time.&lt;/p&gt;

&lt;p&gt;Anyway, long story short.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's a couple tips to help you avoid burnout as a new lead
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Plan your time carefully
&lt;/h3&gt;

&lt;p&gt;Block out time on your calendar to manage your week, set hard boundaries between when you start/end the day (especially if you work remotely) and stick to it.&lt;/p&gt;

&lt;p&gt;You probably don't need to attend every meeting thats on your calendar. But if for some reason, you are required, depending on the meeting, I'll multi-task and be a "fly on the wall" and just listen, other times I'll have to be an active participant.&lt;/p&gt;

&lt;p&gt;If you need to set aside a day to do some hands-on dev work, put it in the calendar and decline everything else. Unless you're working on life/death scenarios, it could probably wait until later. Some people have No Meeting Thursdays, Code Review Afternoons... Call it whatever you want.&lt;/p&gt;

&lt;p&gt;When performance review season rolls around, its even more helpful to block out time to prepare everyone's review. I've tried to write all my reviews the night before in one week for my team, and it wound up taking me HOURS for each person as I tried to remember everything they did.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Stay out of the critical path
&lt;/h3&gt;

&lt;p&gt;The critical path in a project is the set of tasks that NEED to be accomplished in order for something to work. For example - if you're building a feature that adds a button to your site, adding a linter or a CSS theme to your project is not required for that button to work. The button itself is obviously the important work.&lt;/p&gt;

&lt;p&gt;As a tech lead, you gotta stop picking up any of that important work. If its a time sensitive thing, and you aren't able to get it done in time, it'll have some downstream consequences, and start blocking other projects from getting done. Thats what your team is for - they can go heads down on the work.&lt;/p&gt;

&lt;p&gt;But what if some of your team doesn't know how to make changes to your CI/CD pipeline, or the front-end dev hasn't worked on backend SQL queries? Its on you as the lead to support the team to do the work - like that time we hired a junior dev who knew python and just a little javascript to do front-end work in an Angular app. He had never worked with a full-blown framework with all the bells and whistles before, but we had bugs to fix and no time to kill, and sure enough, with some training and mentoring, he got up to speed and started cranking through bugs.&lt;/p&gt;

&lt;p&gt;Now, its important you stay technically involved at some level, but your focus will be on the big picture, like making sure that important features are getting shipped, and less on the small details, like why is &lt;code&gt;[1,2,3].flatMap&lt;/code&gt; throwing an error.&lt;/p&gt;

&lt;p&gt;Want to make your week easier? Try these to start.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start with blocking out a 4 hour chunk one day a week for heads-down coding time and decline everything. See how it goes and you can adjust depending on your workload.&lt;/li&gt;
&lt;li&gt;Take a look at upcoming work that you would usually do like adding a new API endpoint to a backend, and offer to pair with another dev so that they can learn how to do that work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're a new tech lead, definitely check out some of the &lt;a href="https://www.intricatecloud.io/2018/12/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead/"&gt;9 books that helped me navigate my first time being a tech lead&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;I'd like to hear from you - what were some of the things that frustrated you when you first became a tech lead?&lt;/p&gt;

</description>
      <category>career</category>
      <category>techlead</category>
    </item>
    <item>
      <title>Setting up automatic failover for your static websites hosted on S3 </title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Wed, 16 Oct 2019 22:57:56 +0000</pubDate>
      <link>https://dev.to/intricatecloud/setting-up-automatic-failover-for-your-static-websites-hosted-on-s3-47b7</link>
      <guid>https://dev.to/intricatecloud/setting-up-automatic-failover-for-your-static-websites-hosted-on-s3-47b7</guid>
      <description>&lt;p&gt;Up until late last year, you couldn't set up automatic failover in CloudFront to look at a different bucket. You could only have one Primary origin at a time, which meant that you needed additional scripts to monitor and trigger failover. (It was one of the &lt;a href="https://www.intricatecloud.io/2018/04/gotchas-with-cloudfront-s3-architecture/" rel="noopener noreferrer"&gt;gotcha's I had written about previously about using CloudFront&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Then AWS dropped the bombshell during re:invent 2018 that Cloudfront can now support it by using Origin Groups - &lt;a href="https://aws.amazon.com/about-aws/whats-new/2018/11/amazon-cloudfront-announces-support-for-origin-failover/" rel="noopener noreferrer"&gt;Announcement&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this post, I'll show how you can configure this for an existing site via the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding an origin group to your Cloudfront distribution
&lt;/h2&gt;

&lt;p&gt;I was working on this for one of my websites - gotothat.link - its not a static website, but its just a site that serves a bunch of redirects so all the same rules apply.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1 - Configure replication on your S3 bucket
&lt;/h3&gt;

&lt;p&gt;Go to the replication settings page for your S3 bucket. &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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30051547%2Fs3-replication-settings-page.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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30051547%2Fs3-replication-settings-page.png" alt="S3 Bucket Replication Settings page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you haven't yet enabled Versioning, it will tell you that you're required to do so.&lt;/p&gt;

&lt;p&gt;Enable replication for the entire bucket by clicking Add Rule. For our use case, we don't need to encrypt the replicated contents. &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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30051918%2Fs3-replication-rule-page.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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30051918%2Fs3-replication-rule-page.png" alt="replication rule page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can follow the prompt to create a new bucket in us-west-1 - N. California. &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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30052022%2Freplication-bucket-options.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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30052022%2Freplication-bucket-options.png" alt="replication bucket options"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've created the S3 bucket, make sure to update the bucket policy on your new replication bucket to allow the static website hosting to work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::gotothat.link-west-1/*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the IAM role, I'll let the console create one for me - this is whats generated automatically for you&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;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"s3:Get*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:ListBucket"&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&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="s2"&gt;"arn:aws:s3:::gotothat.link"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::gotothat.link/*"&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&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="s2"&gt;"s3:ReplicateObject"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:ReplicateDelete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:ReplicateTags"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"s3:GetObjectVersionTagging"&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;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:s3:::gotothat.link-west-1/*"&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;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;Once you click Save, you should see this panel appear now when you're on the Management page of your S3 bucket.&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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30052335%2Fs3-replication-dashboard.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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30052335%2Fs3-replication-dashboard.png" alt="S3 management console - replication settings page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Replicate data
&lt;/h3&gt;

&lt;p&gt;Replication is now enabled. But we're not quite done. &lt;/p&gt;

&lt;p&gt;When you enable replication, any objects created/modified from that point forward are then replicated to your target bucket. Any existing objects are NOT automatically transferred over - you have to do a one-time sync when you first set it up.&lt;/p&gt;

&lt;p&gt;If all you have in your bucket is your website files and nothing else, then you can do an &lt;code&gt;aws s3 sync s3://source.bucket s3://target.bucket&lt;/code&gt; to import all your existing objects. Depending on how much is in there, it can take awhile.&lt;/p&gt;

&lt;p&gt;If you have some objects in your source S3 bucket that require you use the &lt;code&gt;Website-Redirect-Location&lt;/code&gt; metadata (this is needed if you want to &lt;a href="https://www.intricatecloud.io/2019/09/that-time-i-needed-to-create-20k-shortlinks-and-how-to-create-them-on-aws/" rel="noopener noreferrer"&gt;serve redirects out of your domain like this&lt;/a&gt;):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot do a sync with the replicated bucket using something like &lt;code&gt;aws s3 sync s3://my-bucket-name s3://my-bucket-name-us-west-1&lt;/code&gt;. While the sync may succeed, your redirects will not. This is annoying.&lt;/li&gt;
&lt;li&gt;The S3 docs state that &lt;code&gt;Website-Redirect-Location&lt;/code&gt; isn't copied over when using a sync command, although it doesn't specify why. &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectCOPY.html" rel="noopener noreferrer"&gt;See docs here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;However, Objects that are automatically replicated will keep their Website-Redirect-Location metadata. But any files that are &lt;code&gt;aws s3 sync&lt;/code&gt;'d between 2 buckets will not have that metadata.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last step is to go to the replicated bucket, select Properties, and enable Versioning + Static Website Hosting for it. If you forget this, you won't receive the 301 redirect.&lt;/p&gt;

&lt;p&gt;At this point, you should have a bucket in another region, with all the same contents as your primary bucket, with static website hosting enabled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Failover in CloudFront
&lt;/h2&gt;

&lt;p&gt;Create a new origin with an origin id like &lt;code&gt;gotothatlink-failover-prod&lt;/code&gt; and use the URL of the form &lt;code&gt;gotothat.link-west-1.s3-website-us-west-1.amazonaws.com&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Create a new origin group and add your two origins at the prompt. The primary origin will be our original S3 bucket, and the secondary origin will be the new failover origin we created. &lt;/p&gt;

&lt;p&gt;For the failover criteria, we can check all the 5xx status codes since that would imply issues happening in S3. You can check 404s if you want to protect against someone accidentally deleting all your data, or 403 Forbidden errors to protect against accidental bucket policy changes.&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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30053502%2Fcloudfront-origin-group.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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30053502%2Fcloudfront-origin-group.png" alt="origin group settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, go to the Behaviors tab, and make sure to update the behavior to route all paths to your Origin Group (and not your Origin). Otherwise, even if it fails over, it will still send requests to your primary origin.&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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30053716%2Fcloudfront-behaviors-update.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%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2019%2F09%2F30053716%2Fcloudfront-behaviors-update.png" alt="edit behavior settings page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you've set these changes, you'll have to wait for the CloudFront distribution to finish updating (could take up to a half hour).&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it by "accidentally" trashing your primary bucket
&lt;/h2&gt;

&lt;p&gt;Since I've enabled failover on 404 errors, I'll run a test where I delete everything in my bucket. I ran &lt;code&gt;aws s3 rm --recursive s3://gotothat.link/&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;And now to save you boatloads of time - here is every error I ran into when setting this up.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You get a 404 - Cloudfront is still looking at your primary bucket. Turns out I hadn't updated the Behaviors for the CloudFront distribution.&lt;/li&gt;
&lt;li&gt;You get a 403 - Your replicated bucket has incorrect permissions. See the section above labeled "&lt;em&gt;Configure replication on your S3 bucket&lt;/em&gt;" to see the required bucket policy.&lt;/li&gt;
&lt;li&gt;You now get a 200 - your object doesn't have the &lt;code&gt;Website-Redirect-Location&lt;/code&gt; metadata associated with it

&lt;ul&gt;
&lt;li&gt;Take a look at the replicated object by doing the following - note that it includes &lt;code&gt;WebsiteRedirectLocation&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check that you haven't done an &lt;code&gt;aws s3 sync&lt;/code&gt; to import your redirects to your bucket.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;☁  intricate-web  aws s3api get-object --bucket gotothat.link-west-1 --key newvideo  newvideo-1
{
    "AcceptRanges": "bytes",
    "LastModified": "Sun, 22 Sep 2019 05:21:08 GMT",
    "ContentLength": 0,
    "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
    "VersionId": "dynNjEGPeE3.z6kCCp.zgvVlbYkA.h3X",
    "ContentType": "binary/octet-stream",
    "WebsiteRedirectLocation": "https://youtube.com/video",
    "Metadata": {},
    "ReplicationStatus": "REPLICA"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you're doing it via the console, this doesn't take long to set up. Its a handful of steps that you can do in &amp;lt; 30 mins that will give you extra confidence that your site is backed up, and still functional even if S3 goes down (it usually has a high-profile outage at least once a year).&lt;/p&gt;

&lt;p&gt;With this solution in place, you have an HTTPS website served from a global CDN with automatic failover in case of an S3 outage or data loss in your primary bucket so that you don't need to take any downtime. All this for just a few bucks a month. :mindblown:&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Create your own short link redirects using CloudFront + S3 </title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Tue, 01 Oct 2019 19:14:17 +0000</pubDate>
      <link>https://dev.to/intricatecloud/create-your-own-short-link-redirects-using-cloudfront-s3-1kmj</link>
      <guid>https://dev.to/intricatecloud/create-your-own-short-link-redirects-using-cloudfront-s3-1kmj</guid>
      <description>&lt;p&gt;Get branded short links under your own domain for dirt-cheap by self-hosting it on AWS using CloudFront + S3. It's a no-code server-less solution to get redirects on the cheap and in this guide, I'll detail how to set it up. If you're interested in some good reasons why - check out my &lt;a href="https://www.intricatecloud.io/2019/09/that-time-i-needed-to-create-20k-shortlinks-and-how-to-create-them-on-aws/"&gt;first post on it here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this guide, I'll go over &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what do our redirects/short links need to be able to do&lt;/li&gt;
&lt;li&gt;how to set it up via the console if thats what you're comfortable with, or doing it via the AWS node SDK.&lt;/li&gt;
&lt;li&gt;How to check that its working&lt;/li&gt;
&lt;li&gt;A few things to check when it doesn't (it never really works the first time, doesn't it?)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What do we need out of our short links?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I need to be able to create custom links to redirect&lt;/li&gt;
&lt;li&gt;I need to be able to change them later&lt;/li&gt;
&lt;li&gt;I want to see traffic to the redirect, and how many times they've been clicked (this will be part of another post)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Technical Requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Highly Available, Redundant (99.9% SLA)&lt;/li&gt;
&lt;li&gt;Serve HTTPS 301 redirects&lt;/li&gt;
&lt;li&gt;Serverless. Cloudfront + S3 means we don't have any servers to manage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the architecture we're going to set up on AWS: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ArdtpjW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/08062438/shortlinks-on-aws-11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ArdtpjW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/08062438/shortlinks-on-aws-11.png" alt="cloudcraft diagram showing cloudfront + s3 with replication"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In short, we have...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a CloudFront CDN which gives us free HTTPS for our domain (courtesy of AWS Certificate Manager)&lt;/li&gt;
&lt;li&gt;An S3 bucket with Cross-Replication enabled to another bucket in another region&lt;/li&gt;
&lt;li&gt;Each redirect is stored as an object with the &lt;code&gt;Website-Redirect-Location&lt;/code&gt; metadata pointing to our redirect&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting it up via the console
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Set up Cloudfront + S3
&lt;/h3&gt;

&lt;p&gt;I've set up this diagram for one of my domains - gotothat.link so I'll show you how I've set that up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vcrXfCnL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22012412/r53-gotothatlink-image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vcrXfCnL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22012412/r53-gotothatlink-image.png" alt="registered domains on Route53"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I own gotothat.link using Route53 as my registrar. You don't need to have your domain on AWS Route53 if you want to host your short links on AWS - if you have one already via something like GoDaddy or Namecheap, thats fine too, you can use that.&lt;/p&gt;

&lt;p&gt;Cloudfront will be providing an HTTPS URL that we can use using S3 as an origin which will serve our redirects. Cloudfront will have its logs piped to another bucket, and we can get some metrics out of that.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfu9j9Rg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22012918/cloudfront-gotothatlink.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfu9j9Rg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22012918/cloudfront-gotothatlink.png" alt="cloudfront properties"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--l2p4uP3s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22013108/s3-origin-cloudfront.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--l2p4uP3s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22013108/s3-origin-cloudfront.png" alt="s3 origin properties"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An S3 bucket will be used to store the actual redirects.&lt;/p&gt;

&lt;p&gt;For an in-depth guide on setting up those 2 services, you can &lt;a href="https://www.intricatecloud.io/2018/04/creating-a-serverless-static-website/"&gt;check out my post which walks you through setting up via the console&lt;/a&gt; or &lt;a href="https://www.intricatecloud.io/2018/04/creating-your-serverless-website-in-terraform-part-2/"&gt; if you want to set it up with terraform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have Cloudfront deployed and connected to your S3 bucket, we can  add our redirects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Add a redirect
&lt;/h3&gt;

&lt;p&gt;Create an empty file and upload it to the console, with Website-Redirect-Location metadata pointing to the URL you want to redirect to. In this case, I've uploaded an empty file named &lt;code&gt;not-a-rick-roll&lt;/code&gt; and added the metadata to point to a particular YouTube video.&lt;/p&gt;

&lt;p&gt;You can use the default permissions, default storage type, and no need for encryption. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_oRPzP-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/29204247/s3-upload-metadata.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_oRPzP-n--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/29204247/s3-upload-metadata.png" alt="aws s3 upload file with metadata screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot, you'll see theres a warning under the Metadata header - &lt;code&gt;You cannot modify object metadata after it is uploaded.&lt;/code&gt; I'm sure theres a reason this is included, but I've been able to just go into the object's metadata and change the values from the screen below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yXep6MkN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22013916/object-metadata-redirect.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yXep6MkN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22013916/object-metadata-redirect.png" alt="s3 object and metadata"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By default, S3 will add a Content-Type metadata to your object when you upload it. So you can see in the screenshot that the Content-Type has been set to &lt;code&gt;binary/octet-stream&lt;/code&gt; which implies this link will return some file. Its worth noting in this case, that by adding the Website-Redirect-Location metadata to your object, S3 will serve up a HTTP 301 Redirect to your target redirect - a 301 response only needs to include a Location header specifying the redirect. So it doesn't quite matter what the Content-Type is on your object. You can leave it alone, or delete it.&lt;/p&gt;

&lt;p&gt;Thats it! Assuming you have many more of these, you can even automate the creation of your redirects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 3: Automating it
&lt;/h3&gt;

&lt;p&gt;If you use the CLI, you can script it using the AWS CLI - &lt;code&gt;aws s3 cp not-a-rick-roll s3://gotothat.link/not-a-rick-roll --website-redirect "https://www.youtube.com/watch?v=dQw4w9WgXcQ"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you have your redirects stored in a CSV, you can parse it and upload it using the node aws-sdk - check out this example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# redirects.csv

notarickroll,https://youtube.com/video
no/really/not/a/rickroll/video, https://youtube.com/video
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// redirects.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;csv-parse/lib/sync&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&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;AWS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;aws-sdk&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;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;S3&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;redirects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redirects.csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nx"&gt;redirects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;redirect&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Uploading &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putObject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gotothat.link&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redirect&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="na"&gt;Metadata&lt;/span&gt;&lt;span class="p"&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;Website-Redirect-Location&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&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="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verify your redirects
&lt;/h2&gt;

&lt;p&gt;You can check to see that its working by using good ol' &lt;code&gt;curl&lt;/code&gt; to make a request to your site - gotothat.link, and verify that you get back a 301 redirect for that object. This is what the output of that command looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;☁  intricate-web  curl -i https://gotothat.link/not-a-rick-roll
HTTP/2 301
content-length: 0
location: https://www.youtube.com/watch?v=dQw4w9WgXcQ
date: Sun, 22 Sep 2019 01:52:57 GMT
server: AmazonS3
x-cache: Miss from cloudfront
via: 1.1 96b6c9282feceea8aa00c25902322bb6.cloudfront.net (CloudFront)
x-amz-cf-pop: EWR53-C1
x-amz-cf-id: rNwzh3rL-ErWpi0YuDrqQcCvvmbqa_ZWj7cPl71WrPPi1vlFbW0siA==
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have URL redirects served out of your own domain so you can do things like gotothat.link/not-a-rick-roll (check it out - its totally not a rick roll 😎)&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;If you change a redirect and you don't see it taking effect, there might be one of two things happening:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you're trying this in your browser, you might have a cached version of the redirect. Open an incognito window and try it there.&lt;/li&gt;
&lt;li&gt;Cloudfront is still sending you back a cached version of the redirect. You'll have to use &lt;code&gt;curl&lt;/code&gt; to inspect the request so you can see whats happening.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;☁  intricate-web  curl -i https://gotothat.link/not-a-rick-roll
HTTP/2 301
content-length: 0
location: https://www.youtube.com/watch?v=dQw4w9WgXcQ
date: Sun, 22 Sep 2019 01:52:57 GMT
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 c67ae9899d89f9402837da3a0ead9442.cloudfront.net (CloudFront)
x-amz-cf-pop: EWR53-C1
x-amz-cf-id: 7pDL0XgVjGmdMqcHf7tSsRILd0WfZpihpLF66SYVJLlzqfCJG2PMRg==
age: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, we want to pay attention to the &lt;code&gt;x-cache&lt;/code&gt; &amp;amp; &lt;code&gt;age&lt;/code&gt; response header. In this case, it says &lt;code&gt;Hit from cloudfront&lt;/code&gt; and the &lt;code&gt;age == 4&lt;/code&gt;. The age is the time that object has been in the cache. If you've updated your redirect recently, and you're still seeing an old redirect in your response headers, then you'll need to invalidate your Cloudfront cache. You can do that from the Invalidations page&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lPg4CDog--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22015855/invalidations-cloudfront.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lPg4CDog--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/22015855/invalidations-cloudfront.png" alt="the Invalidations page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll know it updated when the &lt;code&gt;age&lt;/code&gt; response header shows something recent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;There you have it! By this point, you should have your own domain sending you back redirects that you can monitor yourself. Now, this isn't the whole story because theres still a feature or two that we need to set up. So two posts coming up next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to do this if you use an infrastructure-as-code tool like terraform&lt;/li&gt;
&lt;li&gt;how to tell when specific links are getting clicked by monitoring your CloudFront logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try out this setup, leave me a ❤️ or a comment to let me know how it went - feedback welcome! Stay tuned for the next post and follow me here on dev.to 😄&lt;/p&gt;

</description>
      <category>aws</category>
      <category>webdev</category>
    </item>
    <item>
      <title>That time I needed to create 20k short links, and how to create them on AWS</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Mon, 09 Sep 2019 20:16:27 +0000</pubDate>
      <link>https://dev.to/intricatecloud/that-time-i-needed-to-create-20k-short-links-and-how-to-create-them-on-aws-4djl</link>
      <guid>https://dev.to/intricatecloud/that-time-i-needed-to-create-20k-short-links-and-how-to-create-them-on-aws-4djl</guid>
      <description>&lt;p&gt;This problem landed on my lap awhile ago, and I've seen it come up time and time again so I wanted to share how we went about it. Our marketing team was submitting one of our products for a review for a client, it has a ton of content. There was a long list of deep-links in the product that we wanted the reviewers to see, but we needed to create short links so they could embed them on some doc.&lt;/p&gt;

&lt;p&gt;There wound up being a total of 20k short links, which meant that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I needed to be able to upload 20,000 in one go, it needed a scriptable API&lt;/li&gt;
&lt;li&gt;whatever we used needed to be around for at least 6 months to be available during a certain review period&lt;/li&gt;
&lt;li&gt;the links needed to work, and be available&lt;/li&gt;
&lt;li&gt;we needed to be able to create the redirects from a Google Sheet&lt;/li&gt;
&lt;li&gt;they wanted to know if/when links were clicked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We ultimately landed on a solution with just AWS that comes out dirt cheap - but there were a few things we had to consider first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use something like bit.ly?
&lt;/h2&gt;

&lt;p&gt;For this specific case - based on their pricing, we'd be in the enterprise range under the "Contact Us for a Quote" plan which puts us north of $400/year (and a "quick call" with an account manager) for something I only need once.&lt;/p&gt;

&lt;p&gt;$400/year sounds steep for something that ...should.... be simple and cheap. 301 redirects have been around since 1999 (20 years ago!) - why isnt this a solved problem! (it kinda is, but it isnt)&lt;/p&gt;

&lt;p&gt;Even outside of enterprisey considerations,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bitly comes with its own limits for the free tier, and even doing something like bulk redirects needs to be on their enterprise plan. &lt;/li&gt;
&lt;li&gt;30/month for their basic plan is a bit steep for some 301s, and we can do it for cheaper. (you trade off some features though, primarily analytics - if you dont care about that, fantastic!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apart from that, its a good opportunity to learn the ins &amp;amp; outs of AWS, it doesnt involve a lot of services. If you're familiar with Cloudformation or Terraform, you can set it all up that way.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you build it?
&lt;/h2&gt;

&lt;p&gt;We worked out a solution using Cloudfront + S3, where your Objects have a Website-Redirect-Location metadata attached (&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/how-to-page-redirect.html"&gt;see docs here&lt;/a&gt;). We built it out in an afternoon, and had all 30k redirects generated and working, and it costs just a few bucks/month to run.&lt;/p&gt;

&lt;p&gt;Let's say you own short.url and you want to be able to have short.url/2019barbecue redirect to a Facebook page.&lt;/p&gt;

&lt;p&gt;At a high level, take a look at this architecture (images courtesy of cloudcraft.co):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2ArdtpjW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/08062438/shortlinks-on-aws-11.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2ArdtpjW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/08062438/shortlinks-on-aws-11.png" alt="cloudcraft architecture of shortlinks on aws"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You point your domain (which can be hosted anywhere, doesnt need to be route53) at a Cloudfront distribution that sits in front an S3 bucket served as a website. In that bucket are many objects, with the Website Redirect Location metadata set to redirect to our target url - short.url/2019barbecue redirects to example.com&lt;/p&gt;

&lt;h3&gt;
  
  
  Are your redirects business critical?
&lt;/h3&gt;

&lt;p&gt;Maybe you're running a launch and you need people to click a link. You are now responsible for uptime and so AWS' uptime is your uptime. &lt;/p&gt;

&lt;p&gt;In this case, your availability is the minimum of either Cloudfront or S3 (99.9%) - you do, however, have the benefit of a globally distributed CDN… and a highly durable and replicated object store… with failover to another region in case it craps out - not to mention its cheap-ish to implement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do you want analytics?
&lt;/h3&gt;

&lt;p&gt;Bitly analytics are pretty neat - thats a tradeoff since its something you'll need to do yourself - but if you already have an analytics/monitoring platform, you can get your data through there. For things on my personal site, I don’t care for seeing analytics - i just want to share a customized short link.&lt;/p&gt;

&lt;p&gt;Out of the box, you get some level of Cloudwatch monitoring around Cloudfront including error rates, data in/out, and total requests. (&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/monitoring-using-cloudwatch.html#monitoring-console.distributions"&gt;full list here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If you want more info, then you need to dump Cloudfront access logs to another S3 bucket and use Athena (&lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/analyze-logs-athena/"&gt;the docs for that here&lt;/a&gt;) for some quick analytics to see things like source IP, referer, requested path, response code&lt;/p&gt;

&lt;h2&gt;
  
  
  Any gotchas i should be aware of?
&lt;/h2&gt;

&lt;p&gt;Be mindful of updating where a redirect points to. 301 redirects CAN be cached indefinitely by your browser since the spec indicates a 301 means Permanent Redirect. &lt;/p&gt;

&lt;p&gt;If someone has already clicked your link before, it's possible that they may be taken to the old location for some period of time. In practice, I've seen a long tail of requests to the old value of the redirect kinda like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zB6clIzO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/09040905/long-tail-graph.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zB6clIzO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://s3.amazonaws.com/intricatecloud-content/wp-content/uploads/2019/09/09040905/long-tail-graph.png" alt="graph of decreasing requests vs time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;302 redirects are meant to be temporarily cached, so it has less issues. Some architectures limit you to 301 redirects, others give you the option to do a 302. In this case, Cloudfront + S3 only lets you do 301s and thats just a constraint we need to deal with unless we want to throw Lambda in the mix as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If you find yourself needing to create some customized short links or redirects, consider using Cloudfront + S3 to serve them using the architecture above - you get all the benefits of an enterprise-grade solution at a fraction of the cost if you're already familiar with AWS. &lt;/p&gt;

&lt;p&gt;I'm working on a series of posts to thoroughly explain how to set up short links on AWS. Next up, I'll be covering how to set up the short links in the console, and then how you could do it in terraform. &lt;/p&gt;

&lt;p&gt;Give the article some ❤️ and &lt;a href="https://www.intricatecloud.io/intricate-cloud-newsletter-signup/"&gt;sign up to my mailing list here&lt;/a&gt; so you don't miss the next post!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>adding google sign-in to your webapp - a react example</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Wed, 07 Aug 2019 21:43:10 +0000</pubDate>
      <link>https://dev.to/intricatecloud/adding-google-sign-in-to-your-webapp-a-react-example-152k</link>
      <guid>https://dev.to/intricatecloud/adding-google-sign-in-to-your-webapp-a-react-example-152k</guid>
      <description>&lt;p&gt;In this next part of the series, I'll be walking you through an implementation of google sign-in with a simple react app and a bonus react-router example.&lt;/p&gt;

&lt;p&gt;Up until now, we've seen 2 different hello world examples of how to add google sign-in on the front-end - using plain HTML and vanilla JS. It's been all nice and dandy for a hello world, but one thing thats been missing while I was figuring out google sign-in is what a working implementation looks like - especially in React. &lt;/p&gt;

&lt;p&gt;*There is a &lt;a href="https://github.com/anthonyjgrove/react-google-login"&gt;react-google-login component&lt;/a&gt; that configures all of google sign-in behind a &lt;code&gt;&amp;lt;GoogleLogin&amp;gt;&lt;/code&gt; tag. It's quite useful and I've used it in a few instances - my one complaint is that you can't get at the return value of the &lt;code&gt;gapi.auth2.init()&lt;/code&gt; method. This post will show whats going on under the covers if you prefer not to use a library.&lt;/p&gt;

&lt;h2&gt;
  
  
  creating a new react app with google sign-in
&lt;/h2&gt;

&lt;p&gt;First - create the app &lt;code&gt;create-react-app google-auth-demo&lt;/code&gt;. The files we'll mainly be working with are App.js and index.html.&lt;/p&gt;

&lt;p&gt;Add the google sign-in script tag to your &lt;code&gt;public/index.html&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;head&amp;gt;
  ...
  &amp;lt;script src="https://apis.google.com/js/api.js" async defer&amp;gt;&amp;lt;/script&amp;gt;
  ...
&amp;lt;/head&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  add the login button
&lt;/h2&gt;

&lt;p&gt;In App.js - add some state to keep track of when the user has signed in&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contructor(props) {
    super(props)
    this.state = {
        isSignedIn: false,
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the button to the component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;render() {
  return (
    &amp;lt;div className="App"&amp;gt;
        &amp;lt;header className="App-header"&amp;gt;
          &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;

          &amp;lt;p&amp;gt;You are not signed in. Click here to sign in.&amp;lt;/p&amp;gt;
          &amp;lt;button id="loginButton"&amp;gt;Login with Google&amp;lt;/button&amp;gt;
        &amp;lt;/header&amp;gt;
      &amp;lt;/div&amp;gt;
  )
}

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

&lt;/div&gt;



&lt;p&gt;Wait, how do I avoid showing this if the user is signed in? We can use the state to conditionally show it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getContent() {
  if (this.state.isSignedIn) {
    return &amp;lt;p&amp;gt;hello user, you're signed in &amp;lt;/p&amp;gt;
  } else {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;You are not signed in. Click here to sign in.&amp;lt;/p&amp;gt;
        &amp;lt;button id="loginButton"&amp;gt;Login with Google&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }

}

render() {
  return (      
    &amp;lt;div className="App"&amp;gt;
      &amp;lt;header className="App-header"&amp;gt;
        &amp;lt;img src={logo} className="App-logo" alt="logo" /&amp;gt;
        &amp;lt;h2&amp;gt;Sample App.&amp;lt;/h2&amp;gt;

        {this.getContent()}           
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Since conditionals are a little hard to write with inline JSX, I've pulled out the conditional block to another method to provide the component that we want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, you'll have a button that does nothing (the best type of button) and you'll see the "You are not signed in" message&lt;/p&gt;

&lt;h2&gt;
  
  
  add sign-in
&lt;/h2&gt;

&lt;p&gt;To finish setting up google sign-in, you'll want to initialize the library using &lt;code&gt;gapi.auth2.init()&lt;/code&gt;. A good place to do that is inside of &lt;code&gt;componentDidMount()&lt;/code&gt; callback.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;componentDidMount() {
  window.gapi.load('auth2', () =&amp;gt; {
    this.auth2 = gapi.auth2.init({
      client_id: '260896681708-o8bddcaipuisksuvb5u805vokq0fg2hc.apps.googleusercontent.com',
    })
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use the default styling, use the &lt;code&gt;gapi.signin2.render&lt;/code&gt; method when initializing your component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;onSuccess() {
  this.setState({
    isSignedIn: true
  })
}

componentDidMount() {
  window.gapi.load('auth2', () =&amp;gt; {
    this.auth2 = gapi.auth2.init({
      client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
    })

    window.gapi.load('signin2', function() {
      // render a sign in button
      // using this method will show Signed In if the user is already signed in
      var opts = {
        width: 200,
        height: 50,
        onSuccess: this.onSuccess.bind(this),
      }
      gapi.signin2.render('loginButton', opts)
    })
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When using this method, the button will automatically show whether you're signed in, but the &lt;code&gt;onSuccess&lt;/code&gt; callback won't actually run unless the user clicks it when it says "Sign In". Otherwise, you are logged in automatically. One way to hook into the end of that auto sign in process is by adding a callback to the promise returned by &lt;code&gt;gapi.auth2.init&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;window.gapi.load('auth2', () =&amp;gt; {
  this.auth2 = gapi.auth2.init({
    client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
  })

  this.auth2.then(() =&amp;gt; {
    this.setState({
      isSignedIn: this.auth2.isSignedIn.get(),
    });
  });
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  making a "protected" route
&lt;/h2&gt;

&lt;p&gt;If you're using react-router and you want to add a "protected" route to your React app, you can hijack the &lt;code&gt;render&lt;/code&gt; prop of a &lt;code&gt;&amp;lt;Route&amp;gt;&lt;/code&gt;. You can do something 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;authCheck(props, Component) {
  return this.auth2.isSignedIn.get() ? &amp;lt;Component {...props} /&amp;gt; : &amp;lt;UnauthorizedPage/&amp;gt;

}

render() {
  ...
  &amp;lt;Route path="/home" render={this.authCheck.bind(this, HomePage)}/&amp;gt;
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By hooking into the render property on &lt;code&gt;&amp;lt;Route&amp;gt;&lt;/code&gt;, you can dynamically define what component will load when you try to access that Route. &lt;/p&gt;

&lt;p&gt;This is the strategy employed by the &lt;a href="https://www.npmjs.com/package/react-private-route"&gt;react-private-route project&lt;/a&gt; library to make it a little bit easier to write, definitely worth checking out.&lt;/p&gt;

&lt;h1&gt;
  
  
  conclusion
&lt;/h1&gt;

&lt;p&gt;If you're implementing google sign-in in a React app - check out &lt;a href="https://github.com/intricatecloud/google-sign-in-demo/tree/master/react/google-auth-demo"&gt;my github repo intricatecloud/google-sign-in-demo&lt;/a&gt; to see all the code above in a working setup.&lt;/p&gt;

&lt;p&gt;Throughout this 3-part series, we've covered going from &lt;a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-pt-1/"&gt;a hello-world example of google sign-in&lt;/a&gt;, to &lt;a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-using-the-js-library/"&gt;using the javascript library&lt;/a&gt; to do some hacky things. Now, we've reviewed all the code you need to integrate with the Google Sign-In button.&lt;/p&gt;

&lt;p&gt;Sometimes, tutorials like this can be hard to follow, and it just won't click unless you see it. I'm putting together this series as a live-coding walkthrough where you can see all the mistakes I make going along with the tutorial. &lt;a href="https://www.intricatecloud.io/intricate-cloud-newsletter-signup/"&gt;Sign up to my mailing list here&lt;/a&gt; to get notified when it goes live.&lt;/p&gt;

</description>
      <category>auth</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>adding google sign-in to your webapp - using the js library</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Tue, 23 Jul 2019 22:16:37 +0000</pubDate>
      <link>https://dev.to/intricatecloud/adding-google-sign-in-to-your-webapp-using-the-js-library-3n0g</link>
      <guid>https://dev.to/intricatecloud/adding-google-sign-in-to-your-webapp-using-the-js-library-3n0g</guid>
      <description>&lt;p&gt;In the first part of the series, we decided to use the &lt;a href="https://developers.google.com/identity/sign-in/web/"&gt;Google Sign-In for websites library&lt;/a&gt; to allow you to show some info about the user using javascript. We used the default Google Sign-In workflow with their default button.&lt;/p&gt;

&lt;p&gt;In this part, we'll be going over how to use the &lt;code&gt;gapi&lt;/code&gt; library to configure Sign-In and then actually sign-in the user, as well as a few snippets for handling some common user scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  create your own sign in button
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Load the api.js library instead of platform.js 🤷 (not sure why they're different)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Change &lt;code&gt;https://apis.google.com/js/platform.js&lt;/code&gt; to &lt;code&gt;https://apis.google.com/js/api.js?onload=onLibraryLoaded&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here, we configure our Sign-In client once the library has loaded using the &lt;code&gt;?onload=onLibraryLoaded&lt;/code&gt; callback that you can provide via the URL. &lt;code&gt;api.js&lt;/code&gt; will add a global variable called &lt;code&gt;gapi&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a button to your index.html with a button click handler&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;button onclick="onSignInClicked()"&amp;gt;Sign in with button onClick&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add the following to your script tag in index.html to handle the button click
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function onLibraryLoaded() {
    gapi.load('auth2', function() {
        gapi.auth2.init({
            client_id: 'YOUR_CLIENT_ID.apps.googleusercontent.com',
            scope: 'profile'
        })
    })
}

function onSignInClicked() {
    gapi.load('auth2', function() {
        gapi.auth2.signIn().then(function(googleUser) {
          console.log('user signed in')
        }, function(error) {
            console.log('user failed to sign in')
        })
    })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then access the &lt;code&gt;gapi.auth2&lt;/code&gt; library and initialize it using our &lt;code&gt;client_id&lt;/code&gt; from the Developer Console.&lt;/p&gt;

&lt;h2&gt;
  
  
  how to handle some common user scenarios
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;listening for when the user has signs in&lt;/li&gt;
&lt;li&gt;checking if the user is logged in&lt;/li&gt;
&lt;li&gt;getting the users info&lt;/li&gt;
&lt;li&gt;only allow users from a particular domain to log in&lt;/li&gt;
&lt;li&gt;only allow certain users to log in&lt;/li&gt;
&lt;li&gt;hide content until after a user logs in&lt;/li&gt;
&lt;li&gt;sending your ID token to a backend (if you have one)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  listening for when user signs in
&lt;/h3&gt;

&lt;p&gt;In the above example, we can only run some code after we initialize and sign in the user. But what if you have different parts of the page in different files, each showing something different depending on the user? (this might be the case if you're using components)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class UserAvatarComponent extends React.Component {
    ...
    componentDidMount() {
        gapi.load('auth2', function() {
            gapi.auth2.isSignedIn.listen(function(isSignedIn) {
                console.log('user signed in ', isSignedIn)
                this.setState({status: isSignedIn})
            })
        })
    }    
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  checking if the user is signed in
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function isUserSignedIn() {
  gapi.load('auth2', function() {
      var isSignedIn = auth2.isSignedIn.get();
      console.log('is signed in? ', isSigned In)
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note about using this function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Sign-In library will, by default, sign you in automatically if you've already signed in.&lt;/li&gt;
&lt;li&gt;If you refresh the page, even after the user has signed in, then on first laod, you'll get  &lt;code&gt;auth2.isSignedIn.get() === false&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;After the user is signed in automatically (usually takes a sec), then &lt;code&gt;auth2.isSignedIn.get() === true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Depending on how you handle your login UI, your user might see that they are not logged in for a hot second. It's helpful to use the &lt;code&gt;isSignedIn.listen()&lt;/code&gt; callback if you want to know the precise moment this happens.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  getting the users info
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function showCurrentUserInfo() {
  gapi.load('auth2', function() {
      var googleUser = auth2.currentUser.get()
      console.log('users info ', googleUser)
  })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  only allowing users from a particular domain to login
&lt;/h3&gt;

&lt;p&gt;This is a little bit of a hack and is probably easy to circumvent, but you can use the &lt;code&gt;getHostedDomain()&lt;/code&gt; method to get the G-Suite domain the user comes from. If the user does not have a G-Suite domain, then it'll be blank.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function onSignIn(googleUser) {
    if(googleUser.getHostedDomain() !== 'mysite.com') {
        // show a Not Authorized message
    } else {
        // show the users dashboard
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  only allowing certain users to login
&lt;/h3&gt;

&lt;p&gt;This is even more of a hack, but seems to be the only you can do it from within javascript. You really shouldn't. Don't know why I'm including it. The brute method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function onSignIn(googleUser) {
    var profile = googleUser.getBasicProfile()
    if(profile.getEmail() === 'admin@example.com' ||
       profile.getEmail() === 'client@example.com') {
           // show the user dashboard
    } else {
        // show a Not Authorized message
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  hiding content until after the user logs in
&lt;/h3&gt;

&lt;p&gt;This is also a hack. This is easy to workaround if you fidget with the CSS in your browser, but can work if it fits your use case. The reason this is a bad idea is that in a static website, all of the information thats available in your HTML is available to the user. DO NOT USE THIS if you have actual sensitive information to hide. It is a good candidate for showing your favorite cat pictures.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
...
  &amp;lt;div id="greeting"&amp;gt;
    You are not authorized to view this content
  &amp;lt;/div&amp;gt;
  &amp;lt;div id="dashboard"&amp;gt;
    ...
  &amp;lt;/div&amp;gt;
  &amp;lt;script&amp;gt;
    // hide initially
    $('#dashboard').hide()

    function onSignIn(googleUser) {
      setTimeout(function() {
        // show the content
        $('#greeting').hide()
        $('#dashboard').show()
      }, 1000);
    }
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  send the token to identify the user (not their ID)
&lt;/h3&gt;

&lt;p&gt;If you're making a backend request, you'll want to send the users ID token to your backend as an Authorization header. On your backend, you'd then be able to validate and decode the ID token (&lt;a href="https://developers.google.com/identity/sign-in/web/backend-auth"&gt;see here for examples&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The benefit of the approach is that this ID token (a type of JWT token) is signed by Google, so you know the information hasn't been tampered with.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$.ajax({
  url: 'myapi/example',
  headers: {'Authorization': googleUser.getAuthResponse().id_token)},
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In this post, we've seen how you can configure the Google Sign-In library via javascript, and use it to do things like get the users info and check if they're signed in (theres some nuances to be aware of with the login flow). &lt;/p&gt;

&lt;p&gt;Demo code is available on &lt;a href="https://github.com/intricatecloud/google-sign-in-demo/blob/master/gapi-demo.html"&gt;Github intricatecloud/google-sign-in-demo&lt;/a&gt;. Replace &lt;code&gt;YOUR_CLIENT_ID&lt;/code&gt; with your client ID, and you'll be able to see the sign in buttons in action.&lt;/p&gt;

&lt;p&gt;For the last part of this series, we'll look at some examples of how you might use google sign-in in a React and Angular application. &lt;a href="https://www.intricatecloud.io/2019/08/adding-google-sign-in-to-your-webapp-a-react-example/"&gt;Check it out here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Originally published on &lt;a href="https://www.intricatecloud.io/2019/07/adding-google-sign-in-to-your-webapp-using-the-js-library/"&gt;www.intricatecloud.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>authentication</category>
      <category>webdev</category>
    </item>
    <item>
      <title>adding google sign-in to your web app - pt 1</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Wed, 17 Jul 2019 19:58:59 +0000</pubDate>
      <link>https://dev.to/intricatecloud/adding-google-sign-in-to-your-web-app-pt-1-435k</link>
      <guid>https://dev.to/intricatecloud/adding-google-sign-in-to-your-web-app-pt-1-435k</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/KwOmVpd1DUA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check out my new video where I walk through the code below!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gotten stuck in a rabbit hole figuring out how to add "Log In with Google" to your web app?  In this series, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why you might want to use the Sign-In for Website JS library, and getting started with it&lt;/li&gt;
&lt;li&gt;Adding the library via javascript&lt;/li&gt;
&lt;li&gt;adding it to an existing project (angular &amp;amp; react)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their docs give you a decent place to start if you know what you're looking for, but there's a few areas where their docs and other guides online can be confusing. There's also minimal guidance as to how to use it within existing projects. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Am I supposed to be following the &lt;a href="https://developers.google.com/identity/protocols/OAuth2UserAgent#example"&gt;Google Identity docs&lt;/a&gt; or do I need &lt;a href="https://developers.google.com/identity/sign-in/web/"&gt;Google Sign-in for Web?&lt;/a&gt;? &lt;/li&gt;
&lt;li&gt;Wait, why are those 2 guides so different? They even load different libraries!&lt;/li&gt;
&lt;li&gt;This should be simple! I'm just trying to load a users avatar!!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answer to "How do I add Log in with Google?" boils down to 1 question you need to be able to answer: What exactly are you trying to do?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you just want to be able to see the users name and picture, maybe their email? Use the &lt;a href="https://developers.google.com/identity/sign-in/web/"&gt;Sign-In for Websites library&lt;/a&gt; (full walkthrough below).&lt;/li&gt;
&lt;li&gt;Do you have your own user database and want to offer Google login as an extra option? This is Federated Login and you'd want to use &lt;a href="https://developers.google.com/identity/protocols/OpenIDConnect"&gt;their OpenID Connect protocol&lt;/a&gt;. This is used by platforms like auth0, firebase, oneidentity.&lt;/li&gt;
&lt;li&gt;Do you want to be able to interact with the users Google account and do things like see their calendar, or create a Google doc? You'll need to use &lt;a href="https://developers.google.com/identity/protocols/OAuth2UserAgent#example"&gt;their OAuth workflow&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the purposes of this series, I'm going to explore when you should use the Sign-In for Websites library which uses the OpenID Connect protocol (also used by Federated Login systems) - this library can be easily misunderstood, but is still incredibly useful. &lt;strong&gt;The goal of this authentication library is to let you identify a user with their Google account. Thats it.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  So when should I use it?
&lt;/h2&gt;

&lt;p&gt;Good reasons to use it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have some content stored on a backend service that you deliver via an API only after the user has signed in&lt;/li&gt;
&lt;li&gt;you only have to support Google/G-Suite users&lt;/li&gt;
&lt;li&gt;you dont need to authorize users (e.g. allow some users to be admins)&lt;/li&gt;
&lt;li&gt;your site is public&lt;/li&gt;
&lt;li&gt;you have a single-page app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use cases where it's worth considering federated login:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you have &lt;strong&gt;pre-existing user content&lt;/strong&gt; stored on a backend service&lt;/li&gt;
&lt;li&gt;you have an internal site that you want to restrict to users from a specific domain. (e.g. only users from @example.com should see it)&lt;/li&gt;
&lt;li&gt;you want to prevent people from seeing your page unless they've logged in. The best you can do with this library is show/hide elements on the page if the user logged in, but thats enough if you only load data from an API after a user has logged in.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The library is designed to be used with HTML/JS and only interacts with your page via the "Sign in with Google" button. You can integrate this with other frameworks like Angular/React which I'll be covering in the next part of this series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Google Sign-in step-by-step
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Hello World HTML
&lt;/h3&gt;

&lt;p&gt;To start with, all you need is an index.html file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;

&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;Google Auth Demo&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;Welcome to the Demo&amp;lt;/h1&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add the Google Sign-In Button
&lt;/h3&gt;

&lt;p&gt;Before adding in the button, you first need to create a client ID/secret which is a way of identifying that you, the developer, are allowing users to get the identity information from Google and delivered to your website.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating credentials for Sign-In
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Go to the Google API Console - &lt;a href="https://console.developers.google.com/apis/dashboard"&gt;https://console.developers.google.com/apis/dashboard&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create a new project, or use an existing project if you already have one set up.&lt;/li&gt;
&lt;li&gt;Then click on Credentials -&amp;gt; Create Credentials -&amp;gt; OAuth Client ID&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what I put in for this demo:&lt;/p&gt;

&lt;p&gt;Name: google-auth-demo&lt;br&gt;
Authorized Javascript Origins: &lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;&lt;br&gt;
Authorized Redirect URIs: empty&lt;/p&gt;

&lt;p&gt;*A note about the Javascript origins: if you have a plain HTML file that you load in your browser using a file path like /home/dperez/index.html, this won't work. You'll need to "serve" your website on your computer so that you have a URL, even if its just localhost. You can use &lt;code&gt;python -m SimpleHTTPServer 8080&lt;/code&gt; (which is commonly available) to serve up your current directory or you can use an npm package like &lt;a href="https://www.npmjs.com/package/http-server"&gt;http-server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You'll then get a Client ID &amp;amp; Client Secret. These are 2 identifiers for your Oauth Client. At this point, you have authorized that Client ID to accept logins and return you the users information. Copy them down. If you accidentally click out of the screen, you can always copy them again later.&lt;/p&gt;
&lt;h3&gt;
  
  
  Add the library + credentials + button to your HTML
&lt;/h3&gt;

&lt;p&gt;In your index.html page, add the following to your HTML page:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
&amp;lt;head&amp;gt;
  ...
  &amp;lt;meta name="google-signin-client_id" content="your-client-id-goes-here"&amp;gt;
  &amp;lt;script src="https://apis.google.com/js/platform.js" async defer&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;Welcome to the Demo&amp;lt;/h1&amp;gt;
  &amp;lt;div class="g-signin2"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script tag grabs the library from Google that reads your &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tag to use as the Client ID, and then automatically re-styles the button with the CSS class &lt;code&gt;.g-signin2&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;At this point, if you refresh your page, you should see a pretty Sign-In button. If you click it, you'll go through your Google Login flow in a popup, and you'll finally land back on your site, and the button will say, "Signed In". &lt;/p&gt;

&lt;p&gt;Great, we're most of the way there! But in its current form, this is still useless.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Identify the user
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Update the g-signin2 div to include the &lt;code&gt;data-onsuccess&lt;/code&gt; attribute:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div class="g-signin2" data-onsuccess="onSignIn"&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That attribute contains the name of the function that will be called once a user has successfully logged in with Google.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a function called "onSignIn".
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
  ...
  &amp;lt;script&amp;gt;
    function onSignIn(googleUser) {
      // get user profile information
      console.log(googleUser.getBasicProfile())
    }
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;onSignIn&lt;/code&gt; function will be called with an argument containing the information provided by Google. If you refresh the page, you'll notice that &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You're automatically signed in&lt;/li&gt;
&lt;li&gt;The button gets updated very shortly after refresh (1s delay)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you open up your javascript console, you'll see the users information printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  Eea: "108716981921401766503"
  Paa: "https://lh3.googleusercontent.com/-y_ba58pC4us/AAAAAAAAAAI/AAAAAAAAAYE/wMGKOxlWR90/s96-c/photo.jpg"
  U3: "perez.dp@gmail.com"
  ig: "danny perez"
  ofa: "danny"
  wea: "perez"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the basic profile containing a series of values that identify me as a user. Under any normal circumstances, this object would have fields like &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;email&lt;/code&gt;, but for some unknown reason (I wish I had the answer but &lt;a href="https://github.com/google/google-api-javascript-client/issues/440"&gt;they havent given an answer either&lt;/a&gt;), its gibberish - but at least its consistent and hasn't changed.&lt;/p&gt;

&lt;p&gt;You can either get the data directly by doing &lt;code&gt;googleUser.getBasicProfile()['U3']&lt;/code&gt; or use the more human-readable approach by using the functions like &lt;code&gt;googleUser.getBasicProfile().getName()&lt;/code&gt; or &lt;code&gt;googleUser.getBasicProfile().getEmail()&lt;/code&gt;. (&lt;a href="https://developers.google.com/identity/sign-in/web/reference#googleusergetid"&gt;See here for the javascript client api reference docs&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Sign out
&lt;/h2&gt;

&lt;p&gt;Add a sign out button after the user has signed in by adding a button in your index.html and the click handler in your javascript.&lt;/p&gt;

&lt;p&gt;index.html&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;body&amp;gt;
...
  &amp;lt;button onclick="signOut()"&amp;gt;Sign out&amp;lt;/button&amp;gt;
&amp;lt;/body&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function signOut() {
  console.log('user signed out')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! At this point, we've added a Sign-In with Google button and we can identify the user by name/email.  Also, thats it. Thats all this library can help you do.&lt;/p&gt;

&lt;p&gt;...but what about basic things like saving users data to the backend? or showing an admin page?! Why isn't this in the docs?! &lt;/p&gt;

&lt;p&gt;That'll be covered on the next post coming 7/22 - using the js library to add it to your site without HTML.&lt;/p&gt;

&lt;p&gt;If you found the post useful, give it a ❤️ and follow me to get notified of the next post.  &lt;/p&gt;

</description>
      <category>auth</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Using webdriverio with headless Chrome in Docker</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Tue, 07 May 2019 18:05:55 +0000</pubDate>
      <link>https://dev.to/intricatecloud/using-webdriverio-with-headless-chrome-in-docker-1cp1</link>
      <guid>https://dev.to/intricatecloud/using-webdriverio-with-headless-chrome-in-docker-1cp1</guid>
      <description>&lt;p&gt;Using headless chrome for your UI tests works great out of the box on your laptop, but it won’t work out of the box when you're trying to run your tests in Docker. One recent work project was getting webdriverio tests successfully running in a Docker container as part of a Jenkins pipeline. Locally, our tests worked fine, but when we ran them in docker, they became super flaky with errors showing random Chrome crashes, or “Chrome is unreachable” errors. Hope is not lost though - There is in fact a proper incantation of options to pass to Chrome to get it running successfully (and without random crashes).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt; Here's a snippet of &lt;code&gt;wdio.conf.js&lt;/code&gt; showing our Chrome configuration. To see why some of these need to be included, read on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// wdio.conf.js
...
browserName: 'chrome',
chromeOptions: {
  args: [
    '--disable-infobars',
    '--window-size=1280,800',
  ].concat((function() {
    return process.env.HEADLESS_CHROME === '1' ? [
      '--headless',
      '--no-sandbox',
      '--disable-gpu',
      '--disable-setuid-sandbox',
      '--disable-dev-shm-usage'] : [];
  })()),
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  --disable-dev-shm-usage
&lt;/h3&gt;

&lt;p&gt;Add this flag if you're seeing the error &lt;a href="https://github.com/elgalu/docker-selenium/issues/20"&gt;(like this guy did) “Session deleted because of page crash from tab crash”&lt;/a&gt; in your logs when your tests fail (or a message along the lines of a page crash), or Chrome is unreachable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do we need this?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There’s a tmp folder that Chrome uses related to its internal memory management that by default tries to use the /dev/shm directory. I don't quite understand the internals of Chrome, but here's &lt;a href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#tips"&gt;some details about this from the puppeteer project&lt;/a&gt;. Anyway, Docker containers have a &lt;a href="https://docs.docker.com/engine/reference/run/"&gt;default size of 64MB for /dev/shm (cmd+f for --shm-size)&lt;/a&gt;. Chrome quickly runs out of space in there and your page will then crash, taking your tests down with it. No bueno. There’s two solutions to this:&lt;/p&gt;

&lt;p&gt;The easiest way to deal with this problem is to just not use it. There are some small cons, but a good rule of thumb is that unless you're trying to parallelize multiple concurrent Chrome sessions within one container, you're probably safe to disable it by adding &lt;code&gt;--disable-dev-shm-usage&lt;/code&gt; to your Chrome options.&lt;/p&gt;

&lt;p&gt;If you are though (&lt;a href="https://github.com/GoogleChrome/puppeteer/issues/1834#issuecomment-381106859"&gt;like this engineer&lt;/a&gt;), then you'll need to increase the size of your /dev/shm. For Docker containers,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;there’s a flag for increasing the shared memory size &lt;code&gt;--shm-size=1G&lt;/code&gt; that you can pass to your docker run command.&lt;/li&gt;
&lt;li&gt;you can also mount /dev/shm from your host machine to the docker container via &lt;code&gt;-v /dev/shm:/dev/shm&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  --disable-setuid-sandbox --no-sandbox
&lt;/h3&gt;

&lt;p&gt;If you see errors failing to connect to Chrome &lt;a href="https://github.com/GoogleChrome/puppeteer/issues/370"&gt;like this&lt;/a&gt;, you might need these flags.  Chrome uses sandboxing in order to protect your machine from random content on the internet (&lt;a href="https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux_sandboxing.md"&gt;see details here&lt;/a&gt;). If you’re running Chrome inside of a &lt;em&gt;disposable&lt;/em&gt; container, then your container is already acting as a sandbox for Chrome, and so you don’t quite &lt;em&gt;need&lt;/em&gt; chrome’s sandbox (your mileage may vary here depending on what you’re trying to do). &lt;/p&gt;

&lt;p&gt;I typically see these two flags passed along together in various writeups online. &lt;a href="https://groups.google.com/a/chromium.org/forum/m/#!topic/chromium-discuss/kfhzPq_Al94"&gt;This answer from the Chromium team&lt;/a&gt; suggests that &lt;code&gt;--disable-setuid-sandbox&lt;/code&gt; was only used in older versions of chrome.&lt;/p&gt;

&lt;h3&gt;
  
  
  --disable-gpu
&lt;/h3&gt;

&lt;p&gt;The official Puppeteer docs suggest that this flag is only needed on Windows. Various snippets online usually use &lt;code&gt;--disable-gpu&lt;/code&gt; alongside &lt;code&gt;--headless&lt;/code&gt; because an earlier version of headless chrome had bugs with it.&lt;/p&gt;

&lt;h3&gt;
  
  
  --disable-infobars
&lt;/h3&gt;

&lt;p&gt;This flag can help a minor annoyance - if you take a screenshot of the browser in your tests, then you’ll see a bar at the top of your browser page saying that Chrome is being controlled by automated process. This also pushes elements farther down the page. Its usually fine, but if it gets in your way, then try using this option. This flag was at one point removed around &lt;a href="https://github.com/GoogleChrome/puppeteer/issues/1765#issuecomment-356761402"&gt;Jan 2018&lt;/a&gt;, and then &lt;a href="https://bugs.chromium.org/p/chromium/issues/detail?id=820453#c6"&gt;added back&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Yes, Chrome can run headlessly and it can run inside of a container, and be stable (for the most part). You do however need the right concoction of parameters to get it working right, but hopefully this saves you a few hours of debugging random page crashes. Try out those Chrome options in your configuration (they will also work if you're using puppeteer or other selenium/chrome combo). &lt;/p&gt;

&lt;p&gt;Running into different errors? See &lt;a href="https://www.intricatecloud.io/2019/03/debugging-webdriverio-tests-with-vscode/"&gt;how to attach the VSCode debugger to step through your UI tests&lt;/a&gt; to help you narrow down the issue. &lt;/p&gt;

</description>
      <category>webdriverio</category>
      <category>testing</category>
    </item>
    <item>
      <title>Debugging webdriverio tests with VSCode</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Tue, 02 Apr 2019 13:21:40 +0000</pubDate>
      <link>https://dev.to/intricatecloud/debugging-webdriverio-tests-with-vscode-34p0</link>
      <guid>https://dev.to/intricatecloud/debugging-webdriverio-tests-with-vscode-34p0</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/66E7y12GQaE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;EDIT - If you're interested in seeing me walk through this post live - check out my latest Youtube video!&lt;/p&gt;

&lt;p&gt;Debugging tests with webdriverio can get frustrating when you’re trying to figure out why your test is sometimes clicking the wrong elements or just plain not working. Am I using the correct selector? Which element is it actually clicking on? There’s 3 things that you can do to help you drill down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adding many console.log statements to your test

&lt;ul&gt;
&lt;li&gt;using a debugger to step through the test one line at a time&lt;/li&gt;
&lt;li&gt;using webdriverio’s &lt;code&gt;browser.debug()&lt;/code&gt; to get an interactive js session with the browser&lt;/li&gt;
&lt;li&gt;while this seems like the obvious choice, &lt;a href="https://www.intricatecloud.io/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/"&gt;using browser.debug has its own limitations that I describe here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I searched and had found this post of getting &lt;a href="http://blog.likewise.org/2017/02/debugging-a-javascript-webdriverio-project-in-vscode/"&gt;webdriverio tests running inside of vscode&lt;/a&gt; to help me step through a test file line by line. That post is using an older version of node, and newer versions of node (v8.11.x+) throw an error like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node --debug=127.0.0.1:5859

(node:29011) [DEP0062] DeprecationWarning: `node --debug` and `node --debug-brk` are invalid. Please use `node --inspect` or `node --inspect-brk` instead.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's 2 parts to get this to work with newer versions of node: VSCode configuration and webdriverio configuration&lt;/p&gt;

&lt;h2&gt;
  
  
  VSCode configuration
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/docs/editor/debugging#_launch-configurations"&gt;See here for creating a VSCode Launch Configuration&lt;/a&gt;. Adding this file will allow you to &lt;a href="https://code.visualstudio.com/docs/editor/debugging"&gt;run the VSCode debugger&lt;/a&gt; and step through your code line by line. &lt;/p&gt;

&lt;p&gt;Heres a working .vscode/launch.json file that you can use and adapt to fit your needs&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "type": "node",
  "request": "launch",
  "name": "Run in debug",
  "port": 5859,
  "timeout": 60000,
  "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/wdio",
  "cwd": "${workspaceRoot}",
  "console": "integratedTerminal",
  "args": [
      "--spec", "main.js"
  // "--spec main.js" will be passed to your executable as
  // "wdio '--spec main.js'" (which isn't what you want)
  ],
  "env": {
      "DEBUG": "1" 
      // use an environment variable to be able
      // to toggle debug mode on and off
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration will run your &lt;code&gt;runtimeExecutable&lt;/code&gt; and by defining the &lt;code&gt;"port"&lt;/code&gt; variable, it will attempt a connection to port 5859. Once that connection is successfully made, you'll be able to set breakpoints and step through your test.&lt;/p&gt;

&lt;h2&gt;
  
  
  webdriverio configuration
&lt;/h2&gt;

&lt;p&gt;On the webdriverio side, we need to tell it to listen for connections from a debugger on port 5859. &lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;wdio.conf.js&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;exports.config = {
  debug: true,
  execArgv: ['--inspect-brk=127.0.0.1:5859],

  // other wdio configuration
  specs: ['some/specs/here'],
  suites: {
    ....
  }
  ....
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet will start webdriverio and start listening for connections from a debugger on 127.0.0.1:5859 (which you did in your VSCode configuration). The program will stop at this point to wait for a debugger to connect, and if nothing connects, the command will fail.&lt;/p&gt;

&lt;p&gt;Once you run it successfully, you should see this type of output&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/Users/dperez/workspace/wdio/node_modules/.bin/wdio "--spec" "main.js"

Debugger listening on ws://127.0.0.1:5859/9698ad4c-8d7d-447f-a259-1c566cd511d6
Debugger attached.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the program pause at this point until a connection is made. If you never see "Debugger attached", it means VSCode has not connected to your program, and so you can't set breakpoints and debug.&lt;/p&gt;

&lt;p&gt;If you run this as part of your CI process (Gitlab/Jenkins), you can make debug mode configurable. You can use &lt;code&gt;env&lt;/code&gt; vars in your .vscode/launch.json configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
"console": "integratedTerminal",
"env": {
  "DEBUG": 1
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will run your program with that env var set by using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DEBUG=1 /Users/dperez/workspace/wdio/node_modules/.bin/wdio "--spec" "main.js"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your wdio.conf.js file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exports.config = {
  debug: process.env.DEBUG === '1',
  execArgv: process.env.DEBUG === '1' ? ['--inspect-brk=127.0.0.1:5859'] : []
  ...
  // remaining wdio options
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way, the program will only wait to attach to the debugger when you run it inside of VSCode (where the environment variable is set), and everywhere else it will run normally without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Add these snippets to your configuration if you need to step through your program and watch how the program is executing. It sure beats writing tons of console.logs all over your code. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interested in using webdriverio's &lt;code&gt;browser.debug()&lt;/code&gt; mode? &lt;a href="https://www.intricatecloud.io/2018/11/webdriverio-tips-using-browser-debug-to-help-debug-your-tests/"&gt;check out my post on using wdio's debug mode to play with the browser&lt;/a&gt;. &lt;/li&gt;
&lt;li&gt;In case you’re here because you’re trying to solve the dreaded &lt;a href="https://www.intricatecloud.io/2018/11/webdriverio-tips-element-wrapped-in-div-is-not-clickable/"&gt;‘element is not clickable at point’ error message, check out this post here&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to read more stuff like this, give me a ❤️ and follow me here on dev.to.&lt;/p&gt;

</description>
      <category>webdriverio</category>
      <category>testing</category>
    </item>
    <item>
      <title>9 books that helped me navigate my first time being a tech lead</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Wed, 12 Dec 2018 18:08:11 +0000</pubDate>
      <link>https://dev.to/intricatecloud/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead-eb7</link>
      <guid>https://dev.to/intricatecloud/9-books-that-helped-me-navigate-my-first-time-being-a-tech-lead-eb7</guid>
      <description>&lt;p&gt;The tech lead was moving to another team for a long-term assignment, and I took over as the engineering manager/team lead. From the outside, the tech lead's job seemed doable, but I quickly realized I was getting in over my head. Unfortunately, my team was responsible for a lot of centralized infrastructure as well as day-to-day technical operations. I had no tech lead training, and how could there be? I was certain that the tech lead role was so different across companies, how could there be guidelines to it? In my previous role as the senior engineer on my team, I felt capable of tackling larger projects, but I only ever had 1 project to tackle. Now, I needed to manage 3-5 projects for my small team of 5 engineers.&lt;/p&gt;

&lt;p&gt;The best I could do was do as the last person did which only gets you far enough to keep your head above water. I realized that the only way for me to get past this would be to hit the books, and learn all the management that I never learned in college. I read a lot that year. More than the past 3 years combined. The most helpful books I read all boiled down to 3 areas of the job that I (like many others) struggled with: dealing with team &amp;amp; individual performance, delegating, and making my team a great team to work on.&lt;/p&gt;

&lt;p&gt;*Disclaimer: I've tried to link to the authors website where I could. Otherwise, the links go to Amazon (not referral links) if you want to get the book. I have no association with any of the below authors, just a fan of their writing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dealing with performance
&lt;/h2&gt;

&lt;p&gt;One thing I felt affected the team was an under-performing team member. Surely like many others, I hadn't ever seen an effective performance review myself, or seen how other leads dealt with performance on their team (Thats probably a good thing, but unhelpful for me). While I know at a high level what you're supposed to do, like talk about and deal with the problem, I struggled to actually do it - how could I, a new lead, give feedback to someone who was previously a peer on my team? It's definitely awkward the first few times! Fortunately, in this area, there are people much smarter than I who have shared their experiences in great deal to help you get past these types of problems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.radicalcandor.com/"&gt;Radical Candor: Be a Kick-Ass Boss Without Losing Your Humanity by Kim Scott&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;If you want to focus on giving great feedback - this provides a pretty powerful mental model for giving and receiving feedback without feeling like an asshole. It's focused on being upfront with people, lest you be plotting against them or humiliating them.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/Leading-Teams-10-Challenges-Solutions-ebook/dp/B014K0ONV2"&gt;Leading Teams: 10 challenges and solutions by Mandy Flint, Elisabet Vinberg Hearn&lt;/a&gt; and &lt;a href="https://www.amazon.com/dp/1491932058"&gt;Debugging Teams: Better Productivity through Collaboration by Brian W. Fitzpatrick, Ben Collins-Sussman&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;If you want to focus on getting yourself un-stuck from some problems on your team - these 2 books go in-depth on common issues that teams might have, and some step-by-step guidelines for how to deal with them.&lt;/li&gt;
&lt;li&gt;This was an area I didn't feel comfortable asking any of the other managers about. We had many small teams, and we were all very friendly so it was hard to find people you can talk to. The next best thing was seeing what other more successful leaders had done in these scenarios.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://youtu.be/RXxHBSuW4lM"&gt;Manager Conversation with Low Performers at UMCB on Youtube&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;It's very rare you'll ever get to shadow someone else's performance review - they're private and personal by nature (or you're HR). If you've ever wondered what a "good" conversation about poor performance could look like, this video helped me a TON! There's some other related videos there that will show you what NOT to do, but it is great to see how you can quickly diffuse an awkward situation.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lesson #1 summary: Be incredibly explicit about your expectations &lt;strong&gt;with their job&lt;/strong&gt; so that they can never say, "How was I supposed to know?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegating
&lt;/h2&gt;

&lt;p&gt;Another awkward part about my job was telling people what to do - our team had a mission that we were mostly aligned on, but we don't always get to work on cool stuff and the work still has to get done on time. I had seen other people do it well, I had seen others do it poorly - but I wouldn't have been able to explain to you why. I felt awkward the first few times saying, "hey roger, can you take a look at this issue?" only to have that developer come back with something that I wasn't expecting (see Lesson #1).&lt;/p&gt;

&lt;p&gt;When I was a fellow engineer on the team, I felt capable of working on bigger projects and making sure that we shipped the right things, but now, I was also accountable for all the projects the team was working on, not just mine. I had about twice as many things to do now, and the typical-engineer-turned-manager in a bout of frustration might ask, "When am i supposed to code if I'm stuck in meetings and dealing with people all the time?" It was difficult to juggle all the projects that I was now responsible for, as well as do the work on critical projects, and plan cross-team initiatives, and insert 20 more things here. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/dp/0789428903"&gt;How to Delegate (Essential Managers Series) by Robert Heller&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/dp/0814414745"&gt;The Busy Manager's Guide to Delegation (Worksmart Series) by Richard Luecke, Perry McIntosh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To summarize these books: Be incredibly explicit about your expectations &lt;strong&gt;with projects/tasks&lt;/strong&gt; so that they can never say, "How was I supposed to know?" &lt;/p&gt;

&lt;p&gt;While these two books sound a little cheesy, they gave me a great framework and process for delegating. After reading them, I started blocking out time on my calendar for going through our projects and trying to match people's goals and motivations with the work we had to do. A bunch of us got AWS certificates, one engineer earned with a promotion, and an intern joined us full-time. And we built great stuff too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making a good team
&lt;/h2&gt;

&lt;p&gt;One way to build a better team is to see more teams and how they operate, and use them as guiding examples for building your own teams. The catch is, barring you leaving your job and working elsewhere, you won't get to see that many teams and so you might not even know what your team would look like at its best! I absolutely loved reading these books because they provided case studies of real teams with real stories across some high-profile companies. Some people had really crappy times at their job, others didn't and they explain why in-depth.&lt;/p&gt;

&lt;p&gt;These books are more focused on software engineering teams:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.thekua.com/atwork/2014/09/talking-with-tech-leads/"&gt;Talking with Tech Leads: From Novices to Practitioners by Patrick Kua&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;Patrick Kua is a great speaker &lt;a href="https://www.youtube.com/results?search_query=patrick+kua"&gt;with some of his talks available on Youtube&lt;/a&gt; about technical leadership covering things like &lt;a href="https://www.youtube.com/watch?v=CjgWwmBW-bc"&gt;What I wish I knew as a first time Tech Lead&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=N9UPW-2wL5U"&gt;Geek's Guide to Leading Teams&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/dp/149195177X"&gt;Building Software Teams: Ten Best Practices for Effective Software Development by Joost Visser, Sylvan Rigal, Gijs Wijnholds, Zeeger Lubsen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These books cover teams in general:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/dp/0596518021"&gt;Beautiful Teams: Inspiring and Cautionary Tales from Veteran Team Leaders by Andrew Stellman, Jennifer Greene&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/dp/B01HUER114"&gt;Extreme Teams: Why Pixar, Netflix, Airbnb, and Other Cutting-Edge Companies Succeed Where Most Fail by Robert Bruce Shaw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.amazon.com/dp/149195227X"&gt;Scaling Teams: Strategies for Building Successful Teams and Organizations by David Loftesness, Alexander Grosse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To summarize these in one sentence: Be incredibly explicit about your expectations &lt;strong&gt;with the team's culture&lt;/strong&gt; so that they can never say, "How as I supposed to know?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I had a great time leading my team for over a year. While at times, it was incredibly daunting to think how I would get through a particularly problematic week, my team would stay on track and over time, we were able to move to more proactive work. There's many different areas in management where you could spend days and day learning, but if you do 1 thing and nothing else...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tell your team to be incredibly explicit about their expectations from you as their lead so that you can never say, "How was I supposed to know?"&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;If you found this helpful, you can ❤️ 🦄 it and follow me here on dev.to 😄&lt;/p&gt;

&lt;p&gt;Lastly to fellow tech leads on dev.to - what was the hardest part of being a first-time tech lead?&lt;/p&gt;

</description>
      <category>career</category>
      <category>techlead</category>
    </item>
    <item>
      <title>3 things you might see in your logs once your site is public</title>
      <dc:creator>Danny Perez</dc:creator>
      <pubDate>Mon, 03 Dec 2018 23:44:54 +0000</pubDate>
      <link>https://dev.to/intricatecloud/3-things-you-might-see-in-your-logs-once-your-site-is-public-4lbd</link>
      <guid>https://dev.to/intricatecloud/3-things-you-might-see-in-your-logs-once-your-site-is-public-4lbd</guid>
      <description>&lt;p&gt;You've finished deploying your website to its new domain. You start to see your normal user traffic, but then you also notice funny patterns in your access logs. Here's a few examples of things you might see in your access logs once your site or API is public in production.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automated vulnerability scanners from all over the world&lt;/li&gt;
&lt;li&gt;Crawlers&lt;/li&gt;
&lt;li&gt;SQL Injection attempts&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Scanners
&lt;/h2&gt;

&lt;p&gt;You know what regularly shows up? Scanners. &lt;a href="https://geekflare.com/open-source-web-security-scanner/" rel="noopener noreferrer"&gt;Lots and lots of open source scanners&lt;/a&gt;. IPs originating from all over the world China, Ukraine, Russia. Randomly throughout the week, we'll see scanner traffic. What does scanner traffic look like?&lt;/p&gt;

&lt;p&gt;There's a few signs you can use to tell:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have slightly elevated error rates for a sustained period of ~30 minutes and suddenly dies off. A mix of 4xx's and 5xx's.
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2018%2F11%2F04064624%2FScreen-Shot-2018-11-04-at-1.46.07-AM.png" title="Elevated error rates" alt="elevated error rates" width="" height=""&gt;
&lt;/li&gt;
&lt;li&gt;It's requesting paths that don't exist in your application. &lt;/li&gt;
&lt;li&gt;Some scanners use a specific User-Agent header that identifies itself as a scanner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You might see requests 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;x.x.x.x - - [07/Apr/2018:02:50:27 +0000] "GET /wp-json/oembed/1.0/embed?url=..%2F..%2F..%2F..%2F..%2F..%2F..%2FWindows%2FSystem32%2Fdrivers%2Fetc%2Fhosts%00&amp;amp;format=xml HTTP/1.0" 404 132 "http://www.zzzz.yyy/xxxx" "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.57.2 (KHTML, like Gecko) Version/5.1.6 Safari/534.57.2"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In there is an encoded URL: &lt;code&gt;../../../../../../../Windows/System32/drivers/etc/hosts&amp;amp;&lt;/code&gt;. This type of request looks mainly like a recon attempt to see if it can even get to files in that directory.&lt;/p&gt;

&lt;p&gt;If you're running a wordpress site, always be prepared to receive a barrage of known exploits to your site. We're not even running a wordpress website and we get this type of thing in our access logs. Included there is a &lt;a href="https://blog.sucuri.net/2014/09/slider-revolution-plugin-critical-vulnerability-being-exploited.html" rel="noopener noreferrer"&gt;revslider_show_image vulnerability to steal wp-config file from 2014&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| count | uri                                                                           |
|-------|-------------------------------------------------------------------------------|
| 12    | /wp-admin/admin-ajax.php                                                      |
| 6     | /wp-admin/admin-ajax.php?action=revslider_show_image&amp;amp;img=../wp-config.php     |
| 6     | /wp-admin/tools.php?page=backup_manager&amp;amp;download_backup_file=../wp-config.php |
| 3     | /wp-admin/wp-login.php?action=register                                        |
| 2     | /help/wp/wp-admin/setup-config.php                                            |
| 1     | /help/new/wp-admin/setup-config.php                                           |
| 1     | /help/wp-admin/setup-config.php                                               |
| 1     | /wp-admin/js/password-strength-meter.min.js?ver=4.9.1                         |
| 1     | /wp-admin/js/password-strength-meter.min.js?ver=4.9.2                         |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on how your application is hosted, you may even see increased latencies during a scan:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2018%2F11%2F04065335%2FScreen-Shot-2018-11-04-at-1.53.20-AM.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fs3.amazonaws.com%2Fintricatecloud-content%2Fwp-content%2Fuploads%2F2018%2F11%2F04065335%2FScreen-Shot-2018-11-04-at-1.53.20-AM.png" title="increased latency graph" alt="increased latency graph" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Crawlers
&lt;/h2&gt;

&lt;p&gt;These are innocuous. Just Google and Bing indexing content. Typical internet being the internet. This particular crawler is nice enough to include a User Agent header that sends you to a site that says exactly what they do with all the data they collect. Check it out - &lt;a href="http://www.exensa.com/crawl" rel="noopener noreferrer"&gt;http://www.exensa.com/crawl&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x.x.x.x [30/Mar/2018:04:39:15 +0000] "GET /robots.txt HTTP/1.1" 404 136 "-" "Barkrowler/0.7 (+http://www.exensa.com/crawl)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SQL injection attacks
&lt;/h3&gt;

&lt;p&gt;You might see these recon attacks - you can take a known public URL and add a URL-encoded SELECT statement to see if anything funny comes out in the response. 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;x.x.x.x [11/Apr/2018:16:09:37 +0000] "GET /REDACTED&amp;amp;response_mode=%28SELECT%20%28CHR%28113%29%7C%7CCHR%28107%29%7C%7CCHR%28120%29%7C%7CCHR%28107%29%7C%7CCHR%28113%29%29%7C%7C%28SELECT%20%28CASE%20WHEN%20%285423%3D5423%29%20THEN%201%20ELSE%200%20END%29%29%3A%3Atext%7C%7C%28CHR%28113%29%7C%7CCHR%28122%29%7C%7CCHR%28112%29%7C%7CCHR%2898%29%7C%7CCHR%28113%29%29%29&amp;amp;response_type=code&amp;amp;scope=openid HTTP/1.1" 302 0 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.0 (KHTML, like Gecko) Chrome/4.0.202.0 Safari/532.0"

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

&lt;/div&gt;



&lt;p&gt;There's a SQL injection string in there that is URL encoded. If you decode it, you get this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(SELECT (CHR(113)||CHR(107)||CHR(120)||CHR(107)||CHR(113))||(SELECT (CASE WHEN (5423=5423) THEN 1 ELSE 0 END))::text||(CHR(113)||CHR(122)||CHR(112)||CHR(98)||CHR(113)))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are tests for injection. If the response contains something out of the ordinary, they know they can do it. Not entirely sure whats going on here though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defend against it
&lt;/h2&gt;

&lt;p&gt;Here's a few things that you can use to keep most of these people away.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use security groups (in AWS) / iptables to limit inbound traffic only to the ports that you need. If only and 80/443 are open, you're primarily prone to web application attacks instead of SSH/FTP/DNS and the like. Here's an &lt;a href="https://aws.amazon.com/whitepapers/aws-security-best-practices/" rel="noopener noreferrer"&gt;AWS Whitepaper on Security Best Practices&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Periodically review your logs to see what's going on. If you see SQL injection attempts, see which calls have returned a 200/500 - this means something potentially useful has made it to attacker.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Outright reject anything thats not even remotely close to your applications URLs - For example, you'd want to block Windows paths when your application is hosted on Linux like paths containing &lt;code&gt;System32&lt;/code&gt;, &lt;code&gt;cmd.exe&lt;/code&gt;, &lt;code&gt;C:\\&lt;/code&gt; in your access logs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run a scan against your application locally and be one step ahead of hackers. Know what you're exposed to first. Check out these &lt;a href="https://geekflare.com/open-source-web-security-scanner/" rel="noopener noreferrer"&gt;open source scanners&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Many of these problems go away if you &lt;a href="https://www.intricatecloud.io/2018/04/creating-a-serverless-static-website/" rel="noopener noreferrer"&gt;host your static website on AWS S3 - here's a guide I wrote if you're doing it for the first time&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;if you're looking for an alternative to Wordpress? See this guide for &lt;a href="https://www.intricatecloud.io/2018/06/part-1-build-test-deploy-and-monitor-a-static-website-building-a-blog-with-gatsby/" rel="noopener noreferrer"&gt;building a blog with gatsby.js&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you found this helpful, you can ❤️ it and follow me here on dev.to 😄&lt;/p&gt;

</description>
      <category>devops</category>
      <category>security</category>
      <category>aws</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
