<?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>Forem: Kenny Horna</title>
    <description>The latest articles on Forem by Kenny Horna (@kennyhorna).</description>
    <link>https://forem.com/kennyhorna</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%2F626900%2F52cef40d-970a-4d44-bf6d-8051b8a464be.jpeg</url>
      <title>Forem: Kenny Horna</title>
      <link>https://forem.com/kennyhorna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/kennyhorna"/>
    <language>en</language>
    <item>
      <title>Restricting access to certain files in our Laravel app</title>
      <dc:creator>Kenny Horna</dc:creator>
      <pubDate>Fri, 07 May 2021 01:54:39 +0000</pubDate>
      <link>https://forem.com/kennyhorna/restricting-access-to-certain-files-in-our-laravel-app-27gh</link>
      <guid>https://forem.com/kennyhorna/restricting-access-to-certain-files-in-our-laravel-app-27gh</guid>
      <description>&lt;p&gt;We generally want our app files to be public (profile pictures, product images, and so on). But what happens if we want to restrict the access to them to only logged-in users or users that have a certain role? Today you'll know how.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we need to do
&lt;/h2&gt;

&lt;p&gt;Before implementing it we have to understand the strategy. Basically, we'll do this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Define a new disk. This is for ease of reading (and writing) files&lt;/li&gt;
&lt;li&gt;Define the controller and method that will be responsible for returning the files&lt;/li&gt;
&lt;li&gt;Create the path to the elements of this new disk&lt;/li&gt;
&lt;li&gt;Securing our route&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple right? Well, it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Defining a new disk
&lt;/h3&gt;

&lt;p&gt;For this, head to the configuration file that handles it: &lt;code&gt;config/filesystems.php&lt;/code&gt;. It defines the different disks that our app provides.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;"disk"&lt;/strong&gt; is nothing more than a representation of a driver and location. You can have a disk that points to your app's local disk, AWS S3, or another different provider.&lt;/p&gt;

&lt;p&gt;In our case we will create the &lt;code&gt;'files'&lt;/code&gt; disk. This will be a local disk that will point to the &lt;code&gt;/storage/app/files&lt;/code&gt; directory. In that directory is where we will store our restricted files. &lt;/p&gt;

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

&lt;span class="s1"&gt;'disks'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="s1"&gt;'files'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'driver'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'local'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'root'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;storage_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'app/files'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="s1"&gt;'visibility'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'private'&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;blockquote&gt;
&lt;p&gt;Notice that we set the visibility as &lt;code&gt;'private'&lt;/code&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Defining our controller
&lt;/h3&gt;

&lt;p&gt;We create our controller:&lt;/p&gt;

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

php artisan make: controller FilesController &lt;span class="nt"&gt;--invokable&lt;/span&gt;


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

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;I opted for a &lt;a href="https://laravel.com/docs/master/controllers#single-action-controllers" rel="noopener noreferrer"&gt;single action controller&lt;/a&gt; since it will only have one method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We head over to our newly created controller and implement our logic.&lt;/p&gt;

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

&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Controllers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Storage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Symfony\Component\HttpFoundation\StreamedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilesController&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Controller&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;function&lt;/span&gt; &lt;span class="n"&gt;__invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;abort_if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'files'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"The file doesn't exist. Check the path."&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'files'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&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;Checking the code you'll see that we do 2 things: First, we check if the file exists. If it doesn't, we will abort the call by returning a &lt;code&gt;404&lt;/code&gt; response. After this, we will only use the path that was sent in the request to return the file.&lt;/p&gt;

&lt;p&gt;Notice that our method expects to receive the &lt;code&gt;$path&lt;/code&gt; parameter.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Defining our route
&lt;/h3&gt;

&lt;p&gt;We go to &lt;code&gt;routes/web.php&lt;/code&gt; and add our new route. In my case I will give it the following form:&lt;/p&gt;

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

&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Http\Controllers\FilesController&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Route&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Other routes..&lt;/span&gt;

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/files/{path}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FilesController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;--&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Now you understand where the &lt;code&gt;$path&lt;/code&gt; variable that reaches the controller comes from. So, this route will capture requests such as: &lt;code&gt;https://laravel8.test/files/my-file.jpg&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Securing our route
&lt;/h3&gt;

&lt;p&gt;Now we only have to secure the route. To accomplish it we have different options and that will depend on how you'll want to handle it in your app. In this example I will use the &lt;code&gt;'auth'&lt;/code&gt; middleware to do it. You could also use your ACL system to restrict access to your route or another alternative.&lt;/p&gt;

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

&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/files/{path}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;FilesController&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'auth'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                                                   &lt;span class="o"&gt;^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;As a test, I uploaded an image to the secure directory /&lt;code&gt;storage/app/arsenal-goal.jpg&lt;/code&gt;. In addition, I installed Laravel Breeze and created a user to compare the requests with/without an authenticated user.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

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

&lt;p&gt;On the left, we have the case when trying to access as a logged in user. On the right, you can see that it redirects us to the login, since I tried to access from incognito mode without logging in.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>security</category>
    </item>
  </channel>
</rss>
