<?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: Isaac Adams</title>
    <description>The latest articles on DEV Community by Isaac Adams (@isaacadams).</description>
    <link>https://dev.to/isaacadams</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%2F270347%2F300559f0-78d4-4d44-abc6-c63fdfa1c5d0.jpg</url>
      <title>DEV Community: Isaac Adams</title>
      <link>https://dev.to/isaacadams</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/isaacadams"/>
    <language>en</language>
    <item>
      <title>Learning to love my code again...</title>
      <dc:creator>Isaac Adams</dc:creator>
      <pubDate>Wed, 21 Feb 2024 01:14:24 +0000</pubDate>
      <link>https://dev.to/isaacadams/learning-to-love-my-code-again-4e5o</link>
      <guid>https://dev.to/isaacadams/learning-to-love-my-code-again-4e5o</guid>
      <description>&lt;p&gt;Writing good code is hard. It just is. &lt;/p&gt;

&lt;p&gt;I rarely review my own code and think to myself: "this code is amazing!"&lt;/p&gt;

&lt;p&gt;In fact, I usually hate it.&lt;/p&gt;

&lt;p&gt;Back when I was a new software developer, fresh out of college, I fell in love with every piece of code I wrote.&lt;/p&gt;

&lt;p&gt;A few years in, and I began to realize that whenever I came across code I had written only months ago, I hated it.&lt;/p&gt;

&lt;p&gt;Now, I write code, look at it, and hate it.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;It isn't because I hate software development. I love it.&lt;/p&gt;

&lt;p&gt;It's because I have had my code reviewed so many times by so many different people, I can now hear their voices in my head when I read my own code.&lt;/p&gt;

&lt;p&gt;Does anyone else experience this?&lt;/p&gt;

&lt;p&gt;I do not think this is a good thing. And my primary way of combatting this experience is to highlight what "good code" means to me.&lt;/p&gt;




&lt;h3&gt;
  
  
  It works.
&lt;/h3&gt;

&lt;p&gt;It does the thing it's supposed to do. Yup. That is a foundational aspect to good code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Intent is clear.
&lt;/h3&gt;

&lt;p&gt;I am not going to say something nerdy like "it must follow clean code principles". Or that variables should be named a certain way. Or something about spaces versus tabs.&lt;/p&gt;

&lt;p&gt;I am going to simply say that the intent of the code you wrote is &lt;em&gt;clear&lt;/em&gt;. That I can read the code and understand not only what it is doing, but the purpose or intention behind it.&lt;/p&gt;

&lt;p&gt;Whether you add comments or not. Whether the code is simple or complex. Whether it's 10 lines or 1,000 lines. Either I can understand it or I cannot.&lt;/p&gt;

&lt;h3&gt;
  
  
  It has &lt;em&gt;some&lt;/em&gt; unit tests.
&lt;/h3&gt;

&lt;p&gt;I don't care about perfect code coverage. I don't think anyone should because I think it is an unrealistic and unhelpful goal. If developers aren't careful, writing unit tests can often become a burden and counterproductive when every single function, class, or module needs to be tested against every single case imaginable.&lt;/p&gt;

&lt;p&gt;No. Nerp. Zip. Ain't nobody got time for that.&lt;/p&gt;

&lt;p&gt;Cover the base cases and sprinkle in a couple edge cases if they are likely to occur and/or easy to write.&lt;/p&gt;




&lt;p&gt;Although I struggle with hating my code these days, if I remind myself of these principles and my code lives up to the standards, it gives me a way to love my code again.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Securing a .NET 4.6.x API with OpenID Connect</title>
      <dc:creator>Isaac Adams</dc:creator>
      <pubDate>Wed, 08 Sep 2021 21:16:51 +0000</pubDate>
      <link>https://dev.to/isaacadams/registering-an-openid-client-in-net-4-6-x-136p</link>
      <guid>https://dev.to/isaacadams/registering-an-openid-client-in-net-4-6-x-136p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;OpenID Connect is powerful... and confusing. If you have ever experienced the frustration of adding authentication/authorization to an API built on .NET 4.6.x where the token comes from an OpenID Server, then you have come to the right place. I have accomplished this and can help you implement it in this guide.&lt;/p&gt;

&lt;p&gt;Here are the requirements of the system we will be implementing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protect legacy API from unauthorized access&lt;/li&gt;
&lt;li&gt;only requests that include a valid bearer token (&lt;code&gt;access_token&lt;/code&gt; from identity server) will be allowed&lt;/li&gt;
&lt;li&gt;identity server should issue a valid &lt;code&gt;access_token&lt;/code&gt; to our configured client&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Protecting the API
&lt;/h2&gt;

&lt;p&gt;In this particular instance, I want to protect a legacy application from unauthorized access. That kind of requirement calls for an API resource to be made. API resources are configured on the identity server and are used to protect API(s) from unauthorized access. So, I added the following code to my self hosted identity server which sits on a .NET core application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ApiResource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"legacy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"protects the legacy API from unauthorized access"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ApiSecrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Scopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// only interested in a single scope for this purpose&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"legacy.access"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"grants access to use the legacy API"&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;p&gt;Now I need to add authorization to the legacy application. First, add the &lt;code&gt;IdentityServer3.AccessTokenValidation&lt;/code&gt; nuget package to your .NET 4.6.x web app.&lt;/p&gt;

&lt;p&gt;Next, in &lt;code&gt;Startup.cs&lt;/code&gt;, add the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Owin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Owin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;IdentityServer3.AccessTokenValidation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;assembly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;OwinStartup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Legacy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Startup&lt;/span&gt;&lt;span class="p"&gt;))]&lt;/span&gt;
&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Legacy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Startup&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IAppBuilder&lt;/span&gt; &lt;span class="n"&gt;appBuilder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;appBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseIdentityServerBearerTokenAuthentication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;IdentityServerBearerTokenAuthenticationOptions&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Authority&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://localhost/identityserver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// this will ultimately change per environment and therefore come from configuration&lt;/span&gt;
                &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"legacy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ClientSecret&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"secret"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// this should be populated through configuration&lt;/span&gt;
                &lt;span class="n"&gt;RequiredScopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"legacy.access"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;ValidationMode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ValidationMode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;EnableValidationResultCache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will enable the use of the &lt;code&gt;[Authorize]&lt;/code&gt; attribute and respond to any request with &lt;code&gt;401 Unauthorized&lt;/code&gt; unless it has the Bearer Authorization header with a valid &lt;code&gt;access_token&lt;/code&gt; issued to it from the identity server with the &lt;code&gt;legacy.access&lt;/code&gt; scope.&lt;/p&gt;

&lt;p&gt;Yes, the &lt;code&gt;ClientId&lt;/code&gt; and &lt;code&gt;ClientSecret&lt;/code&gt; should match the &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;ApiSecret&lt;/code&gt; you configured in the &lt;code&gt;ApiResource&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching the access_token
&lt;/h2&gt;

&lt;p&gt;The last piece is getting the &lt;code&gt;access_token&lt;/code&gt; which will be added to the Authorization header for making authorized requests to the legacy app.&lt;/p&gt;

&lt;p&gt;In order to see this in action, we will need a client that can make an authenticated request to the identity server for the token.&lt;/p&gt;

&lt;p&gt;Add the following configuration to your identity server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ClientId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ClientSecrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"client-secret"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// the secret should be populated through configuration&lt;/span&gt;
    &lt;span class="n"&gt;AllowedGrantTypes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GrantTypes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ClientCredentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AllowedScopes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"legacy.access"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;AccessTokenType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AccessTokenType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&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;By adding &lt;code&gt;legacy.access&lt;/code&gt;, we are saying that this client can generate an &lt;code&gt;access_token&lt;/code&gt; that will include the &lt;code&gt;legacy.access&lt;/code&gt; scope.&lt;/p&gt;

&lt;p&gt;Using postman (or whatever tool you use), construct the following request given that your identity server is hosted @ localhost/identityserver&lt;/p&gt;

&lt;p&gt;!! NOTE: make sure your request parameters are formed as &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt; content type&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/identityserver/connect/token&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/x-www-form-urlencoded&lt;/span&gt;
&lt;span class="na"&gt;Content-Length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;89&lt;/span&gt;

client_id=client&amp;amp;client_secret=client-secret&amp;amp;grant_type=client_credentials&amp;amp;scope=legacy.access
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the expected response should look like the following&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;"access_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"xxx.xxx.xxx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expires_in"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer"&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;h2&gt;
  
  
  Using the access_token
&lt;/h2&gt;

&lt;p&gt;Lets setup a controller in our legacy app&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Web.Http&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;Legacy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;RoutePrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"api/test"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestController&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ApiController&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IHttpActionResult&lt;/span&gt; &lt;span class="nf"&gt;Test&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World"&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="n"&gt;Authorize&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"auth"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IHttpActionResult&lt;/span&gt; &lt;span class="nf"&gt;AuthTest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorized: Hello World"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure your controller is properly registered in your app by hitting the endpoint that doesn't have the &lt;code&gt;[Authorize]&lt;/code&gt; attribute.&lt;/p&gt;

&lt;p&gt;Now, take the &lt;code&gt;access_token&lt;/code&gt; you generated and place it inside the Authorization header (prefixed with "Bearer") and make a request to the &lt;code&gt;AuthTest()&lt;/code&gt; endpoint&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/legacy/api/test/auth&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
&lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bearer xxx.xxx.xxx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;If the response is &lt;code&gt;401 Unauthorized&lt;/code&gt; ❌, something is not configured properly 😢&lt;/li&gt;
&lt;li&gt;If the response is &lt;code&gt;200 OK&lt;/code&gt; 🚀 and the content is &lt;code&gt;Authorized: Hello World&lt;/code&gt;, you are in business ✅&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>dotnet</category>
      <category>csharp</category>
      <category>openid</category>
    </item>
    <item>
      <title>git: removing or replacing sensitive data</title>
      <dc:creator>Isaac Adams</dc:creator>
      <pubDate>Thu, 04 Mar 2021 15:40:46 +0000</pubDate>
      <link>https://dev.to/isaacadams/git-removing-or-replacing-sensitive-information-22n6</link>
      <guid>https://dev.to/isaacadams/git-removing-or-replacing-sensitive-information-22n6</guid>
      <description>&lt;h2&gt;
  
  
  Table Of Contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
Introduction &lt;/li&gt;
&lt;li&gt;Examples&lt;/li&gt;
&lt;li&gt;Email Use Case&lt;/li&gt;
&lt;li&gt;Definitions&lt;/li&gt;
&lt;li&gt;Credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;BFG Repo Cleaner is both faster and easier to use than using git filter-branch. It can handle most use cases for editing sensitive information in a git log.&lt;/p&gt;

&lt;p&gt;Read the instructions (see above series) for installing BFG before moving ahead. Otherwise, if you already have it installed, move onto the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Accidents happen. We have all been there. You committed sensitive data (e.g. passwords, keys, emails, or phone numbers) to the branch. Ok, no problem. Amend the previous commit. Easy. Right? Wrong.&lt;/p&gt;

&lt;p&gt;This time you didn't realize the mistake until several weeks later. The commit is now baked deep into the repository, making it almost impossible to fix using git. A git rebase is too difficult due to the complexity or amount of the changes. Other methods like git filter-branch might be too slow. Now what?&lt;/p&gt;

&lt;p&gt;[Enter BFG Repo Cleaner]&lt;/p&gt;

&lt;p&gt;BFG is a tool that cleans git repositories of sensitive data. It is easy to use and faster than git filter-branch. Using BFG is easy because it expects "expressions" as input. An "expression" (see definition) describes what the sensitive data is, how to search for it and how to edit it. BFG expects one or more expressions as input from the command line or from a local file.&lt;/p&gt;

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

&lt;p&gt;I will refer to the "expressions file" (see definition) in examples. This is the local file that contains a list of expressions separated by newlines.&lt;/p&gt;

&lt;p&gt;The creator of bfg gave an example of how to use the tool in a stack overflow answer (see credits).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ bfg --replace-text replacements.txt -fi *.php  my-repo.git&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;replacements.txt&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

PASSWORD1
PASSWORD2==&amp;gt;examplePass
PASSWORD3==&amp;gt;
regex:password=\w+==&amp;gt;password=
regex:\r(\n)==&amp;gt;$1


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

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;expression example&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PASSWORD1&lt;/td&gt;
&lt;td&gt;Replace literal string 'PASSWORD1' with '***REMOVED***' (default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PASSWORD2==&amp;gt;examplePass&lt;/td&gt;
&lt;td&gt;replace with 'examplePass' instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PASSWORD3==&amp;gt;&lt;/td&gt;
&lt;td&gt;replace with the empty string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;regex:password=\w+==&amp;gt;password=&lt;/td&gt;
&lt;td&gt;Replace, using a regex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;regex:\r(\n)==&amp;gt;$1&lt;/td&gt;
&lt;td&gt;Replace Windows newlines with Unix newlines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's unpack every argument in the example command&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--replace-text replacements.txt&lt;/code&gt; 

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--replace-text &amp;lt;path: file location describing replacements&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replacements.txt&lt;/code&gt;  is the path to the expressions file &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-fi *.php&lt;/code&gt; 

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-fi &amp;lt;glob: files to search&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the "filter content including" (-fi) option tells bfg to only perform its actions "in these files"&lt;/li&gt;
&lt;li&gt;configured to only perform the replacements in all files that end in &lt;code&gt;.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;remove this if you want bfg to search through all files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;my-repo.git&lt;/code&gt; 

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;path: folder location of repo&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;this is the path to the repo you want to &lt;/li&gt;
&lt;li&gt;use &lt;code&gt;.&lt;/code&gt; (a dot) as the input when invoking bfg from the root of your target repo&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Email Use Case &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I want to use BFG to replace my old email since my github account uses a new email. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;important note.&lt;/strong&gt; &lt;br&gt;
emails are best stored in configuration files, rather than hard coded into the source.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For this example, I will be using dummy emails.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ bfg -rt replace-email.txt .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;replace-email.txt&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

old-email@gmail.com==&amp;gt;my.new.email@gmail.com


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

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;old-email@gmail.com==&amp;gt;my.new.email@gmail.com&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;[left part of the expression]: &lt;code&gt;old-email@gmail.com&lt;/code&gt; is the old email I want to replace&lt;/li&gt;
&lt;li&gt;[middle part of the expression]: &lt;code&gt;==&amp;gt;&lt;/code&gt; indicates to bfg that I want to give it a value for replacing the old email&lt;/li&gt;
&lt;li&gt;[right part of the expression]: &lt;code&gt;my.new.email@gmail.com&lt;/code&gt; is the value that will replace the old email&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-rt replace-email.txt&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-rt&lt;/code&gt; is the alternative form of &lt;code&gt;--replace-text&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;replace-email.txt&lt;/code&gt; is the path to the expressions file
&lt;/li&gt;
&lt;li&gt;invoking bfg from the same directory as the file means makes it easier to reference the file&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;the dot indicates that the repo I am targeting is the current directory from which I am invoking bfg&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running bfg will rewrite the current branch with new commits. Be very careful when using this tool! In case something goes wrong, make a copy of the branch you are working on by checking it out with a new name.&lt;/p&gt;
&lt;h2&gt;
  
  
  Definitions &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;word&lt;/th&gt;
&lt;th&gt;description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;expression&lt;a&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;special syntax describing "the what" and "the how" to replace&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expressions file&lt;a&gt;&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;it is a file which contains expressions separated by new lines&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Credits &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;an interesting &lt;a href="https://github.com/newren/git-filter-repo/tree/b1606ba8ac8393d704ba41319c0bba3e334f3341" rel="noopener noreferrer"&gt;alternative&lt;/a&gt; to BFG&lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag__stackexchange--container"&gt;
  &lt;div class="ltag__stackexchange--title-container"&gt;
    
      &lt;div class="ltag__stackexchange--title"&gt;
        &lt;div class="ltag__stackexchange--header"&gt;
          &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackoverflow-logo-b42691ae545e4810b105ee957979a853a696085e67e43ee14c5699cf3e890fb4.svg" alt=""&gt;
          &lt;a href="https://stackoverflow.com/questions/4110652/how-to-substitute-text-from-files-in-git-history/15730571#15730571" rel="noopener noreferrer"&gt;
            &lt;span class="title-flare"&gt;answer&lt;/span&gt; re: How to substitute text from files in git history?
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="ltag__stackexchange--post-metadata"&gt;
          &lt;span&gt;Mar 31 '13&lt;/span&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;a class="ltag__stackexchange--score-container" href="https://stackoverflow.com/questions/4110652/how-to-substitute-text-from-files-in-git-history/15730571#15730571" rel="noopener noreferrer"&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-arrow-up-eff2e2849e67d156181d258e38802c0b57fa011f74164a7f97675ca3b6ab756b.svg" alt=""&gt;
        &lt;div class="ltag__stackexchange--score-number"&gt;
          123
        &lt;/div&gt;
        &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fstackexchange-arrow-down-4349fac0dd932d284fab7e4dd9846f19a3710558efde0d2dfd05897f3eeb9aba.svg" alt=""&gt;
      &lt;/a&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--body"&gt;
    
&lt;p&gt;I'd recommend using the &lt;a href="http://rtyley.github.io/bfg-repo-cleaner/" rel="nofollow noreferrer noopener"&gt;BFG Repo-Cleaner&lt;/a&gt;, a simpler, faster alternative to &lt;code&gt;git-filter-branch&lt;/code&gt; specifically designed for rewriting files from Git history.&lt;/p&gt;
&lt;p&gt;You should carefully follow these steps here: &lt;a href="https://rtyley.github.io/bfg-repo-cleaner/#usage" rel="nofollow noreferrer noopener"&gt;https://rtyley.github.io/bfg-repo-cleaner/#usage&lt;/a&gt; - but the core bit is just this: download the &lt;a href="http://rtyley.github.io/bfg-repo-cleaner/#download" rel="nofollow noreferrer noopener"&gt;BFG's jar&lt;/a&gt; (requires Java 7 or above) and run this command…&lt;/p&gt;
    
  &lt;/div&gt;
  &lt;div class="ltag__stackexchange--btn--container"&gt;
    &lt;a href="https://stackoverflow.com/questions/4110652/how-to-substitute-text-from-files-in-git-history/15730571#15730571" class="ltag__stackexchange--btn" rel="noopener noreferrer"&gt;Open Full Answer&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;



</description>
      <category>git</category>
      <category>tutorial</category>
      <category>productivity</category>
      <category>tooling</category>
    </item>
    <item>
      <title>git: BFG installation</title>
      <dc:creator>Isaac Adams</dc:creator>
      <pubDate>Thu, 04 Mar 2021 12:51:25 +0000</pubDate>
      <link>https://dev.to/isaacadams/git-bfg-installation-5ee1</link>
      <guid>https://dev.to/isaacadams/git-bfg-installation-5ee1</guid>
      <description>&lt;p&gt;BFG Repo Cleaner is both faster and easier to use than using git filter-branch. It handles simple use cases for editing sensitive information in git. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rtyley.github.io/bfg-repo-cleaner/"&gt;BFG usage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://repo1.maven.org/maven2/com/madgag/bfg/1.13.2/bfg-1.13.2.jar"&gt;BFG download&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.java.com/en/download/manual.jsp"&gt;java download&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Invoking BFG
&lt;/h2&gt;

&lt;p&gt;BFG is a .jar file that is invoked from the command line using java.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ java -jar path/to/bfg.jar&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Invoking BFG this way can become tedious...&lt;/p&gt;

&lt;h2&gt;
  
  
  Easier way to reference BFG?
&lt;/h2&gt;

&lt;p&gt;To simplify it, consider creating an alias&lt;/p&gt;

&lt;p&gt;.bashrc&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
alias bfg='java -jar path/to/bfg.jar'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BFG is now invokable from the command line in any directory: &lt;/p&gt;

&lt;p&gt;&lt;code&gt;$ bfg ...&lt;/code&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>tutorial</category>
      <category>tooling</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Fixing slow down speeds w/ curl in alpine docker images</title>
      <dc:creator>Isaac Adams</dc:creator>
      <pubDate>Thu, 21 Nov 2019 20:21:47 +0000</pubDate>
      <link>https://dev.to/isaacadams/fixing-slow-down-speeds-w-curl-in-alpine-docker-images-3bdj</link>
      <guid>https://dev.to/isaacadams/fixing-slow-down-speeds-w-curl-in-alpine-docker-images-3bdj</guid>
      <description>&lt;h1&gt;
  
  
  The problem
&lt;/h1&gt;

&lt;p&gt;After playing around with using the jenkins:alpine base image and attempting to download a .war (60 MB) file using curl, I discovered that it was EXTREMELY slow. I am talking about the difference between it taking a minute and a half on my host machine and 15 mins during the docker image build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; jenkins:alpine&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_WAR="/usr/share/jenkins/jenkins.war"&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    https://updates.jenkins-ci.org/download/war/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/jenkins.war
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;jenkins:jenkins &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; jenkins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Obviously, this is ridiculous and there had to be something wrong. After searching online, I found there was very little documentation on how to resolve this issue, so I started doing my own experimenting.&lt;/p&gt;

&lt;h4&gt;
  
  
  Overview
&lt;/h4&gt;

&lt;p&gt;downloading .war of 60 MB took&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;minute and a half on host machine
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h4RwQIfb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/bjkpo00e3jrz6ddzrclj.PNG" alt="host machine" width="570" height="126"&gt;
&lt;/li&gt;
&lt;li&gt;15 min during docker alpine image build
&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--AqpcX0iP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/b5f7m5hceuhk59o4xijm.PNG" alt="alpine slow" width="800" height="253"&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  NOTICE
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;made some updates were made on 12.4.2019&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What did NOT work 👎
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Adding &lt;code&gt;docker build ...&lt;/code&gt; options
&lt;/h3&gt;

&lt;p&gt;I was unable to get any increase from adding in options to &lt;code&gt;docker build ...&lt;/code&gt; such as ...&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--network host&lt;/code&gt; : performs the commands using the host network&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-m 1GB&lt;/code&gt; : allocates more memory to the image build&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Fixing alpine DNS issues
&lt;/h3&gt;

&lt;p&gt;I also read online about some DNS issues that alpine has and followed some of their instructions for fixing them. None of that helped increase download speeds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using the docker "ADD" command (12.4.2019)
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;ADD&lt;/code&gt; is easily the cleanest and most straightforward option. However, I found it was just as slow as using curl. See the secondary solution at the end of this article to see how I was able to still use the simplicity of &lt;code&gt;ADD&lt;/code&gt; and while also getting an increase in speed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; jenkins:alpine&lt;/span&gt;
...
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://updates.jenkins-ci.org/download/war/${JENKINS_VERSION} ${JENKINS_WAR}&lt;/span&gt;
...

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Changing image base &lt;em&gt;(some success)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;I noticed that when switched out from an alpine based image, I was able to significantly increase my download speeds. However, it still wasn't as fast as my host machine.&lt;/p&gt;

&lt;p&gt;Notice the change in "TAG" in first line the &lt;code&gt;FROM jenkins:TAG&lt;/code&gt; from "alpine" to "latest". The "latest" tag does not use alpine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; jenkins:latest&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_WAR="/usr/share/jenkins/jenkins.war"&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    https://updates.jenkins-ci.org/download/war/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/jenkins.war
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;jenkins:jenkins &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; jenkins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5O8ftS7p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/3vqmrpxc7jxhksyqbcvg.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5O8ftS7p--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/3vqmrpxc7jxhksyqbcvg.PNG" alt="non alpine faster" width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Increasing curl limit rate &lt;em&gt;(some success)&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;Using the alpine base image, I found that setting &lt;code&gt;--limit-rate 1G&lt;/code&gt; as a &lt;code&gt;curl ...&lt;/code&gt; option increased the download speed. I could have stopped at this point, but the download was still taking about 1-5 mins. It seemed rather unstable to me, so I kept experimenting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; jenkins:alpine&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_WAR="/usr/share/jenkins/jenkins.war"&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;--limit-rate&lt;/span&gt; 1G &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    https://updates.jenkins-ci.org/download/war/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/jenkins.war
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;jenkins:jenkins &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; jenkins&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2XXQ762y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/s58w9n6g6skve9gjcr1s.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2XXQ762y--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/s58w9n6g6skve9gjcr1s.PNG" alt="Alt Text" width="800" height="111"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  The solution 😄
&lt;/h1&gt;

&lt;p&gt;I found that by combining switching the image base (from alpine to debian) and setting &lt;code&gt;--limit-rate 1G&lt;/code&gt; curl option I was able to make the download happen in &lt;strong&gt;2 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I still wanted to use alpine as my base image, so I changed the dockerfile to use multi-stage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;debian:buster-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;downloader&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt &lt;span class="nb"&gt;install &lt;/span&gt;curl &lt;span class="nt"&gt;-y&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/root&lt;/span&gt;

&lt;span class="c"&gt;# running curl inside the debian image instead of alpine&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;--limit-rate&lt;/span&gt; 1G &lt;span class="nt"&gt;-o&lt;/span&gt; jenkins.war &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    https://updates.jenkins-ci.org/download/war/&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;/jenkins.war


&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; jenkins:alpine&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_WAR="/usr/share/jenkins/jenkins.war"&lt;/span&gt;
&lt;span class="c"&gt;# getting the downloaded .war file from the debian image&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=downloader /home/root/jenkins.war ${JENKINS_WAR}&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;jenkins:jenkins &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; jenkins&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--D9_8wwcC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/dps7mhqd1qi1slfqliqe.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--D9_8wwcC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/dps7mhqd1qi1slfqliqe.PNG" alt="debian image fastest" width="749" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Another &lt;em&gt;better&lt;/em&gt; solution 🚀 (12.4.2019)
&lt;/h1&gt;

&lt;p&gt;It suddenly hit me that the &lt;code&gt;ADD&lt;/code&gt; or &lt;code&gt;COPY&lt;/code&gt; docker commands &lt;em&gt;must&lt;/em&gt; have the capability to use URLs. Once I confirmed that &lt;code&gt;ADD&lt;/code&gt; can do this, I felt so dumb. When I first tried using &lt;code&gt;ADD&lt;/code&gt; to download the jenkins.war file it was still very slow 😢 &lt;/p&gt;

&lt;p&gt;BUT, then I thought, "what about switching the base image and then using ADD?". And what do you know, it was FAST 😄 It took about 3 seconds to download. This is more simple because the code is easier to follow and it removes the need to run &lt;code&gt;apt update&lt;/code&gt; and &lt;code&gt;apt install curl&lt;/code&gt;. This results in a smaller image overall.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;debian:buster-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;downloader&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; JENKINS_VERSION&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /home/root&lt;/span&gt;
&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; https://updates.jenkins-ci.org/download/war/${JENKINS_VERSION}/jenkins.war \&lt;/span&gt;
jenkins.war

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; jenkins:alpine&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JENKINS_WAR="/usr/share/jenkins/jenkins.war"&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=downloader /home/root/jenkins.war ${JENKINS_WAR}&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown &lt;/span&gt;jenkins:jenkins &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;JENKINS_WAR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>docker</category>
      <category>productivity</category>
      <category>devops</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
